2012-03-28 1 views
9

ho questo:Python itertools.product riordinare la generazione

shape = (2, 4) # arbitrary, could be 3 dimensions such as (3, 5, 7), etc... 

for i in itertools.product(*(range(x) for x in shape)): 
    print(i) 

# output: (0, 0) (0, 1) (0, 2) (0, 3) (1, 0) (1, 1) (1, 2) (1, 3) 

Fin qui, tutto bene, itertools.product avanza l'elemento più a destra su ogni iterazione. Ma ora voglio essere in grado di specificare l'ordine di iterazione secondo il seguente:

axes = (0, 1) # normal order 
# output: (0, 0) (0, 1) (0, 2) (0, 3) (1, 0) (1, 1) (1, 2) (1, 3) 

axes = (1, 0) # reversed order 
# output: (0, 0) (1, 0) (2, 0) (3, 0) (0, 1) (1, 1) (2, 1) (3, 1) 

Se shapes aveva tre dimensioni, axes avrebbe potuto essere per esempio (0, 1, 2) o (2, 0, 1) ecc, quindi non è una questione di semplicemente utilizzando reversed() . Così ho scritto un codice che lo fa, ma sembra molto inefficiente:

axes = (1, 0) 

# transposed axes 
tpaxes = [0]*len(axes) 
for i in range(len(axes)): 
    tpaxes[axes[i]] = i 

for i in itertools.product(*(range(x) for x in shape)): 
    # reorder the output of itertools.product 
    x = (i[y] for y in tpaxes) 
    print(tuple(x)) 

Tutte le idee su come fare correttamente questo?

+0

per i vostri esempi '' tpaxes' è [1, 0] 'e' axes' è '(1, 0)'. Potresti voler cambiare i dati di esempio per maggiore chiarezza in modo che siano diversi :) – hochl

+0

Vero, assi = tpax perché questo è l'unico modo possibile per riordinare gli assi di una matrice 2d. Per una matrice 3d, non è così. Se gli assi fossero '(2, 0, 1)' allora tpaxes sarebbe '(1, 2, 0)' per esempio. –

+0

Lo so - volevo solo sottolineare che un esempio più complicato sarebbe meglio in questo caso; Senza offesa. – hochl

risposta

5

Bene, questo è in effetti un manuale specializzato product. Dovrebbe essere più veloce in quanto gli assi vengono riordinate solo una volta:

def gen_chain(dest, size, idx, parent): 
    # iterate over the axis once 
    # then trigger the previous dimension to update 
    # until everything is exhausted 
    while True: 
     if parent: next(parent) # StopIterator is propagated upwards 

     for i in xrange(size): 
      dest[idx] = i 
      yield 

     if not parent: break 

def prod(shape, axes): 
    buf = [0] * len(shape) 
    gen = None 

    # EDIT: fixed the axes order to be compliant with the example in OP 
    for s, a in zip(shape, axes): 
     # iterate over the axis and put to transposed 
     gen = gen_chain(buf, s, a, gen) 

    for _ in gen: 
     yield tuple(buf) 


print list(prod((2,4), (0,1))) 
# [(0, 0), (0, 1), (0, 2), (0, 3), (1, 0), (1, 1), (1, 2), (1, 3)] 
print list(prod((2,4), (1,0))) 
# [(0, 0), (1, 0), (2, 0), (3, 0), (0, 1), (1, 1), (2, 1), (3, 1)] 
print list(prod((4,3,2),(1,2,0))) 
# [(0, 0, 0), (1, 0, 0), (0, 0, 1), (1, 0, 1), (0, 0, 2), (1, 0, 2), ... 
0
for i in itertools.product(*(range(x) for x in reversed(shape))): 
    print tuple(reversed(i)) 
+0

Funziona nel caso 2D, ma mi piacerebbe essere in grado di specificare il modo in cui gli assi sono invertiti. Ho aggiornato la domanda per riflettere in modo più chiaro. –

+0

@GiovanniFunchal Quindi penso che il tuo approccio sia corretto: 'tupla (io [y] per y in tpaxes' –

1

Non so quanto sia efficiente questo è, ma si dovrebbe essere in grado di fare qualcosa di simile ...

shape = (2, 4, 3) 
axes = (2, 0, 1) 

# Needed to get the original ordering back 
axes_undo = tuple(reversed(axes)) 

# Reorder the shape in a configuration so that .product will give you 
# the order you want. 
reordered = tuple(reversed(map(lambda x: shape[x], list(axes)))) 

# When printing out the results from .product, put the results back 
# into the original order. 
for i in itertools.product(*(range(x) for x in reordered)): 
    print(tuple(map(lambda x: i[x], list(axes_undo)))) 

ho provato è fino a 4 dimensioni e sembra funzionare. ;)

Sto solo scambiando le dimensioni intorno e poi invertendole.

1

Hai provato a cronometrare per vedere quanto tempo ci vuole? Quello che hai non dovrebbe essere molto più lento che senza riordino.

Si potrebbe provare a modificare ciò che è necessario utilizzare l'assegnazione della giunzione sul posto.

tpaxes = tuple(tpaxes) 
for i in itertools.product(*(range(x) for x in shape)): 
    # reorder the output of itertools.product 
    i[:] = (i[y] for y in tpaxes) 
    print(tuple(x)) 

Inoltre si potrebbe ottenere un aumento di velocità, rendendo tpaxes una variabile locale di una funzione piuttosto che una variabile globale (che ha tempi di ricerca più lenti)

In caso contrario il mio suggerimento è in qualche modo scrivere la propria funzione del prodotto ..

5

Se lo può permettere memoria-saggio: Lasciate itertools.product fare il duro lavoro, e utilizzare zip per commutare gli assi intorno.

import itertools 
def product(shape, axes): 
    prod_trans = tuple(zip(*itertools.product(*(range(shape[axis]) for axis in axes)))) 

    prod_trans_ordered = [None] * len(axes) 
    for i, axis in enumerate(axes): 
     prod_trans_ordered[axis] = prod_trans[i] 
    return zip(*prod_trans_ordered) 

piccolo test:

>>> print(*product((2, 2, 4), (1, 2, 0))) 
(0, 0, 0) (1, 0, 0) (0, 0, 1) (1, 0, 1) (0, 0, 2) (1, 0, 2) (0, 0, 3) (1, 0, 3) (0, 1, 0) (1, 1, 0) (0, 1, 1) (1, 1, 1) (0, 1, 2) (1, 1, 2) (0, 1, 3) (1, 1, 3) 

La versione di cui sopra è veloce, se non ci sono prodotti troppo maggio. Per i set di risultati grandi, quanto segue è più veloce, ma ...usa eval (anche se in modo piuttosto sicuro):

def product(shape, axes): 
    d = dict(("r%i" % axis, range(shape[axis])) for axis in axes) 
    text_tuple = "".join("x%i, " % i for i in range(len(axes))) 
    text_for = " ".join("for x%i in r%i" % (axis, axis) for axis in axes) 
    return eval("((%s) %s)" % (text_tuple, text_for), d) 

Edit: Se si desidera modificare non solo l'ordine di iterazione, ma anche la forma (come nell'esempio del PO), sono necessari piccoli cambiamenti :

import itertools 
def product(shape, axes): 
    prod_trans = tuple(zip(*itertools.product(*(range(s) for s in shape)))) 

    prod_trans_ordered = [None] * len(axes) 
    for i, axis in enumerate(axes): 
     prod_trans_ordered[axis] = prod_trans[i] 
    return zip(*prod_trans_ordered) 

E la versione eval:

def product(shape, axes): 
    d = dict(("r%i" % axis, range(s)) for axis, s in zip(axes, shape)) 
    text_tuple = "".join("x%i, " % i for i in range(len(axes))) 
    text_for = " ".join("for x%i in r%i" % (axis, axis) for axis in axes) 
    return eval("((%s) %s)" % (text_tuple, text_for), d) 

test:

>>> print(*product((2, 2, 4), (1, 2, 0))) 
(0, 0, 0) (1, 0, 0) (2, 0, 0) (3, 0, 0) (0, 0, 1) (1, 0, 1) (2, 0, 1) (3, 0, 1) (0, 1, 0) (1, 1, 0) (2, 1, 0) (3, 1, 0) (0, 1, 1) (1, 1, 1) (2, 1, 1) (3, 1, 1) 
+0

+1, mi piace particolarmente la soluzione 'eval'. Questo è un pezzo di codice creativo che non si vede tutti i giorni! Potrebbe essere brutto, ma è piuttosto chiaro cosa fa e se le prestazioni sono davvero critiche, penso che sia l'unico modo :) –

+0

Hm, da dove viene (0, 0, 3), l'ultimo asse dovrebbe essere spostato in mezzo , non dovrebbe? – bereal

+0

@bereal: i valori dell'asse 1 vengono prima aumentati, quindi dell'asse 2, quindi dell'asse 0. Quali valori appaiono su quale asse non è influenzato dal parametro 'axes'; determina solo l'ordine in cui vengono aumentati i valori. – WolframH

0
import itertools 

normal = (0, 1) 
reverse = (1, 0) 

def axes_ordering(x): 
    a, b = x 
    return b - a 

shape = (2, 4) 

for each in itertools.product(*(range(x) for x in shape)): 
    print(each[::axes_ordering(normal)], each[::axes_ordering(reverse)]) 

risultato:

(0, 0) (0, 0) 
(0, 1) (1, 0) 
(0, 2) (2, 0) 
(0, 3) (3, 0) 
(1, 0) (0, 1) 
(1, 1) (1, 1) 
(1, 2) (2, 1) 
(1, 3) (3, 1)