In Python, I have a list of ranges like that:
A = [range(0,2),range(0,4),range(0,3),range(0,3)]
First I have to convert all of these ranges into sets. I can create an empty set and add the resulting list to it. I would have:
B = [[0, 1], [0, 1, 2, 3], [0, 1, 2], [0, 1, 2]]
But after that, I have to create all the possible combinations of elements between the lists. The set with the lowest values would be [0, 0, 0, 0] and the highest values would be: [1, 3, 2, 2]. It would be a combination of 2x4x3x3 = 72 sets. How can I achieve this result, starting with the list of ranges (A)?
You can use the built-in itertools module to take the cartesian product of all the range objects in A, and skip making B altogether:
import itertools
A = [range(2), range(4), range(3), range(3)]
list(itertools.product(*A))
Output (skipping some items for readability):
[(0, 0, 0, 0),
(0, 0, 0, 1),
(0, 0, 0, 2),
(0, 0, 1, 0),
(0, 0, 1, 1),
.
.
.
(1, 3, 2, 2)]
Verifying the length:
>>> len(list(itertools.product(*A)))
72
Note that itertools.product() yields tuple objects. If for whatever reason you'd prefer these to be lists, you can use a comprehension:
[[*p] for p in itertools.product(*A)]
Another approach, as #don'ttalkjustcode points out, is that you can avoid creating A entirely and skip directly to the cartesian product via the map() function:
list(itertools.product(*map(range, (2, 4, 3, 3))))
However, this assumes that all your ranges start at 0.
You could generalize this mapping technique by using a lambda which will create range objects from a list of tuples:
>>> list(map(lambda t: range(*t), ((6, -3, -1), (0, 3), (5,), (10, 1, -2))))
[range(6, -3, -1), range(0, 3), range(0, 5), range(10, 1, -2)]
to get the Cartesian product do the following :
A = []
for i in range(2):
for j in range(4):
for k in range(3):
for n in range(3):
combo = [i,j,k,n]
A.append(combo)
I know how to get all possible combinations with replacement using itertools, but I want to limit the number of combinations with replacement by using limited number of elements from a larger set.
To give an example, I have a set
[0,1,2]
and I want to get k-combinations with replacement (k=4) but using maximum 2 different elements from a set [0,1,2]
so sets of elements that can appear in each combination are:
[0,1], [1,2], [0,2].
Here, I also want to avoid repetition of combinations, so in this example [0,0,0,0], [1,1,1,1] or [2,2,2,2] should not duplicate.
The output for this example:
[0,0,0,0]
[0,0,0,1]
[0,0,1,1]
[0,1,1,1]
[1,1,1,1]
[1,1,1,2]
[1,1,2,2]
[1,2,2,2]
[2,2,2,2]
[0,0,0,2]
[0,0,2,2]
[0,2,2,2]
I hope I am clear.
You can try the following.
import itertools
s = [0,1,2]
k = 4
limit = 2
result = set()
for c in itertools.combinations(s, limit):
result.update(itertools.combinations_with_replacement(c, k))
Or with a set comprehension:
result = {
r
for c in itertools.combinations(s, 2)
for r in itertools.combinations_with_replacement(c, k)
}
Both result in:
print(*result, sep="\n")
(0, 0, 0, 1)
(0, 1, 1, 1)
(0, 0, 0, 0)
(0, 2, 2, 2)
(2, 2, 2, 2)
(1, 2, 2, 2)
(1, 1, 1, 2)
(1, 1, 2, 2)
(0, 0, 0, 2)
(0, 0, 1, 1)
(1, 1, 1, 1)
(0, 0, 2, 2)
I create a list of all permutations of lets say 0,1,2
perm = list(itertools.permutations([0,1,2]))
This is used for accessing indexes in another list in that specific order. Every time a index is accessed it is popped.
When an element is popped, the elements with indexes higher than the popped elements index will shift one position down. This means that if I want to pop from my list by indexes [0,1,2] it will result in an index error, since index 2 will not exist when I reach it. [0,1,2] should therefor be popped in order [0,0,0].
more examples is
[0,2,1] = [0,1,0]
[2,0,1] = [2,0,0]
[1,2,0] = [1,1,0]
right now this is being handled through a series of checks, my question is if anyone knows a smart way to turn the list of lists generated by itertools into the desired list:
[(0, 1, 2), (0, 2, 1), (1, 0, 2), (1, 2, 0), (2, 0, 1), (2, 1, 0)]
[(0, 0, 0), (0, 1, 0), (1, 0, 0), (1, 1, 0), (2, 0, 0), (2, 1, 0)]
Simply iterate through each tuple, and decrement the indexes of each subsequent index that is greater than that element:
l=[(0, 1, 2), (0, 2, 1), (1, 0, 2), (1, 2, 0), (2, 0, 1), (2, 1, 0)]
def lower_idxs(lst):
new_row = list(lst)
for i, val in enumerate(new_row):
for j in xrange(i+1, len(new_row)):
if new_row[i] < new_row[j]:
new_row[j] -= 1
return new_row
print [lower_idxs(x) for x in l]
will print out
[[0, 0, 0], [0, 1, 0], [1, 0, 0], [1, 1, 0], [2, 0, 0], [2, 1, 0]]
Here is a fancier one-liner based on Randy C's solution:
print [tuple(y-sum(v<y for v in x[:i]) for i,y in enumerate(x)) for x in l]
Here's a one-liner for it (assuming your list is l):
[v-sum(v>v2 for v2 in l[:k]) for k, v in enumerate(l)]
I have a list B=[0,0,0,0,0,0,0,0,0] but it can be any length.
I am trying to iterate through all possible values I can place in B by iteration. When some condition C is met, I want to "reset" the element I just iterated and bump the next item up by 1. Sort of like binary:
000 becomes 001 but then when we increase to 002, condition C is met so we drop it to 0 and increment the next column: 002 becomes 010, etc.
Sorry if I explained that poorly.
So B might go from
B=[0,0,0,0,1,2,5]
to
B=[0,0,0,0,1,2,6]
to
B=[0,0,0,0,1,2,7]
and so forth.
But when condition C is met, I want to reset in this way:
B=[0,0,0,0,1,2,96]
...attempt to increment
B=[0,0,0,0,1,2,97]
...attempt to increment
Condition C met
B=[0,0,0,0,1,3,0]
And be able to do this until I eventually hit condition C on the far left element (equivalent to hitting 1111111 and being unable to increase it any more).
For the sake of easier coding let's say condition C = the sum of all the numbers exceeds 100.
My attempt (as requested by agf):
B=[0,0,0,0,0,0,0,0]
lenB=len(B)
while sum(B)<=100: #I think I have to somehow account for having tried incrementing the far left instead
B[lenB-1]+=1 #increment last value in B
while sum(B)>100: #if the sum is greater than 100
B[lenB-1]=0 #reset the far right element
B[lenB-2]+=1 #increment the next element
#but this is wrong because it needs to perform this check again and again
#for every column, while also checking if B[len-1] or B[len-2] even exists
EDIT: My Condition C in reality is MUCH more complex than simply checking if Sum(B)>100. I'm just using this as a dummy condition because I can simply replace "if sum(B)>100" with my more complex conditional function.
Edit: I appear to have created a solution to a different, more complex problem. Here is my solution to the problem as clarified by agf in the comments:
def uphill(initial=None):
"""Yields a tuple of integers. On each iteration, add one to the last column
. If True is sent then reset the column, and begin iterating the previous
column, until the first column is matched."""
b = initial
column = len(initial)-1
while True:
if (yield tuple(b)):
b[column] = 0
if column > 0:
column -= 1
b[column] += 1
else:
yield None
raise StopIteration
yield None
else:
b[column] += 1
gen = uphill([1, 2, 0])
for b in gen:
print(b)
if sum(b) >= 4:
gen.send(True)
Giving us:
(1, 2, 0)
(1, 2, 1)
(1, 3, 0)
(2, 0, 0)
(3, 0, 0)
(4, 0, 0)
Old solution:
We can create a very elegant solution with generators and the little-known generator.send():
def waterfall(columns):
"""Yields a tuple of integers. On each iteration, adds one to the last list
item. The consumer can send column numbers to the waterfall during iteration
- when this is done, the specified column is reset to 0 and the previous
column is incremented. When the first column is reset, the iterator ends."""
b = [0]*columns
while True:
reset = (yield tuple(b))
if not reset == None:
while not reset == None:
b[reset] = 0
if reset > 0:
b[reset-1] +=1
else:
yield None
raise StopIteration
reset = (yield None)
else:
b[-1] += 1
gen = waterfall(3)
for b in gen:
print(b)
if b[2] >= 3:
gen.send(2)
if b[1] >= 2:
gen.send(1)
if b[0] >= 1:
gen.send(0)
Which gives us:
(0, 0, 0)
(0, 0, 1)
(0, 0, 2)
(0, 0, 3)
(0, 1, 0)
(0, 1, 1)
(0, 1, 2)
(0, 1, 3)
(0, 2, 0)
(1, 0, 0)
You could happily change these conditions to anything. You simply send the generator the index of the column you wish to reset (which automatically increments the one above it by one) when your condition of choice is met. When the last column is reset, it finishes the generator.
It's also worth noting you can use gen.close() to stop it at any time, without needing to reach the final column. (gen.send(0) is the same as gen.close()).
An example with a different condition:
gen = waterfall(2)
for b in gen:
print(b)
if sum(b) >= 3:
gen.send(1)
if b[0] >= 3:
gen.send(0)
Giving us:
(0, 0)
(0, 1)
(0, 2)
(0, 3)
(1, 0)
(1, 1)
(1, 2)
(2, 0)
(2, 1)
(3, 0)
def increment(box, condition):
# last index in the list
maxindex = index = len(box) - 1
while True:
# so you can see it's correct
print box
# increment the last digit
box[-1] += 1
# while the overflow condition is True
while condition(box):
# reset the current digit
box[index] = 0
# and move to the next index left
index -= 1
# if we're past the end of the list
if index < 0:
# stop
return
# increment the current digit
box[index] += 1
# back to the rightmost digit
index = maxindex
increment([0] * 3, lambda box: sum(box) > 4)
Does this do what you want?
B=[0,1,0]
def check(ll,callback):
"""
This function only works if you
increment the last element in the list.
All other incrementing is done in this function.
"""
for idx in reversed(range(len(ll))):
if(callback(ll)):
ll[idx]=0
ll[idx-1]+=1
else:
break #this index wasn't updated, so the next one won't be either.
#a check to see if every element is 1
return all(map(lambda x: x==1,ll))
def checksum(ll):
return True if sum(ll)>100 else False
count=0
while True:
count+=1
B[-1]+=1
if(check(B,checksum)): break
print B
print B # [1,1,1]
print count
Even for this example, we run through over 5000 iterations before [1,1,1]
EDIT
Added a simple break statement as it is only necessary to check the list as long as it changed during the last iteration.
You can do this with a list comprehension and range() (using small values here to stop the output being very large):
>>> [(a, b, c) for a in range(2) for b in range(4) for c in range(3)]
[(0, 0, 0), (0, 0, 1), (0, 0, 2), (0, 1, 0), (0, 1, 1), (0, 1, 2), (0, 2, 0), (0, 2, 1), (0, 2, 2), (0, 3, 0), (0, 3, 1), (0, 3, 2), (1, 0, 0), (1, 0, 1), (1, 0, 2), (1, 1, 0), (1, 1, 1), (1, 1, 2), (1, 2, 0), (1, 2, 1), (1, 2, 2), (1, 3, 0), (1, 3, 1), (1, 3, 2)]
Note that if you want to loop over it, rather than generating a big list at the beginning, using a generator expression only creates items as needed:
for a, b, c in ((a, b, c) for a in range(2) for b in range(4) for c in range(3)):
...
And note that in Python 2.x, here you would want to use xrange() rather than range to get generators rather than lists.
This seems to address your incrementing issue as you're expecting:
b=[0,0,0,0,0,0,0,1,7]
def incr(arr, condition, pred):
arr[-1] += 1
element = -1
while condition(arr) > pred:
if arr[element]:
arr[element] = 0
arr[element-1] += 1
else:
element -= 1
if abs(element) == len(arr):
break
return arr
print b
for i in xrange(0,10):
b = incr(b, sum, 15)
print b
A function accepts the list and a functional condition (e.g., sum) and the point where the increment should carry over.
Thus it returns a result like this for the example sum (15):
>>>
[0, 0, 0, 0, 0, 0, 0, 1, 7]
[0, 0, 0, 0, 0, 0, 0, 1, 8]
[0, 0, 0, 0, 0, 0, 0, 1, 9]
[0, 0, 0, 0, 0, 0, 0, 1, 10]
[0, 0, 0, 0, 0, 0, 0, 1, 11]
[0, 0, 0, 0, 0, 0, 0, 1, 12]
[0, 0, 0, 0, 0, 0, 0, 1, 13]
[0, 0, 0, 0, 0, 0, 0, 1, 14]
[0, 0, 0, 0, 0, 0, 0, 2, 0]
[0, 0, 0, 0, 0, 0, 0, 2, 1]
[0, 0, 0, 0, 0, 0, 0, 2, 2]
Compact but inefficient solution
If your conditions are not digit specific, try using a solution similar to LattyWare, but with a filter to only provide the predicated results.
If you have a list of predicate functions mapping (a, b, c) to bool
for a, b, c in ((a, b, c) for a in range(2) for b in range(4) for c in range(3)):
if all [p(a,b,c) for p in predicates]:
yield a, b, c
Note that this becomes untenable if you can't put a reasonable inital bound on the individual digits (the search spaces becomes too large).