Creating a conditional iterative list - python

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).

Related

Two nested for loops involving itertools don't produce the permutations of the outer loop

I have the following bit of code:
a = [0,1,2,3,4]
b = range(6)
p = itertools.permutations(a)
p2 = itertools.product(b,b,b,b,b)
for pos in p:
for shift in p2:
print(pos,shift)
which prints out:
(0, 1, 2, 3, 4) (0, 0, 0, 0, 0)
(0, 1, 2, 3, 4) (0, 0, 0, 0, 1)
(0, 1, 2, 3, 4) (0, 0, 0, 0, 2)
(0, 1, 2, 3, 4) (0, 0, 0, 0, 3)
(0, 1, 2, 3, 4) (0, 0, 0, 0, 4)
...
(0, 1, 2, 3, 4) (5, 5, 5, 5, 5)
The outer loop is executed only once. If I do
for pos in p:
print(pos)
the loop executes just fine:
(0, 1, 2, 3, 4)
(0, 1, 2, 4, 3)
...
(4, 3, 2, 1, 0)
Why is this happening, and how do I make the two nested for loops work? Note that I have tried looping over list(p) instead of just p and it doesn't make a difference.
The reason for why the code isn't operating as you expect it to is the same reason as why this wouldn't work:
p = iter(range(6))
p2 = iter(range(6))
for pos in p:
for shift in p2:
print(pos,shift)
itertools.product returns an iterator that can only be consumed once. The first time you loop through p2 you have consumed all of the values, and subsequent iterations of the outer loop essentially become a no-op.
You need to store the iterator output to a container that can be iterated over multiple times, or call itertools.product multiple times to reconstruct as necessary.
import itertools
a = [0,1,2,3,4]
b = range(6)
p = itertools.permutations(a)
p2 = list(itertools.product(b,b,b,b,b))
for pos in p:
for shift in p2:
print(pos,shift)
Or
import itertools
a = [0,1,2,3,4]
b = range(6)
p = itertools.permutations(a)
for pos in p:
p2 = itertools.product(b,b,b,b,b)
for shift in p2:
print(pos,shift)
Note: In reality, the permutations and product methods are actually constructor calls to generate operation specific classes, however, they do implement the iterator interface so I'm just calling them that!

Python N nested loops

So this is an example of what I want to do.
def multi_range(range_func):
for a in range_func():
for b in range_func():
yield a, b
However, what I actually want is something that works for N loops. I would think something like this.
def multi_range(range_func, N, sofar=None, results=None):
if sofar is None:
sofar = []
if results is None:
results = []
for a in range_func():
if N == 1:
results.append(sofar + [a])
else:
multi_range(range_func, N - 1, sofar + [a], results)
return results
def test_range():
yield 0
yield 1
for b in multi_range(test_range, 3):
print(b)
This correctly outputs the following.
[0, 0, 0]
[0, 0, 1]
[0, 1, 0]
[0, 1, 1]
[1, 0, 0]
[1, 0, 1]
[1, 1, 0]
[1, 1, 1]
The issue here is it has to create the entire list and store that in memory.
If for example, test_range were this instead, it would consume a large amount of memory.
def test_range():
for x in range(10000):
yield x
(Yes I know it's weird to have a wrapper for range that behaves identically to range. It is just an example)
That is my question. How can I write a function that behaves like this one without storing all results in a list?
Use itertools.product:
>>> from itertools import product
>>> def test_range():
... yield 0
... yield 1
...
>>> for b in product(test_range(), repeat=3):
... print(b)
...
(0, 0, 0)
(0, 0, 1)
(0, 1, 0)
(0, 1, 1)
(1, 0, 0)
(1, 0, 1)
(1, 1, 0)
(1, 1, 1)
If you're curious how this is implemented, see the sample implementation in the linked doc.

Generate k-combinations with replacement of n-size set using maximum s-number of elements of n-size set (where s<n)

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)

Find all subsequences in list

I have a list of 1s and 0s as follows:
lst = [1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1]
I'm looking for a method that finds all the sequences of 0s within this list and returns their indices, i.e.:
[1, 3]
[8, 9]
[13, 13]
[15, 16]
This answer shows a method of getting the longest sequence, but I can't think of a way to work from it to get all the sequences.
def f(l):
_1to0 = [ i+1 for i, (x, y) in enumerate(zip(l[:-1], l[1:])) if y == 0 and x != y ]
_0to1 = [ i for i, (x, y) in enumerate(zip(l[:-1], l[1:])) if x == 0 and x != y ]
if l[0] == 0:
_1to0.insert(0,0)
if l[-1] == 0:
_0to1.append(len(l))
return zip(_1to0, _0to1)
Detect changes 1 -> 0 (starts) and 0 -> 1 (ends)
If start with 0, add a start at indice 0
If ends with 0, add an end at the last indice
Combine starts and ends in pairs
In [1]: list(f([1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1]))
Out[1]: [(1, 3), (8, 9), (13, 13), (15, 16)]
For Python 3.8 you can modify the first answer in referenced code by using the Walrus operator
Code
from itertools import groupby
import operator
lst = [1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1]
r = [(p[1][0][0], p[1][-1][0]) for (x,y) in groupby(enumerate(lst), operator.itemgetter(1)) if (p := (x, list(y)))[0] == 0]
print(r)
Output
[(1, 3), (8, 9), (13, 13), (15, 16)]
Explanation
Adding a Walrus operator to OP code reference we have:
r = [p for (x,y) in groupby(enumerate(lst), operator.itemgetter(1)) if (p := (x, list(y)))[0] == 0]
# Outputs: [(0, [(1, 0), (2, 0), (3, 0)]), (0, [(8, 0), (9, 0)]), (0, [(13, 0)]), (0, [(15, 0), (16, 0)])]
Conditional in the list comprehension:
(p := (x, list(y)))[0] # is a check for x == 0
Need to capture the right terms in p
First p[1] for instance is:
[(1, 0), (2, 0), (3, 0)]
We want the (1, 3) which index 0 of the first and last term of the list
p[1][0][0] # index zero of first tuple -> 1
p[1][-1][0] # index zero of last tuple -> 3
So in general we have the tuple:
(p[1][0][0], p[1][-1][0])
list = [1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 1, 0, 1, 0, 0, 1]
indexes_counts = []
start_zero_index = -1
is_inside_zero_sequence = False
zero_length = 0
# Use enumerate to loop on the lists and indexes
for i, x in enumerate(list):
# If inside a zeroes sequence
if is_inside_zero_sequence:
# If current item is zero too
if 0 == x:
# Increase the zro_length counter
zero_length += 1
# Else, current element is not zero
else:
# Handle end of zeroes sequence
indexes_counts.append([start_zero_index, zero_length])
is_inside_zero_sequence = False
zero_length = 0
# If not in zeroes sequence and current number is not zero
elif 0 == x:
# Handle not zero
is_inside_zero_sequence = True
start_zero_index = i
zero_length = 1
# [[1, 3], [8, 2], [13, 1], [15, 2]]
print(indexes_counts)

Permutations and indexes, python

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)]

Categories

Resources