2009-01-07 8 views
12

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.equivalente pitónico de unshift o rehacer?

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, que podría unshift() la manzana de nuevo en el árbol, y luego dejar que la expresión bucle de re-elegir 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 si no también se puede utilizar redo, que reanuda el procesamiento en la parte superior del bloque, sin evaluar la expresión de bucle. 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 Pythonic para este tipo de problema?

+0

si $ en peso> 10 entonces hay un bucle infinito (el primer ejemplo come toda la memoria, el segundo simplemente nunca se detiene). – jfs

+0

@ J.F .: Tiene razón, pero en este caso es seguro suponer que ninguna manzana superará los 10 kg. –

Respuesta

13

¿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.

+1

Solo para aclarar: los * iteradores incorporados de Python * no tienen el tipo de comportamiento que está buscando. – cdleary

+0

Cambié el estilo del código. Siéntase libre de retroceder. – jfs

+0

Esta parece ser la solución más directa. ¡Gracias! –

-2

No hay manera 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, por supuesto puede volver a agregar manualmente el elemento 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 métodos .push() y .pop() que le permiten tratar listas como stacks.

16

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í es 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 
+0

Gracias, esto responde a mi pregunta original sobre cómo se podría implementar una operación similar a un cambio. –

1

Mientras estaba escribiendo este @Patrick Ya se ha sugerido lo mismo. Pero desde que lo escribí 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]] 
+0

¡Gracias por el ejemplo! –

6

diría que the most Pythonic solution is the simplest one. 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. Iterators don't mix very nicely with mutation, anywho.

simple traducción de su aplicación (ignorando @Patrick 's optimización):

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, se puede utilizar una funcionalidad -como peek con índices secuencia ordenada:

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' Como el argumento "simple", consulte los iteradores collections.deque mencionados en el hilo (vinculado) anterior.

+1

Gracias, es bueno recordar dar un paso atrás en el problema. En lugar de centrarse en un mecanismo como el desvío, lo mejor es resolver el problema real de una manera más simple. –

4

Si no desea seguir la sugerencia de sólo la eliminación de la cláusula else del otro, usted puede escribir su propia unshift función que va a funcionar de una manera 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) 

Entonces en el 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 la 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 
+0

Su 'UnshiftableIterator' no es un iterador (no tiene el método' next() '). Es iterable (tiene el método '__iter __()'). – jfs

+0

@ J.F.Sebastian: cierto. Cambió el nombre para reflejar eso. – nosklo

+0

'para manzana en árbol:' -> 'para manzana en él:'. De lo contrario, los valores no cambiados nunca se usan. – jfs

0

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

0

Volver a la pregunta original sobre impementing unshift, operator.delitem se puede utilizar para implementar una sencilla función no 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]

+0

Eso no está desviado, eso es cambio. –

Cuestiones relacionadas