Reversing equal sized chunks of a list - python

If I have a list:
[1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3]
My goal is to split it into equal sized chunks of n, reverse each chunk, and then put the chunks back in order. So, for the example above, for chunk size 4, I'd get:
[1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3]
[_________] [_________] [________] [______]
| | | |
1 2 3 4 (this is smaller than 4 but receives the same treatment)
||
[4, 3, 2, 1, 4, 3, 2, 1, 4, 3, 2, 1, 3, 2, 1]
This is what I have:
n = 4
l = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3]
chunks = [l[i : i + n] for i in range(0, len(l), n)]
print(chunks)
# [[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3]]
for i in range(len(chunks)):
chunks[i] = list(reversed(chunks[i])) # or chunks[i] = chunks[i][::-1]
from functools import reduce
out = list(reduce(lambda x, y: x + y, chunks))
print(out)
# [4, 3, 2, 1, 4, 3, 2, 1, 4, 3, 2, 1, 3, 2, 1]
I don't think this is very good though. Is there another way that better utilises python's libraries than this?

What about using the following list comprehension:
[x for i in range(0,len(l),4) for x in reversed(l[i:i+4])]
or with parameterized chunk size:
chunk = 4
[x for i in range(0,len(l),chunk) for x in reversed(l[i:i+chunk])]
This generates:
>>> [x for i in range(0,len(l),4) for x in reversed(l[i:i+4])]
[4, 3, 2, 1, 4, 3, 2, 1, 4, 3, 2, 1, 3, 2, 1]
for your given list. Furthermore I guess it is quite declarative (the reversed(..) indicates that you reverse, etc.)

List comprehension sure looks nice, but perhaps it is more readable to simply do a for loop and update each chunk by itself:
lst = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3]
chunk = 4
for i in range(0, len(lst), chunk):
lst[i:i+chunk] = reversed(lst[i:i+chunk])
print(lst)
# [4, 3, 2, 1, 4, 3, 2, 1, 4, 3, 2, 1, 3, 2, 1]
If you do not want the update in place, you can do out = lst[:] at the start.

Related

Avoid duplicates in nested loop python

So i have nested loops and array
[[0, 1], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4]]:
for x in string_list:
for y in string_list:
print(x,y)
provides me the output
[0, 1] [0, 1]
[0, 1] [0, 1, 2, 3, 4, 5, 6]
[0, 1] [0, 1, 2, 3, 4]
[0, 1, 2, 3, 4, 5, 6] [0, 1]
[0, 1, 2, 3, 4, 5, 6] [0, 1, 2, 3, 4, 5, 6]
[0, 1, 2, 3, 4, 5, 6] [0, 1, 2, 3, 4]
[0, 1, 2, 3, 4] [0, 1]
[0, 1, 2, 3, 4] [0, 1, 2, 3, 4, 5, 6]
[0, 1, 2, 3, 4] [0, 1, 2, 3, 4]
But i have a lot of duplicates pairs and i did:
for x in range(0, len(string_list)):
for y in range(x+1, len(string_list)):
print(x,y, string_list)
but it's working only for 2 digit pairs.
So what i want is:
[0, 1] [0, 1]
[0, 1] [0, 1, 2, 3, 4, 5, 6]
[0, 1] [0, 1, 2, 3, 4]
**[0, 1, 2, 3, 4, 5, 6] [0, 1]** // avoid to output that pair cause we had that one
[0, 1, 2, 3, 4, 5, 6] [0, 1, 2, 3, 4, 5, 6]
[0, 1, 2, 3, 4, 5, 6] [0, 1, 2, 3, 4]
[0, 1, 2, 3, 4] [0, 1]
**[0, 1, 2, 3, 4] [0, 1, 2, 3, 4, 5, 6]** // avoid to output that pair cause we had that one
[0, 1, 2, 3, 4] [0, 1, 2, 3, 4]
does it possible to do without using itertools ?
Thank you!
for k, x in enumerate(string_list):
for y in string_list[k:]:
print(x,y)
You can use itertools.combinations:
for x, y in it.combinations(string_list, 2):
# process x, y
Obviously using itertools.combinations is ideal, but since you said you don't want to use itertools, you could use a set comprehension to build the set of unique combinations (you have to convert the lists to tuples to make them hashable), then convert them back to lists as needed:
[list(list(t) for t in f) for f in {
frozenset((tuple(x), tuple(y))) for y in string_list for x in string_list
}]
You can put a continue statement in the inner loop to skip duplicates:
for x in string_list:
for y in string_list:
if x == y:
continue
print(x,y)

Python how to shuffle an ordered list to make sequences of elements?

For instance we have an ordered list:
a = [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4]
I want to reshuffle this array to form:
a = [1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]
Currently I'm doing:
a = np.array([1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4])
n_unique_elements = 4
arrays_with_same_elements = np.array_split(a, 5)
for idx in range(n_unique_elements):
final_list.append(list_similar_a[0][idx])
final_list.append(list_similar_a[1][idx])
final_list.append(list_similar_a[2][idx])
final_list.append(list_similar_a[3][idx])
final_list.append(list_similar_a[4][idx])
So the variable
final_list = [0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4, 0, 1, 2, 3, 4]
There must a pythonic way of doing this. Perhaps a built-in function in numpy? What other different techniques come to your mind to solve this problem?
You can use the key parameter in sort() method:
https://docs.python.org/3.3/howto/sorting.html#key-functions
or
using set()
a = [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4]
b = set(a)
final_list = list(b) * len(b)
So, You can use numpy:
a.reshape([4,3]).T.flatten()
so the .reshape() puts it into a rectangualr martix, the .T switches rows and columns and the .flatten() puts it in a linear vector again
now you only need to come up with parameters for the reshape part e.g. .reshape([step, repetition])
Try it: (pure python without external lib)
STEP = 3
lst0 = [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4]
lst1 = []
for x in range(0, STEP):
for y in range(0, len(lst0), STEP):
lst1.append(lst0[y + x])
print(lst1)
output
[1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4]
Try this:
a = np.array([1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4])
uniqueValues, occurCount = np.unique(a, return_counts=True) # uniqueValues is the array of unique elements of main list
#occurCount is array containing frequency of unique elements
common_occur=min(occurCount) # get frequency of co-occurrance
final_array=np.tile(uniqueValues,common_occur) #get tiled output array
If the frequency of each element is the same and is known beforehand, this solution would also work
FREQ = 3
output = a[::FREQ] * (len(a) // FREQ)
Another numpy-based solution is this:
FREQ = 3
output = a.reshape((-1, FREQ)).flatten(order='F')
The order='F' argument flattens the matrix by columns.

how to apply filter function to items in multiple list?

I wanna get values from multiple(?) list, which is not 0(I think filter can be one of solutions).
list as below:
>>ls = [[i for i in np.random.randint(0, 5, 5)] for _ in range(7)]
>>ls
>>
[[2, 3, 3, 0, 0],
[4, 2, 4, 3, 2],
[1, 2, 4, 2, 4],
[2, 3, 4, 3, 1],
[0, 1, 0, 3, 0],
[3, 4, 4, 4, 3],
[3, 4, 3, 3, 2]]
Expected result is:
[2,3,3,4,2,4,3,2,1,2,4,2,4,2,3,4,3,1,1,3,3,4,4,4,3,3,4,3,3,2]
I tried using filter function, wanted expand this idea, but I failed:
>> [elem for elem in filter(lambda x: x if x != 0 else False, ls[0])]
>>
[2, 3, 3]
I wanna find fastest way to get expected result, not using for loop.
Would you suggest any good idea?
Edit:
Oops, Sorry for confusing you.
I saying 'not usting for loop' means, I wanna use list comprehension instead of for loop, because I heard list comprehension fater than for loop.
Use a nested list comprehension:
[j for i in ls for j in i if j != 0]
ls = [[i for i in np.random.randint(0, 5, 5)] for _ in range(7)]
[[1, 0, 3, 0, 0],
[1, 2, 2, 3, 0],
[1, 1, 1, 4, 3],
[1, 0, 3, 0, 4],
[2, 0, 3, 0, 2],
[1, 0, 4, 4, 0],
[2, 4, 1, 1, 2]]
[j for i in ls for j in i if j != 0]
# [1, 3, 1, 2, 2, 3, 1, 1, 1, 4, 3, 1, 3, 4, 2, 3, 2, 1, 4, 4, 2, 4, 1, 1, 2]
If you want to avoid any explicit looping here's an option using itertools.chain and filter:
from itertools import chain
list(filter(lambda x: x != 0, chain(*ls)))
# [1, 3, 1, 2, 2, 3, 1, 1, 1, 4, 3, 1, 3, 4, 2, 3, 2, 1, 4, 4, 2, 4, 1, 1, 2]
Looks like you're also using NumPy for creating the list. Note that this would be way simpler and more efficient using np.nonzero:
import numpy as np
a = np.random.randint(0, 5, (7,5))
a[np.nonzero(a)]
# [1, 3, 1, 2, 2, 3, 1, 1, 1, 4, 3, 1, 3, 4, 2, 3, 2, 1, 4, 4, 2, 4, 1, 1, 2]
Adding to #yatu's answer, since it's 0, you can just do if x:
print([x for i in ls for x in i if x])
Or without loop:
print(np.array(ls).flatten()[np.array(ls).flatten() != 0].tolist())
Both output:
[1, 4, 4, 2, 1, 3, 3, 4, 4, 1, 3, 3, 2, 2, 2, 1, 4, 2, 2, 1, 4, 1, 2, 3, 2, 4]

Make function working for a single tuple, work for a list of tuples

I have a function
def series_score(sailor, races_to_discard):
places = sailor[1]
for i in range(races_to_discard):
places.remove(max(places))
print(places)
sum_of_places = sum(places)
print(sum_of_places)
that modifies a tuple like this
sailor = ("bob", [2, 4, 1, 1, 2, 5])
into this by removing the highest number in the list
sailor = ("bob", [2, 4, 1, 1, 2])
how could I adapt the function to work on list of tuples like this
list_of_sailors = [('Clare', [3, 1, 1, 2, 1, 1]), ('Bob', [2, 2, 3, 1, 2, 3]), ('Alice', [1, 3, 2, 3, 3, 2]), ('Eva', [4, 5, 4, 4, 5, 5]), ('Dennis', [5, 4, 5, 5, 4, 4])]
You could do something like this
for sailor,races in list_of_sailors:
series_score(sailor, races)
Just include another loop. Here, I'm taking off the highest two numbers in every array:
def series_score(sailors, races_to_discard):
for sailor in sailors:
places = sailor[1]
for i in range(races_to_discard):
places.remove(max(places))
print(places)
sum_of_places = sum(places)
print(sum_of_places)
print(series_score([('Clare', [3, 1, 1, 2, 1, 1]), ('Bob', [2, 2, 3, 1, 2, 3]), ('Alice', [1, 3, 2, 3, 3, 2]), ('Eva', [4, 5, 4, 4, 5, 5]), ('Dennis', [5, 4, 5, 5, 4, 4])], 2))
>>>[1, 1, 1, 1]
4
[2, 2, 1, 2]
7
[1, 2, 3, 2]
8
[4, 4, 4, 5]
17
[4, 5, 4, 4]
17

Python: Sorting a List

I'm trying to sort a list by moving through it and checking if a given element of the list is greater than the next element of the list, and if so, moving it accordingly so that the smaller number is to the left, larger to the right.
This is the code I have so far:
L = [3, 4, 1, 5, 2, 0]
for i in range(0, (len(L)-1)):
if L[i] > L[i+1]:
L[i], L[i+1] = L[i+1], L[i]
print(L)
The output of this for the three iterations are as follows:
[3, 1, 4, 5, 2, 0]
[3, 1, 4, 2, 5, 0]
[3, 1, 4, 2, 0, 5]
My goal is to get it to read [0, 1, 2, 3, 4, 5]. I realize I can just use the sorted() function but I'd prefer not to. I was thinking that I could try and iterate the for loop over the list multiple times, but I am unsure as to how to do so.
Currently you only compare each element and it's successor. However you really want to compare it to all following elements (starting with i+1 and ending with len(L)), so you need a double loop:
L = [3, 4, 1, 5, 2, 0]
for i in range(0, (len(L)-1)):
for j in range(i+1, len(L)):
if L[i] > L[j]:
L[i], L[j] = L[j], L[i]
print(L)
Which prints the following steps:
[1, 4, 3, 5, 2, 0]
[0, 4, 3, 5, 2, 1]
[0, 3, 4, 5, 2, 1]
[0, 2, 4, 5, 3, 1]
[0, 1, 4, 5, 3, 2]
[0, 1, 3, 5, 4, 2]
[0, 1, 2, 5, 4, 3]
[0, 1, 2, 4, 5, 3]
[0, 1, 2, 3, 5, 4]
[0, 1, 2, 3, 4, 5]
You could just put it inside a while loop:
L = [3, 4, 1, 5, 2, 0]
flag = True
while flag:
flag = False
for i in range(0, (len(L)-1)):
if L[i] > L[i+1]:
L[i], L[i+1] = L[i+1], L[i]
flag = True
print(L)
Output:
[3, 1, 4, 5, 2, 0]
[3, 1, 4, 2, 5, 0]
[3, 1, 4, 2, 0, 5]
[1, 3, 4, 2, 0, 5]
[1, 3, 2, 4, 0, 5]
[1, 3, 2, 0, 4, 5]
[1, 2, 3, 0, 4, 5]
[1, 2, 0, 3, 4, 5]
[1, 0, 2, 3, 4, 5]
[0, 1, 2, 3, 4, 5]

Categories