python redo

python - Equivalente pitónico de unshift o rehacer?



redo (9)

Por cierto, lo que realmente quieres es list.insert (0, yourObject)

Estoy aprendiendo Python, y tengo una situación en la que deseo consumir elementos de un iterador. La parte difícil es que bajo ciertas condiciones, quiero "iterar". Es decir, coloque un elemento de nuevo en la parte frontal del iterador antes de hacer un ciclo.

Por ejemplo, supongamos que estoy recogiendo manzanas de un árbol. Mi cesta de fruta solo puede contener 10 kg antes de que deba vaciarse. Pero tengo que recoger cada manzana antes de poder pesarla y determinar si esta manzana excederá la capacidad de la canasta.

En un lenguaje como Perl, podría unshift() la manzana de nuevo en el árbol, y luego dejar que la expresión de bucle vuelva a seleccionar la manzana:

while ($apple = shift(@tree)) { $wt = weight($apple); if ($wt + weight(@basket) > 10) { send(@basket); @basket = (); unshift(@tree, $apple); } else { push(@basket, $element); } }

O también puedo usar redo , que reanuda el procesamiento en la parte superior del bloque, sin evaluar la expresión del ciclo. Entonces, la misma manzana puede volver a procesarse, una vez que la canasta se haya vaciado.

while ($apple = shift(@tree)) { $wt = weight($apple); if ($wt + weight(@basket) > 10) { send(@basket); @basket = (); redo; } else { push(@basket, $apple); } }

¿Cuál sería la solución más pitonica para este tipo de problema?


¿Por qué molestarse con el cambio cuando la cláusula else siempre debería ocurrir?

for apple in tree: if (apple.weight + basket.weight) > 10: send(basket) basket.clear() basket.add(apple)

De todos modos, estoy bastante seguro de que Python no tiene el tipo de comportamiento que estás buscando.


Mientras escribía esto, @Patrick ya sugirió lo mismo. Pero como lo he escrito, pegaré el código de todos modos, con comentarios en los métodos de marcación de código de Patrick.

import random apples=[random.randint(1,3) for j in range(10)] print ''apples'',apples basket=[] y=6 baskets=[] for i in range(len(apples)): if sum(basket+[apples[i]])>y: #basket is full baskets.append(basket)#basket.send() basket=[]#basket.empty() basket.append(apples[i])#add apple to basket print ''baskets'',baskets

aunque esto no hace explotar () las manzanas del iterador original. Por favor, comenta si ese es un comportamiento deseado también.

La salida

apples [1, 1, 3, 3, 1, 1, 3, 3, 2, 3] baskets [[1, 1, 3], [3, 1, 1], [3, 3]]


No hay forma general de insertar un valor en un iterador en python. Una pila o lista vinculada se adapta mejor a eso.

Si está iterando sobre una lista o algo así, por supuesto puede agregar el elemento manualmente a la lista. Pero también puede iterar sobre objetos que no pueden ser manipulados de esa manera.

Si desea utilizar Python para implementar ese algoritmo, deberá elegir una estructura de datos que permita las operaciones que desea usar. Sugiero los .push() y .pop() que te permiten tratar las listas como pilas.


Estoy aprendiendo Python, y tengo una situación en la que deseo consumir elementos de un iterador. La parte difícil es que bajo ciertas condiciones, quiero "iterar". Es decir, coloque un elemento de nuevo en la parte frontal del iterador antes de hacer un ciclo.

Aquí hay una solución simple:

class MyIterator(object): # undo-able iterator wrapper def __init__(self, iterable): super(MyIterator, self).__init__() self.iterator = iter(iterable) self.stack = [] def __iter__(self): return self def next(self): if self.stack: return self.stack.pop() return self.iterator.next() # Raises StopIteration eventually def undo(self, item): self.stack.append(item)

for i in MyIterator(xrange(5)): print i 0 1 2 3 4

rng = MyIterator(xrange(5)) rng.next() 0 rng.next() 1 rng.undo(1) rng.next() 1


Si no quieres seguir la sugerencia del otro de simplemente eliminar la cláusula else, puedes escribir tu propia función de unshift que funcionará de forma similar a la de perl con cualquier iterable:

class UnshiftableIterable(object): def __init__(self, iterable): self._iter = iter(iterable) self._unshifted = [] # empty list of unshifted stuff def __iter__(self): while True: if self._unshifted: yield self._unshifted.pop() else: yield self._iter.next() def unshift(self, item): self._unshifted.append(item)

Luego en tu código:

it = UnshiftableIterable(tree) for apple in tree: if weigth(basket) + weight(apple) > MAX_WEIGHT: send(basket) basket = [] it.unshift(apple) else: basket.append(apple)

Algunas pruebas de UnshiftableIterable :

it = UnshiftableIterable(xrange(5)) for i in it: print ''*'', if i == 2: it.unshift(10) else: print i, # output: * 0 * 1 * * 10 * 3 * 4


Volviendo a la pregunta original sobre el desmovilización imperativa, operator.delitem se puede usar para implementar una función simple que no sea OO:

from operator import delitem def unshift(l,idx): retval = l[0] delitem(l,0) return retval x = [2,4,6,8] firstval = unshift(x,0) print firstval,x

2 [4, 6, 8]



Diría que la solución más Pythonic es la más simple . En lugar de intentar envolver un iterador en una expresión generadora que le permite "retroceder" o algo similarmente complejo, use un ciclo while, como lo hizo en Perl. Los iteradores no se mezclan muy bien con la mutación , cualquiera que sea.

Traducción simple de su implementación (ignorando la optimización de @Patrick ):

while tree: apple = tree.pop(0) if apple.weight + basket.weight > 10: basket.send() basket.clear() tree.insert(0, apple) # Put it back. else: basket.append(apple)

O bien, podría usar una funcionalidad tipo peek con índices de secuencia ordenados:

while tree: apple = tree[0] # Take a peek at it. if apple.weight + basket.weight > 10: basket.send() basket.clear() else: basket.append(tree.pop(0))

Si no te gusta el argumento "simple", echa un vistazo a los iterators de collections.deque mencionados en el hilo (vinculado) anterior.