Pythonic эквивалент unshift или redo?

Я изучаю 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);
  }
}

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

если $ wt> 10, то есть бесконечный цикл (первый пример съедает всю память, второй просто никогда не останавливается).

jfs 08.01.2009 23:48

@ J.F .: Вы правы, но в этом случае можно с уверенностью предположить, что ни одно яблоко не превышает 10 кг.

Bill Karwin 09.01.2009 01:46
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
12
2
7 329
9
Перейти к ответу Данный вопрос помечен как решенный

Ответы 9

Ответ принят как подходящий

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

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

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

Просто чтобы уточнить: Python встроенные итераторы не имеет того поведения, которое он ищет.

cdleary 07.01.2009 22:45

Это кажется наиболее простым решением. Спасибо!

Bill Karwin 10.01.2009 05:26

Нет общего способа передать значение в итератор в 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

Спасибо, это отвечает на мой первоначальный вопрос о том, как можно реализовать операцию, подобную несмещению.

Bill Karwin 10.01.2009 05:27

Пока я писал это, @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 ()! Я не уверен, что буду использовать его в этом случае, но это полезно знать на будущее.

Bill Karwin 10.01.2009 05:28

Я бы сказал, что самое питоническое решение - самое простое. Вместо того, чтобы пытаться обернуть итератор в выражение генератора, которое позволяет «вернуться» или что-то подобное сложное, используйте цикл 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, лучше решить истинную проблему более простым способом.

Bill Karwin 10.01.2009 05:29

Если вы не хотите следовать предложению другого просто удалить предложение 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__()).

jfs 07.01.2009 23:53

@ Дж. Ф. Себастьян: Верно. Изменено имя, чтобы отразить это.

nosklo 08.01.2009 16:50
for apple in tree: -> for apple in it:. В противном случае значения без сдвига никогда не используются.
jfs 08.01.2009 19:11

Спасибо за пример. Приятно видеть, как создать итеративный класс, даже если я не использую это решение в данном конкретном случае.

Bill Karwin 10.01.2009 05:30

Кстати, вам действительно нужен 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]

Это не без сдвига - это сдвиг.

Bill Karwin 07.02.2013 06:03

Другие вопросы по теме