12

我正在学习 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);
  }
}

对于这类问题,最Pythonic的解决方案是什么?

4

10 回答 10

16

我正在学习 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
于 2009-01-07T02:27:28.707 回答
13

当 else 子句应该总是出现时,为什么还要为 unshifting 烦恼呢?

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

无论如何,我相当肯定 Python 没有您正在寻找的那种行为。

于 2009-01-07T02:12:11.360 回答
6

我会说最 Pythonic 的解决方案是最简单的解决方案。与其尝试将迭代器包装在允许您“回溯”或类似复杂的生成器表达式中,不如使用 while 循环,就像在 Perl 中一样!迭代器不能很好地与 mutation 混合,任何人。

您的实现的简单翻译(忽略@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带有有序序列索引的 -like 功能:

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上述(链接)线程中提到的迭代器。

于 2009-01-07T06:09:24.657 回答
4

如果您不想遵循其他人的建议,即删除 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
于 2009-01-07T12:40:38.250 回答
3

您正在寻找一个生成器,一个可以通过 send() 方法接收对其内部状态的修改的迭代器

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

于 2009-01-07T03:29:59.890 回答
2

顺便说一句,你真正想要的是 list.insert(0,yourObject)

于 2011-04-14T01:41:59.280 回答
1

在我写这篇文章时,@Patrick 已经提出了同样的建议。但既然我已经写了它,我还是会粘贴代码,并在 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

虽然这不会 pop() 来自原始迭代器的苹果。请备注这是否也是一种理想的行为。

输出

apples [1, 1, 3, 3, 1, 1, 3, 3, 2, 3]
baskets [[1, 1, 3], [3, 1, 1], [3, 3]]
于 2009-01-07T02:36:02.153 回答
1

目前我的升级版pythonizer无法处理redo,但如果我添加它,我可能会像这样实现它:

while (apple:=(tree.pop(0) if tree else None)):
    while True:
        wt = weight(apple)
        if wt+weight(*basket) > 10:
            sendit(basket)
            basket = []
            continue
        else:
            basket.append(apple)
        break

(注意:我必须更改send为,sendit因为send在 perl 中是预定义的。)

于 2021-12-29T11:43:59.507 回答
0

回到最初关于实施 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]

于 2013-02-06T21:20:56.743 回答
-2

没有办法将值推送到 python 中的迭代器中。堆栈或链表更适合于此。

如果您正在迭代列表或其他内容,当然您可以手动将项目添加回列表。但是您也可以迭代无法以这种方式操作的对象。

如果您想使用 python 来实现该算法,您必须选择一个允许您想要使用的操作的数据结构。我建议使用.push()and.pop()方法让您将列表视为堆栈。

于 2009-01-07T02:18:04.250 回答