Astrid
Astrid

Reputation: 1312

Python for loop offset (Itertools.product)

The following code generates all possible combinations using 0 and 1, where four digits have to be used.

import itertools
for i in itertools.product([0, 1], repeat=4):
    print i

Output:

(0, 0, 0, 0)(0, 0, 0, 1)(0, 0, 1, 0)(0, 0, 1, 1)(0, 1, 0, 0)(0, 1, 0, 1)(0, 1, 1, 0)(0, 1, 1, 1)(1, 0, 0, 0)(1, 0, 0, 1)(1, 0, 1, 0)(1, 0, 1, 1)(1,1, 0, 0)(1, 1, 0, 1)(1, 1, 1, 0)(1, 1, 1, 1)

I would like to be able to set an offset for the for loop. Example:

import itertools
offSet = 10
for i in itertools.product([0, 1], repeat=4):
    # Some code that applies the offset
    print i

Which would then output:

(1, 0, 1, 0)(1, 0, 1, 1)(1,1, 0, 0)(1, 1, 0, 1)(1, 1, 1, 0)(1, 1, 1, 1)

How can I apply a such offset to this for loop?

Note: The code being used is simplified. As I am actually using a very large value for repeat, performance matters. I cannot afford the possible combinations before the offset to be calculated.

Upvotes: 0

Views: 1382

Answers (3)

Aaron Hall
Aaron Hall

Reputation: 395443

Here's a definition that takes a binary (in Python, represented by a string as in '0b1010101') (or an easy human readable and writable version that leaves out the 0b part) and returns the range in the form of an iterator of tuples of strings (which is created lazily). You can materialize the range with a list().

def binrange(start, stop=None):
    '''
    given bin strings return iterator of zeros and ones
    e.g. 
    >>> list(binrange(bin(8))) # bin(8) returns '0b1000'
    [(0, 0, 0, 0), (0, 0, 0, 1), (0, 0, 1, 0), (0, 0, 1, 1), (0, 1, 0, 0), (0, 1, 0, 1), (0, 1, 1, 0), (0, 1, 1, 1)]
    >>> list(binrange('10', bin(4))) # bin(4) returns '0b100'
    [(0, 1, 0), (0, 1, 1)]
    '''
    if stop is None:
        start, stop = '0', start
    start = start[2:] if start.startswith('0b') else start
    stop = stop[2:] if stop.startswith('0b') else stop
    length = len(stop)
    for i in xrange(long(start, 2), long(stop, 2)): # in Python3, use range(), not xrange()
        yield tuple(int(j) for j in ('{0:b}'.format(i).zfill(length)))
        # above line replaces the following commented line
        # yield tuple(int(j) for j in ('{0:{fill}{align}{width}b}'.format(i, fill=0, align='>', width=length)))

and

print(list(binrange(bin(1000**3), bin(1000**3+3))))
print(list(binrange('1000')))
print(list(binrange('0b100')))

prints out:

[(1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0), (1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1), (1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0)]
[(0, 0, 0, 0), (0, 0, 0, 1), (0, 0, 1, 0), (0, 0, 1, 1), (0, 1, 0, 0), (0, 1, 0, 1), (0, 1, 1, 0), (0, 1, 1, 1)]
[(0, 0, 0), (0, 0, 1), (0, 1, 0), (0, 1, 1)]

Upvotes: 1

Eric
Eric

Reputation: 97631

You could use islice:

from itertools import product, islice

i = islice(product([0, 1], repeat=4), 10, None) 
print list(i)

This still fails with respect to:

I cannot afford the possible combinations before the offset to be calculated.

This isn't really what iterators are for. At any rate, you're actually just trying to count in binary, and the other answers here will work

Upvotes: 0

waitingkuo
waitingkuo

Reputation: 93924

How about this:

In [29]: offSet = 10

In [30]: repeat = 4

In [31]: for i in xrange(offSet, 2**repeat):
    print tuple(int(x) for x in bin(i)[2:])
   ....:     
(1, 0, 1, 0)
(1, 0, 1, 1)
(1, 1, 0, 0)
(1, 1, 0, 1)
(1, 1, 1, 0)
(1, 1, 1, 1)

Upvotes: 1

Related Questions