2012-03-28 17 views
9

tengo esto:Python itertools.product reordenar la generación

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) 

Hasta ahora, todo va bien, itertools.product avanzar el elemento más a la derecha en cada iteración. Pero ahora quiero ser capaz de especificar el orden de iteración de acuerdo a lo siguiente:

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) 

Si shapes tenían tres dimensiones, axes podría haber sido por ejemplo (0, 1, 2) o (2, 0, 1) etc, por lo que no es una cuestión de simple uso de reversed() . Así que escribí un código que lo hace pero parece muy ineficiente:

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

¿Alguna idea sobre cómo hacer esto correctamente?

+0

para sus ejemplos 'tpaxes' es' [1, 0] 'y' axes' es '(1, 0)'. Es posible que desee cambiar los datos de ejemplo para mayor claridad, para que sean diferentes :) – hochl

+0

Es cierto que axes = tpaxes porque esa es la única forma posible de reordenar los ejes de una matriz 2d. Para una matriz 3d, ese no es el caso. Si los ejes fueran '(2, 0, 1)', entonces tpaxes sería '(1, 2, 0)' por ejemplo. –

+0

Lo sé, solo quería señalar que un ejemplo más complicado sería mejor en este caso; Sin ofender. – hochl

Respuesta

5

Bueno, esto es de hecho un manual especializado product. Debería ser más rápido ya que los ejes se reordenan sólo una vez:

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

Eso funciona en el caso 2D, pero me gustaría poder especificar cómo se invierten los ejes. He actualizado la pregunta para reflejar eso más claramente. –

+0

@GiovanniFunchal Entonces creo que su enfoque es bueno: 'tupla (i [y] para y en tpaxes)' –

1

No sé qué tan eficiente es esto, pero usted debería ser capaz de hacer algo como esto ...

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

Probé es hasta 4 dimensiones, y parece que funciona. ;)

Estoy intercambiando las dimensiones y luego cambiándolas.

1

¿Has probado el tiempo para ver cuánto tiempo más lleva? Lo que tienes no debe ser mucho más lento que sin reordenar.

Puede intentar modificar lo que tiene que usar la asignación de empalme in situ.

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

También se puede conseguir una aceleración haciendo tpaxes una variable local de una función en lugar de una variable global (que tiene tiempos de búsqueda más lentas)

De lo contrario mi sugerencia es de alguna manera escribir su propia función del producto ..

5

Si se lo puede permitir la memoria en cuanto a: Que itertools.product hacer el trabajo duro, y utilizar zip para cambiar los ejes alrededor.

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) 

pequeña prueba:

>>> 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 versión anterior es rápido si no hay productos podrían ser demasiados. Para grandes conjuntos de resultados, el siguiente es más rápido, pero ...utiliza eval (aunque de una forma bastante segura):

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) 

Editar: Si desea no sólo cambiar el orden de iteración, sino también la forma (como en el ejemplo de la OP), se necesitan pequeños cambios :

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) 

Y la versión 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) 

prueba:

>>> 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, me gusta especialmente la solución 'eval'. ¡Esta es una pieza creativa de codificación que uno no ve todos los días! Puede ser feo, pero es bastante claro lo que hace y si el rendimiento es realmente crítico, creo que es la única forma :) –

+0

Hm, de dónde viene (0, 0, 3), el último eje debe moverse al centro ¿No debería? – bereal

+0

@bereal: Primero se aumentan los valores del eje 1, luego del eje 2, luego del eje 0. Qué valores aparecen en qué eje no se ve afectado por el parámetro 'axes'; solo determina el orden en que se aumentan los valores. – 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)]) 

resultado:

(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) 
Cuestiones relacionadas