2012-03-28 10 views
9

mam to:Python itertools.product kolejność powstawania

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) 

Tak daleko, tak dobre, itertools.product przesuwa element skrajny prawy na każdej iteracji. Ale teraz chcę, aby móc określić kolejność iteracji zgodnie z poniższym:

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) 

Jeśli shapes miał trzy wymiary, axes mogłyby być na przykład (0, 1, 2) lub (2, 0, 1) etc, więc nie jest to kwestia po prostu za pomocą reversed() . Więc napisałem jakiś kod, który robi to, ale wydaje się bardzo nieefektywne:

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

Wszelkie pomysły, w jaki sposób prawidłowo to zrobić?

+0

dla przykładów 'tpaxes' jest '[1, 0]' i' axes' jest '(1, 0) '. Możesz zmienić swoje przykładowe dane dla jasności, więc są różne :) – hochl

+0

Prawda, osie = tpaxes, ponieważ jest to jedyny możliwy sposób zmiany kolejności osi matrycy 2d. W przypadku matrycy 3d tak nie jest. Jeśli osiami były '(2, 0, 1)', wtedy tpaxes byłby na przykład '(1, 2, 0)'. –

+0

Wiem - chciałem tylko wskazać, że bardziej skomplikowany przykład byłby lepszy w tym przypadku; Bez obrazy. – hochl

Odpowiedz

5

Jest to w rzeczywistości podręcznik specjalizowany product. To powinno być szybsze od osi zostanie zmieniona tylko raz:

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

To działa w przypadku 2D, ale chciałbym móc określić, w jaki sposób odwraca się osie. Zaktualizowałem to pytanie, aby jaśniej to wyrazić. –

+0

@GiovanniFunchal Wtedy myślę, że twoje podejście jest w porządku: 'tuple (i [y] dla y w tpaxes)' –

1

Nie wiem, jak skuteczne jest to, ale powinieneś być w stanie zrobić coś takiego ...

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

Starałem się maksymalnie 4 wymiary i wydaje się działać. ;)

Zamieniam po prostu wymiary, a następnie je zamieniam.

1

Czy próbowałeś pomiaru czasu, aby zobaczyć, jak długo to trwa? To, co masz, nie powinno być dużo wolniejsze niż bez zmiany kolejności.

Możesz spróbować zmodyfikować to, co musisz użyć przypisywania połączeń w miejscu.

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

Również można uzyskać przyspieszenie przez co tpaxes lokalnej zmiennej funkcji, zamiast zmiennej globalnej (która ma wolniejsze czasy lookup)

przeciwnym razie moja propozycja jest jakoś napisać własną funkcję produktu ..

5

Jeśli możesz sobie na to pozwolić pamięć mądry: Niech itertools.product wykonać ciężką pracę i użyć zip aby przełączyć się wokół osi.

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) 

Mały 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) 

Powyższa wersja jest szybko, jeśli nie są to produkty zbyt maju. W przypadku zbiorów dużych poniższe są szybsze, ale ...wykorzystuje eval (choć w dość bezpieczny sposób):

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: Jeśli chcesz nie tylko zmienić kolejność iteracji, ale również kształt (jak na przykład OP), potrzebne są małe zmiany :

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) 

a wersja 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, szczególnie podoba mi się rozwiązanie 'eval'. To kreatywny kod, którego nie widać każdego dnia! To może być brzydkie, ale jest całkiem jasne, co robi i jeśli wydajność jest naprawdę krytyczna, myślę, że to jedyny sposób :) –

+0

Hm, gdzie jest (0, 0, 3), ostatnia oś powinna zostać przesunięta do środka , czyż nie? – bereal

+0

@bereal: Wartości osi 1 są najpierw zwiększane, następnie osi 2, a następnie osi 0. Które wartości pojawiają się na której osi nie ma wpływu parametr "osi"; określa tylko kolejność, w jakiej wartości są zwiększane. – 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)]) 

Wynik:

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