Я изучаю Python, и у меня возникла ситуация, когда я хочу использовать элементы итератора. Сложность заключается в том, что при определенных условиях я хочу «отменить итерацию». То есть верните элемент на переднюю часть итератора, прежде чем я зациклюсь.
Например, предположим, что я собираю яблоки с дерева. Моя корзина с фруктами вмещает не более 10 кг до того, как ее нужно опорожнить. Но мне нужно собрать каждое яблоко, прежде чем я смогу взвесить его и определить, не превышает ли это яблоко вместимость корзины.
На таком языке, как Perl, я мог бы вернуть яблоко обратно на дерево, а затем позволить выражению цикла повторно выбрать яблоко:
while ($apple = shift(@tree)) {
$wt = weight($apple);
if ($wt + weight(@basket) > 10) {
send(@basket);
@basket = ();
unshift(@tree, $apple);
} else {
push(@basket, $element);
}
}
Или я также могу использовать unshift(), который возобновляет обработку в верхней части блока, не оценивая выражение цикла. Таким образом, то же самое яблоко можно повторно обработать после того, как корзина будет опорожнена.
while ($apple = shift(@tree)) {
$wt = weight($apple);
if ($wt + weight(@basket) > 10) {
send(@basket);
@basket = ();
redo;
} else {
push(@basket, $apple);
}
}
Какое решение питонический для такого рода проблем было бы наиболее подходящим?
@ J.F .: Вы правы, но в этом случае можно с уверенностью предположить, что ни одно яблоко не превышает 10 кг.






Зачем беспокоиться об отсутствии сдвига, если всегда должно быть условие else?
for apple in tree:
if (apple.weight + basket.weight) > 10:
send(basket)
basket.clear()
basket.add(apple)
В любом случае, я совершенно уверен, что Python не имеет того поведения, которое вы ищете.
Просто чтобы уточнить: Python встроенные итераторы не имеет того поведения, которое он ищет.
Это кажется наиболее простым решением. Спасибо!
Нет общего способа передать значение в итератор в python. Для этого лучше подходят стек или связанный список.
Если вы перебираете список или что-то в этом роде, конечно, вы можете вручную добавить элемент обратно в список. Но вы также можете перебирать объекты, которыми нельзя манипулировать таким образом.
Если вы хотите использовать python для реализации этого алгоритма, вам нужно будет выбрать структуру данных, которая позволяет выполнять операции, которые вы хотите использовать. Я предлагаю методы .push() и .pop(), которые позволяют обрабатывать списки как стеки.
I'm learning Python, and I have a situation where I want to consume items from an iterator. The tricky part is that under certain conditions, I want to "un-iterate." That is, put an item back onto the front of the iterator before I loop.
Вот простое решение:
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
Спасибо, это отвечает на мой первоначальный вопрос о том, как можно реализовать операцию, подобную несмещению.
Пока я писал это, @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]]
Вы ищете генератор, итератор, который может получать изменения своего внутреннего состояния с помощью метода send ().
https://docs.python.org/howto/functional.html#passing-values-into-a-generator
Спасибо за этот совет о send ()! Я не уверен, что буду использовать его в этом случае, но это полезно знать на будущее.
Я бы сказал, что самое питоническое решение - самое простое. Вместо того, чтобы пытаться обернуть итератор в выражение генератора, которое позволяет «вернуться» или что-то подобное сложное, используйте цикл while, как в Perl! Итераторы не очень хорошо сочетаются с мутацией, кто угодно.
Простой перевод вашей реализации (без учета оптимизации @Патрик):
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, упомянутыми в вышеупомянутой (связанной) ветке.
Спасибо, хорошо не забыть отойти от проблемы. Вместо того, чтобы сосредотачиваться на таком механизме, как unshift, лучше решить истинную проблему более простым способом.
Если вы не хотите следовать предложению другого просто удалить предложение 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
Ваш UnshiftableIterator не является итератором (у него нет метода next()). Он повторяется (имеет метод __iter__()).
@ Дж. Ф. Себастьян: Верно. Изменено имя, чтобы отразить это.
for apple in tree: -> for apple in it:. В противном случае значения без сдвига никогда не используются.
Спасибо за пример. Приятно видеть, как создать итеративный класс, даже если я не использую это решение в данном конкретном случае.
Кстати, вам действительно нужен list.insert (0, yourObject)
Возвращаясь к исходному вопросу об имплементации unshift, operator.delitem можно использовать для реализации простой не-объектно-ориентированной функции:
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]
Это не без сдвига - это сдвиг.
если $ wt> 10, то есть бесконечный цикл (первый пример съедает всю память, второй просто никогда не останавливается).