Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
197 views
in Technique[技术] by (71.8m points)

python - Equivalent Nested Loop Structure with Itertools

Python's succint syntax through its batteries allows verbose code line to be expressed in readable one liners. Consider the following examples

====================================================|
for a in range(3):                                  |
    for b in range(3):                              |
        for c in range(3):                          |
            print (a,b,c),                          |
-  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -|
for e in product(range(3), repeat=3):               |
    print e,                                        |
====================================================|
for a in range(3):                                  |
    for b in range(a , 3):                          |
        for c in range(b , 3):                      |
            print (a,b,c),                          |
-  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -|
for e in combinations_with_replacement(range(3), 3):|
    print e,                                        |
====================================================|
for a in range(3):                                  |
    for b in range(a + 1, 3):                       |
        for c in range(b + 1, 3):                   |
            print (a,b,c),                          |
-  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -|
for e in combinations(range(3), 3):                 |
    print e,                                        |
====================================================|
for a in range(3):                                  |
    for b in range(3):                              |
        for c in range(3):                          |
            if len(set([a,b,c])) == 3:              |
                print (a,b,c),                      |
-  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -|
for e in permutations(range(3)):                    |
    print e,                                        |
====================================================|

Of Late I ended up with a deep nested dependent Loop I was trying to express succinctly but failed

The structure of the loop would be as follows

for a in A():
    for b in B(a):
        for c in C(b):
            foo(a,b,c)

Can such structure be expressed in an equivalent itertools notation?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

There's no exact itertools solution, but a simple combination of itertools functions will suffice:

def chain_imap_accumulate(seq, f):
    def acc_f(x):
        for n in f(x[-1]):
            yield x + (n,)
    return chain.from_iterable(imap(acc_f, seq))

def accumulative_product(*generators):
    head, tail = generators[0], generators[1:]
    head = imap(tuple, head())
    return reduce(chain_imap_accumulate, tail, head)

A quick test. Definitions:

from itertools import chain, imap, izip
chain_ = chain.from_iterable

def A():
    yield 'A'
    yield 'B'

def B(x):
    yield int(x, 16)
    yield int(x, 16) + 1

def C(x):
    yield str(x) + 'Z'
    yield str(x) + 'Y'

And the result:

>>> list(accumulative_product(A, B, C))
[('A', 10, '10Z'), ('A', 10, '10Y'), 
 ('A', 11, '11Z'), ('A', 11, '11Y'), 
 ('B', 11, '11Z'), ('B', 11, '11Y'), 
 ('B', 12, '12Z'), ('B', 12, '12Y')]

Almost all the complexity comes from the accumulation of inputs, as a quick "derivation" of the above code shows. The final (c) values can be generated using just a couple of nested itertools constructs:

>>> list(chain_(imap(C, chain_(imap(B, (A()))))))
['10Z', '10Y', '11Z', '11Y', '11Z', '11Y', '12Z', '12Y']

This can be generalized with reduce. To work with reduce, chain_imap can't use the standard imap argument order. It has to be swapped:

def chain_imap(seq, f):
    return chain.from_iterable(imap(f, seq))

This gives the same results:

>>> list(reduce(chain_imap, [B, C], A()))
['10Z', '10Y', '11Z', '11Y', '11Z', '11Y', '12Z', '12Y']

The final task is accumulating the initial values, so that you have access to a, b, and c. This takes a bit of thought to get right, but the implementation is fairly simple -- we just have to convert f into a function that ignores all input values but the last, and appends new values to the full input:

def chain_imap_accumulate(seq, f):
    def acc_f(x):
        for n in f(x[-1]):
            yield x + (n,)
    return chain.from_iterable(imap(acc_f, seq))

This requires that the first inputs be wrapped in tuples, so we map A with tuple:

>>> list(reduce(chain_imap_accumulate, [B, C], imap(tuple, A())))
[('A', 10, '10Z'), ('A', 10, '10Y'), 
 ('A', 11, '11Z'), ('A', 11, '11Y'), 
 ('B', 11, '11Z'), ('B', 11, '11Y'), 
 ('B', 12, '12Z'), ('B', 12, '12Y')]

Rewrite the above for clarity, and the code at the top of this answer results.

By the way, chain_imap_accumulate can be rewritten a bit more tersely using a genex. This can be combined with a shorter version of accumulative_product for a very compact definition (if you're interested in that kind of thing). This also happens to eliminate the itertools dependency entirely:

def chain_map_accumulate(seq, f):
    return (x + (n,) for x in seq for n in f(x[-1]))

def accumulative_product2(*gens):
    return reduce(chain_map_accumulate, gens[1:], (tuple(x) for x in gens[0]()))

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...