Питонический эквивалент unshift или redo? - PullRequest
12 голосов
/ 07 января 2009

Я изучаю Python, и у меня есть ситуация, когда я хочу потреблять элементы из итератора. Сложность в том, что при определенных условиях я хочу «повторить». То есть поместите элемент обратно на переднюю часть итератора, прежде чем выполнять цикл.

Например, предположим, что я собираю яблоки с дерева. Моя корзина с фруктами может вместить только 10 кг, прежде чем ее нужно будет опустошить. Но мне нужно собрать каждое яблоко, прежде чем я смогу взвесить его и определить, будет ли это яблоко превышать емкость корзины.

На таком языке, как Perl, я мог бы unshift() вернуть яблоко обратно на дерево, а затем позволить циклическому выражению перебрать яблоко:

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

Или я также могу использовать redo, который возобновляет обработку в верхней части блока, без оценки выражения цикла. Таким образом, то же самое яблоко может быть переработано после того, как корзина была опустошена.

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

Что было бы наиболее питоническим решением для такого рода проблем?

Ответы [ 9 ]

16 голосов
/ 07 января 2009

Я изучаю Python, и у меня есть ситуация, когда я хочу использовать элементы из итератора. Сложность в том, что при определенных условиях я хочу «повторить». То есть, перед тем, как выполнить цикл, поместите элемент обратно в начало итератора.

Вот простое решение:

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
13 голосов
/ 07 января 2009

Зачем беспокоиться о unshifting, когда всегда следует использовать условие else?

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

В любом случае, я вполне уверен, что Python не имеет такого поведения, которое вы ищете.

6 голосов
/ 07 января 2009

Я бы сказал, что самое питонское решение - самое простое . Вместо того, чтобы пытаться обернуть итератор в выражение генератора, которое позволяет вам «возвращаться назад» или что-то подобное, используйте цикл while, как в Perl! Итераторы не очень хорошо смешиваются с мутацией , в любом случае.

Простой перевод вашей реализации (игнорируя оптимизацию @ 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)

Или, вы можете использовать peek -подобную функциональность с упорядоченными индексами последовательности:

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

Если вам не нравится «простой» аргумент, ознакомьтесь с collections.deque итераторами, упомянутыми в вышеупомянутой (связанной) теме.

4 голосов
/ 07 января 2009

Если вы не хотите следовать предложению другого пользователя просто удалить предложение else, вы можете написать свою собственную функцию unshift, которая будет работать аналогично perl с любой итерацией:

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)

Тогда в вашем коде:

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

Некоторые испытания 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
3 голосов
/ 07 января 2009

Вы ищете генератор, итератор, который может получать изменения своего внутреннего состояния с помощью метода send ()

https://docs.python.org/howto/functional.html#passing-values-into-a-generator

1 голос
/ 14 апреля 2011

Кстати, что вы действительно хотите, так это list.insert (0, yourObject)

1 голос
/ 07 января 2009

Пока я писал это @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

хотя это не выталкивает () яблоки из исходного итератора. Пожалуйста, отметьте, если это тоже желаемое поведение.

выход

apples [1, 1, 3, 3, 1, 1, 3, 3, 2, 3]
baskets [[1, 1, 3], [3, 1, 1], [3, 3]]
0 голосов
/ 07 февраля 2013

Возвращаясь к первоначальному вопросу о том, как избежать unshift, operator.delitem может использоваться для реализации простой не-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 голосов
/ 07 января 2009

Нет общего способа вставить значение в итератор в python. Для этого лучше подходит стек или связанный список.

Если вы перебираете список или что-то еще, конечно, вы можете вручную добавить элемент в список. Но вы также можете перебирать объекты, которыми нельзя манипулировать таким образом.

Если вы хотите использовать python для реализации этого алгоритма, вам придется выбрать структуру данных, которая разрешает операции, которые вы хотите использовать. Я предлагаю методы .push() и .pop(), которые позволяют вам рассматривать списки как стеки.

...