【问题标题】:Pythonic equivalent of unshift or redo?Pythonic 相当于 unshift 或 redo?
【发布时间】:2010-09-29 22:54:34
【问题描述】:

我正在学习 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的解决方案是什么?

【问题讨论】:

  • 如果 $wt > 10 则有一个无限循环(第一个示例吃掉所有内存,第二个示例永远不会停止)。
  • @J.F.:你是对的,但在这种情况下,可以假设没有一个苹果会超过 10 公斤。

标签: python redo


【解决方案1】:

当 else 子句应该总是出现时,为什么还要不换档呢?

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

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

【讨论】:

  • 澄清一下:Python 的 内置迭代器 没有他想要的那种行为。
  • 这似乎是最直接的解决方案。谢谢!
【解决方案2】:

在 python 中,没有通用的方法可以将值推送到迭代器中。堆栈或链表更适合这种情况。

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

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 谢谢,这回答了我最初的问题,即如何实现类似 unshift 的操作。
    【解决方案4】:

    在我写这篇文章时,@Patrick 已经提出了同样的建议。但既然我已经写了,我还是会粘贴代码,在 Patrick 的代码标记方法中使用 cmets。

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

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

      • 感谢有关 send() 的提示!我不确定我会在这种情况下使用它,但很高兴知道未来。
      【解决方案6】:

      我会说the most Pythonic solution is the simplest one。与其尝试将迭代器包装在允许您“回溯”或类似复杂的生成器表达式中,不如使用 while 循环,就像在 Perl 中一样! Iterators don't mix very nicely with 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 迭代器。

      【讨论】:

      • 谢谢,记得从问题中退一步是件好事。与其专注于 unshift 之类的机制,不如用更简单的方式解决真正的问题。
      【解决方案7】:

      如果您不想听从其他人的建议,即删除 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__() 方法)。
      • @J.F.塞巴斯蒂安:是的。更改名称以反映这一点。
      • for apple in tree: -> for apple in it:。否则永远不会使用未移位的值。
      • 感谢您的示例。很高兴看到如何创建一个可迭代的类,即使我在这种特殊情况下不使用这个解决方案。
      【解决方案8】:

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

      【讨论】:

        【解决方案9】:

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

        【讨论】:

        • 这不是 unshift -- 那是 shift。
        【解决方案10】:

        目前我的升级版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 中预定义的。)

        【讨论】:

        • 感谢您的回答!
        猜你喜欢
        • 1970-01-01
        • 2011-11-27
        • 1970-01-01
        • 2015-05-30
        • 2019-04-21
        • 1970-01-01
        • 1970-01-01
        • 2013-02-25
        • 2011-02-28
        相关资源
        最近更新 更多