Python inception of generators - python

I'm trying to make a general way to generate all the combinations of multiple ranges or lists, for example,
[range(0, 2), range(2, 5), range(4, 6), range(2, 3)],
which should return a 2x3x2x1 = 12 element list.
[[0, 2, 4, 2],
[0, 2, 5, 2],
[0, 3, 4, 2],
[0, 3, 5, 2],
[0, 4, 4, 2],
[0, 4, 5, 2],
[1, 2, 4, 2],
[1, 2, 5, 2],
[1, 3, 4, 2],
[1, 3, 5, 2],
[1, 4, 4, 2],
[1, 4, 5, 2]]
So far, everything is fine here. When I hardcode it, by doing
x = [ ( [a,b] for a in rgs[0] for b in rgs[1] ) ]
x.append( ( a + [b] for a in x[-1] for b in rgs[2]) )
x.append( ( a + [b] for a in x[-1] for b in rgs[3]) )
I get the good result. However, when I attempt to generalize it, by doing
x = [ ( [a,b] for a in rgs[0] for b in rgs[1] ) ]
for i in range(1,len(rgs)-1):
x.append( ( a + [b] for a in x[-1] for b in rgs[i+1]) )
I obtain a 6-element list:
[[0, 2, 2, 2],
[0, 3, 2, 2],
[0, 4, 2, 2],
[1, 2, 2, 2],
[1, 3, 2, 2],
[1, 4, 2, 2]]
Also, what I notice is that all the ranges generated after the first two use the range in rgs[-1] instead of the correct ones. I struggle to understand why this happens, as I beleive that those two code example are identical, except the latter is the more general form for arbitrary large number of ranges.

You can use itertools.product to output a list of tuples
Input:
import itertools
a= [range(0, 2), range(2, 5), range(4, 6), range(2, 3)]
list(itertools.product(*a))
Output:
[(0, 2, 4, 2),
(0, 2, 5, 2),
(0, 3, 4, 2),
(0, 3, 5, 2),
(0, 4, 4, 2),
(0, 4, 5, 2),
(1, 2, 4, 2),
(1, 2, 5, 2),
(1, 3, 4, 2),
(1, 3, 5, 2),
(1, 4, 4, 2),
(1, 4, 5, 2)]
I did not get the same result when running your first code. I had to change it up a bit:
x = [ ( [a,b] for a in rgs[0] for b in rgs[1] ) ]
x.append( ( a + [b] for a in x[-1] for b in rgs[2]) )
x = list( a + [b] for a in x[-1] for b in rgs[3])
Most people that don't know about itertools would have done it this way:
x=[]
for i0 in rgs[0]:
for i1 in rgs[1]:
for i2 in rgs[2]:
for i3 in rgs[3]:
x.append([i0,i1,i2,i3])
Or using a list comprehension (DON'T DO THIS, it is VERY messy looking):
[[i0,i1,i2,i3] for i3 in rgs[3] for i2 in rgs[2] for i1 in rgs[1] for i0 in rgs[0]]

Your issue has to do with creating generator expressions in a loop. Generator expressions are implemented as functions, and like functions, they can have "free" variables that they look up in the containing namespaces. Your generator expressions are accessing the i from outside their definition, and because of this, they end up seeing a different i value you expect.
Here's an example that might be easier to understand:
def gen()
print(i)
yield 10
x = []
for i in range(3):
x.append(gen()) # add several generators while `i` has several different values
for g in x:
list(g) # consume the generators, so they can print `i`
Here, rather than using the i value for something useful, I've written a generator function that just prints it out. If you run this code, you'll see that all the generators print out 2, since that's the value of i when they finally run (after the first loop ended).
Your situation is a little more subtle, as you're consuming the previous generator as you create the next one, but the general idea is the same. The generator expression loop that you expect to be over rgs[2] is actually over rgs[3] because it's actually being looked up with rgs[i+1] and i increased between the time the generator expression was declared and when it was consumed.

Related

Python - Finding out all r combinations from a set of cardinality n [duplicate]

This question already has answers here:
How to get all combinations of length n in python
(3 answers)
Closed 1 year ago.
I am trying to generate all possible 3 combinations from the set {1,2,3,4,5} using recursion.
Expected output: [[1,2,3],[1,2,4],[1,2,5],[2,3,4],[2,3,5],[3,4,5],[1,3,4],[1,3,5],[1,4,5],[2,4,5]]
The logic that I am using is that any 3-set combination will either have the first element in it or not have. I am also using the concatenation of lists. Example:
[[1,2,3]] + [[a,b]] gives [[1,2,3],[a,b]]
The below code that uses the above logic doesn't seem to work. I am self-learning so, if I make a mistake, please be patient with me. I am aware there are errors in my recursion. But, trying to backtrack the output in recursion problems is proving quite difficult for me.
Could you please let me know where could be the flaw in this program and guide me towards suitable resources where such types of questions can be better handled. What is the right way of thinking in such questions? Thanks a lot for helping.
Code:
sol = [1,2,3,4,5]
b=3
y= []
def combinations(sol, b):
global y
if len(sol) == b or len(sol)==1 :
return [sol]
y.append([[sol[0]] + combinations(sol[1:], b-1)] + combinations(sol[1:],b))
return y
print(combinations(sol,b)
Use the machinery provided in itertools:
from itertools import combinations
list(combinations(v, 3))
OUTPUT
[(1, 2, 3), (1, 2, 4), (1, 2, 5), (1, 3, 4), (1, 3, 5), (1, 4, 5), (2, 3, 4), (2, 3, 5), (2, 4, 5), (3, 4, 5)]
You CAN do this, by having your function be a generator. At each step, you loop through the possible starting cells, then loop through the results returned by the next step in the recursion.
sol = [1,2,3,4,5]
b=3
def combinations(sol, b):
if b == 0:
yield []
else:
for i in range(len(sol)-b+1):
for j in combinations(sol[i+1:],b-1):
yield [sol[i]]+j
print(list(combinations(sol,b)))
Output:
[[1, 2, 3], [1, 2, 4], [1, 2, 5], [1, 3, 4], [1, 3, 5], [1, 4, 5], [2, 3, 4], [2, 3, 5], [2, 4, 5], [3, 4, 5]]

How to get all the combinations of a list of pairs with a restriction on indices

I have originally two lists of numbers, but for this case, let's say I have a list of tuples and I would like to get all the possible combinations such that the output has a member of the first pair in index 0, one of the second pair in index 1, etc.
For example, let the list be: [(1, 2), (3, 4), (5,6)]
And I want to get:
1, 3, 5
1, 3, 6
1, 4, 5
1, 4, 6
2, 3, 5
2, 3, 6
2, 4, 5
2, 4, 6
The list can be of any length. I tried using itertools.permutations over the two original lists like this:
[list(zip(permutation, list2)) for permutation in itertools.permutations(list1, len(list2))]
but it would return all the possible permutations which is not what I need.
This can be done with a vanilla breadth-first search/depth-first search:
a = [(1, 2), (3, 4), (5,6)]
def search(points):
points = list(points)
paths = []
if points:
first_points = points.pop(0)
else:
return [[]]
for point in first_points:
for path in bfs(points):
paths.append([point] + path)
return paths
print(search(a))
Returns:
[1, 3, 5], [1, 3, 6], [1, 4, 5], [1, 4, 6], [2, 3, 5], [2, 3, 6], [2, 4, 5], [2, 4, 6]]
Now, I wrote this hastily and it's not the most efficient code, but it'll do the job

Permutations of a list with constraints python

I am desperately trying to get all the permutations of a list while enforcing position assignment constraints.
I have a list [1,2,3,4,5,6] (6 is just an example, I would like to find something that could work with every lenght) and I want to find all the lists of lenght 3 (also an example) with the following constraints :
position 1 can be occupied by numbers 1 and 2
position 2 can be occupied by numbers 1,2 and 3
position 3 can be occupied by numbers 2,3 and 4
repetions of a same number are not allowed
That would give these lists : [1,2,3],[1,2,4],[1,3,2],[1,3,4],[2,1,3],[2,3,4],[2,1,4]
For those interested, what I am trying to implement is what is explained pages 5 and 6 of this paper
Filter the product() of those subsets:
from itertools import product
for combo in product([1, 2], [1, 2, 3], [2, 3, 4]):
if len(set(combo)) == 3:
print(combo)
or as a list comprehension:
[combo for combo in product([1, 2], [1, 2, 3], [2, 3, 4]) if len(set(combo)) == 3]
Output:
>>> from itertools import product
>>> [combo for combo in product([1, 2], [1, 2, 3], [2, 3, 4]) if len(set(combo)) == 3]
[(1, 2, 3), (1, 2, 4), (1, 3, 2), (1, 3, 4), (2, 1, 3), (2, 1, 4), (2, 3, 4)]

Decompress an array in Python

I need to decompress an array and I am not sure where to start.
Here is the input of the function
def main():
# Test case for Decompress function
B = [6, 2, 7, 1, 3, 5, 1, 9, 2, 0]
A = Decompress(B)
print(A)
I want this to come out
A = [2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 9, 0, 0]
If you can't see the pattern, B[1] is how many times B[2] shows up in A[], and then B[3] is how many times B[4] shows up in A[], and so on.
How do I write a function for this?
Compact version with zip() and itertools.chain.from_iterable:
from itertools import chain
list(chain.from_iterable([v] * c for c, v in zip(*([iter(B)]*2))))
Demo:
>>> B = [6, 2, 7, 1, 3, 5, 1, 9, 2, 0]
>>> from itertools import chain
>>> list(chain.from_iterable([v] * c for c, v in zip(*([iter(B)]*2))))
[2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 5, 5, 5, 9, 0, 0]
Breaking this down:
zip(*([iter(B)]*2))) pairs counts with values:
>>> zip(*([iter(B)]*2))
[(6, 2), (7, 1), (3, 5), (1, 9), (2, 0)]
It is a fairly standard Python trick to get pairs out of a input iterable.
([v] * c for c, v in zip(*([iter(B)]*2))) is a generator expression that takes the counts and values and produces lists with the value repeated count times:
>>> next([v] * c for c, v in zip(*([iter(B)]*2)))
[2, 2, 2, 2, 2, 2]
chain.from_iterable takes the various lists produced by the generator expression and lets you iterate over them as if they were one long list.
list() turns it all back to a list.
def unencodeRLE(i):
i = list(i) #Copies the list to a new list, so the original one is not changed.
r = []
while i:
count = i.pop(0)
n = i.pop(0)
r+= [n for _ in xrange(count)]
return r
One more one-liner:
def decompress(vl):
return sum([vl[i] * [vl[i+1]] for i in xrange(0, len(vl), 2)], [])
A list comprehension extracts and unpacks pairs (xrange(0, len(vl), 2) iterates through start indices of pairs, vl[i] is a number of repetitions, vl[i+1] is what to repeat).
sum() joins the results together ([] is the initial value the unpacked lists are sequentially added to).
A slightly faster solution (with Python 2.7.3):
A=list(chain.from_iterable( [ B[i]*[B[i+1]] for i in xrange(0,len(B),2) ] ) )
>>> timeit.Timer(
setup='B=[6,2,7,1,3,5,1,9,2,0];from itertools import chain',
stmt='A=list(chain.from_iterable( [ B[i]*[B[i+1]] for i in xrange(0,len(B),2) ] ) )').timeit(100000)
0.22841787338256836
Comparing with:
>>> timeit.Timer(
setup='B=[6,2,7,1,3,5,1,9,2,0];from itertools import chain',
stmt='A=list(chain.from_iterable([v] * c for c, v in zip(*([iter(B)]*2))))').timeit(100000)
0.31104111671447754

How would I succinctly transpose nested lists?

I am writing code to parse a tilemap map from a config file. The map is in the format:
1|2|3|4
1|2|3|4
2|3|4|5
where the numbers represent tiles.
I then make this into an integer array:
[[int(tile) for tile in row.split("|")] for row in "1|2|3|4\n1|2|3|4\n2|3|4|5".lstrip("\n").split("\n")]
This produces an array in the format [row][column], but I would prefer it to be [column][row] as in [x][y] so I wouldn't have to address it backwards (i.e. [y][x]).
But I can't think of any concise ways of attacking the problem.
I have considered reworking the format using xml syntax through Tiled, but it appears too difficult for a beginner.
Thanks in advance for any responses.
use mylist = zip(*mylist):
>>> original = [[1, 2, 3, 4], [1, 2, 3, 4], [2, 3, 4, 5]]
>>> transposed = zip(*original)
>>> transposed
[(1, 1, 2), (2, 2, 3), (3, 3, 4), (4, 4, 5)]
>>> original[2][3]
5
>>> transposed[3][2]
5
How it works: zip(*original) is equal to zip(original[0], original[1], original[2]). which in turn is equal to: zip([1, 2, 3, 4], [1, 2, 3, 4], [2, 3, 4, 5]).
def listTranspose( x ):
""" Interpret list of lists as a matrix and transpose """
tups = zip( *x )
return [ list(t) for t in tups ]

Categories