2013-02-23 20 views
11

La sintassi di succinta di Python tramite le batterie consente di esprimere una riga di codice dettagliata in una sola lettura. Considerare i seguenti esempiStruttura ad anello nidificato equivalente con Itertools

====================================================| 
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,          | 
====================================================| 

negli ultimi tempi ho finito con un loop dipendente profonda nidificati che cercavo di esprimere in modo succinto ma non è riuscito

La struttura del ciclo sarebbe la seguente

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

Questa struttura può essere espressa in una notazione iternools equivalente?

+0

Solo una nota. Quest'ultimo anello in Prolog sarebbe espresso come: 'a (A), b (A, B), c (B, C)'. Se ti piace giocare con i loop, gioca con Prolog. – liori

+0

usi 'a',' b' (valori intermedi) nel corpo del ciclo più interno? – jfs

+0

@liori: Prolog mi ha sempre allettato ma non è mai stato abbastanza intelligente da apprendere l'intelligenza artificiale – Abhijit

risposta

4

non c'è, ma si può fare uno:

def chainGang(steps, currentVars=None): 
    thisOne = steps[0] 
    if currentVars is None: 
     for item in thisOne(): 
      for gang in chainGang(steps[1:], [item]): 
       yield gang 
    elif len(steps) == 1:  
     for item in thisOne(currentVars[-1]): 
      yield currentVars + [item] 
    else: 
     for item in thisOne(currentVars[-1]): 
      for gang in chainGang(steps[1:], currentVars + [item]): 
       yield gang 

E poi:

>>> outer = lambda: ["A", "B", "C", "D"] 
>>> middle = lambda letter: [letter, letter*2, letter*3] 
>>> inner = lambda s: range(len(s)+1) 
>>> for a in chainGang([outer, middle, inner]): 
...  print a 
[u'A', u'A', 0] 
[u'A', u'A', 1] 
[u'A', u'AA', 0] 
[u'A', u'AA', 1] 
[u'A', u'AA', 2] 
[u'A', u'AAA', 0] 
[u'A', u'AAA', 1] 
[u'A', u'AAA', 2] 
[u'A', u'AAA', 3] 
[u'B', u'B', 0] 
[u'B', u'B', 1] 
[u'B', u'BB', 0] 
[u'B', u'BB', 1] 
[u'B', u'BB', 2] 
[u'B', u'BBB', 0] 
[u'B', u'BBB', 1] 
[u'B', u'BBB', 2] 
[u'B', u'BBB', 3] 
[u'C', u'C', 0] 
[u'C', u'C', 1] 
[u'C', u'CC', 0] 
[u'C', u'CC', 1] 
[u'C', u'CC', 2] 
[u'C', u'CCC', 0] 
[u'C', u'CCC', 1] 
[u'C', u'CCC', 2] 
[u'C', u'CCC', 3] 
[u'D', u'D', 0] 
[u'D', u'D', 1] 
[u'D', u'DD', 0] 
[u'D', u'DD', 1] 
[u'D', u'DD', 2] 
[u'D', u'DDD', 0] 
[u'D', u'DDD', 1] 
[u'D', u'DDD', 2] 
[u'D', u'DDD', 3] 
5

Non c'è esatto soluzione itertools, ma una semplice combinazione di itertools funzioni sarà sufficiente:

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) 

Un test rapido. Definizioni:

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' 

E il risultato:

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

Quasi tutta la complessità deriva dall'accumulo di input, come "derivazione" veloce degli spettacoli codice in alto. Il finale (c) valori possono essere generati utilizzando solo un paio di nidificate itertools costrutti:

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

Questo può essere generalizzato con reduce. Per lavorare con reduce, chain_imap non è possibile utilizzare l'ordine degli argomenti standard imap. Deve essere scambiati:

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

Questo dà gli stessi risultati:

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

Il compito finale sta accumulando i valori iniziali, in modo da avere accesso a a, b e c. Questo prende un po 'di pensiero per ottenere il diritto, ma l'implementazione è abbastanza semplice - non ci resta che trasformare f in una funzione che ignora tutti i valori di input, ma l'ultimo, e aggiunge nuovi valori per la piena entrata:

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

Ciò richiede che i primi ingressi essere avvolti in tuple, quindi abbiamo cartina A con 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')] 

riscrivere il sopra per chiarezza, e il codice nella parte superiore di questa risposta risultati.

A proposito, chain_imap_accumulate può essere riscritto un po 'più tersamente utilizzando un genex.Questo può essere combinato con una versione più breve di accumulative_product per una definizione molto compatta (se sei interessato a questo genere di cose). Questo accade anche per eliminare completamente la dipendenza da itertools:

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]())) 
+1

Mi fa male il cervello, ma c'è un modo per usare "itertools.accumulate" di 3.3 con il suo parametro di funzione per semplificare/complessificare questo? – DSM

+0

Sento che hai ragione che ci deve essere un modo per farlo funzionare. Potresti essere in grado di usare 'accumula' invece di' reduce', e accumulare una sequenza di valori 'a',' b' e 'c'; ma il numero di valori di 'a' differirebbe dal numero di valori di' b', che differirebbero dal numero di valori di 'c'. Dovresti quindi capire come ridistribuire i valori in una sequenza piatta di tuple 'a, b, c'. La sfida sarebbe quella di farlo elegantemente ... – senderle

+0

Da accumulare otterresti qualcosa come -> '('A', 'B'), ((10, 11), (11, 12)), ((' 10Z ',' 10Y '), (' 11Z ',' 11Y '), (' 11Z ',' 11Y '), (' 12Z ',' 12Y ')) 'Non sono sicuro che questo sarà più pulito. – Moberg

Problemi correlati