【问题标题】:Pythonic way of determining if the current element is the first or last element of a generator?确定当前元素是生成器的第一个还是最后一个元素的 Pythonic 方法?
【发布时间】:2011-08-24 09:44:19
【问题描述】:

我正在查看一个生成器,如果需要特别注意,确定当前元素是生成器的第一个还是最后一个元素的 Pythonic 方法是什么?

谢谢

基本上是生成标签,所以我有类似的项目

<div class="first">1</div>
<div>...</div>
<div class="last">n</div>

所以我想将最后一项保持在循环中?

【问题讨论】:

  • 你说他们需要特别照顾是什么意思?
  • @bluepnume 我猜他想在它们上面运行一个函数或进程。

标签: python


【解决方案1】:

好吧,至于第一个元素:

for n, item in enumerate(generator()):
  if n == 0:
    # item is first
# out of the loop now: item is last

【讨论】:

  • 没有办法做循环中的最后一项吗?
  • 即使是生成器现在也可能不会,直到为时已晚。
  • @Timmy:知道为什么你需要知道这一点真的很有帮助,可能有一种方法可以在不检查循环中的最后一项的情况下做你正在寻找的事情
【解决方案2】:

首先,使用标志来判断您是否已处理任何内容。对于最后一个,将下一个值保存在一个变量中,如果没有,那就是最后一个。

【讨论】:

  • 应该可以创建一个通用的函数。我很惊讶 itertools 中什么都不存在。
【解决方案3】:

把它变成一个序列,例如:

>>> gen = (x for x in range(5))
>>> L = list(gen)
>>> L[0]
0
>>> L[-1]
4
>>>

如果您需要在循环期间执行此操作:

>>> gen = (x for x in range(5))
>>> L = list(gen)
>>> for idx, item in enumerate(L):
...    if idx == 0:
...        print(u'{item} is first'.format(item=item))
...    if idx == len(L) - 1:
...        print(u'{item} is last'.format(item=item))
...
0 is first
4 is last
>>>

显然,这不是解决方案,如果 是创建生成器的人,并且需要它保持这种状态(以节省内存),但如果您不在乎,这本身比设置标志更 Pythonic(这充其量是隐式的,因为它在迭代持续期间依赖于最后一个元素),enumerate 不会让你更接近于找到最后一个元素。

【讨论】:

  • 谢谢,我只需要第一个和最后一个元素,只是专门处理它们
  • thx,我知道这样做,不喜欢它,但它可能是最好的
  • 您不必将其列入列表。你可以创建一个生成器looks ahead
  • @Timmy - 我不想放弃积分 :) 但最好的答案是 @senderle(因为它以 Python 方式直接对生成器执行您想要的操作)。
【解决方案4】:

当然,它违反了所有生成器的优点,但如果你的迭代不大,你应该使用:

list(gener)[1:-1]

【讨论】:

    【解决方案5】:

    如果您担心动态构建的潜在大型集合,因此您不想暂时将其放入单个数据结构中,这里有一种不同的方法:

    FLAGMASK_FIRST = 1
    FLAGMASK_LAST = 2
    
    def flag_lastfirst(collection):
        first_flag = FLAGMASK_FIRST
        first = True
        index = 0
        for element in collection:
            if not first:
                yield index, first_flag, current
                index += 1
                first_flag = 0
            current = element
            first = False
        if not first:
            yield index, first_flag | FLAGMASK_LAST, current
    
    l = [1, 2, 3, 4]
    for k in flag_lastfirst(l):
        print(k)
    

    该函数将生成一个元组序列,每个元组对应原始集合中的每个元素。

    元组的内容:

    • t[0] = 从 0 开始的索引
    • t[1] = 按位标志,如果元素是第一个元素,则存在 FLAGMASK_FIRST,如果元素是最后一个元素,则存在 FLAGMASK_LAST
    • t[2] = 原始集合中的原始元素

    上面代码的示例输出:

     +-- 0-based index
     v
    (0, 1, 1)
    (1, 0, 2)
    (2, 0, 3)
    (3, 2, 4)
        ^  ^
        |  +-- the element from the original collection
        |
        +-- 1 means first, 2 means last,
            3 means both first and last, 0 is everything else
    

    我确信有更好的方法来构建这种东西,但无论如何这是我的贡献。

    【讨论】:

      【解决方案6】:

      这是一个类似枚举的生成器,它会向前跳过一个;它为最后一个元素返回 -1。

      >>> def annotate(gen):
      ...     prev_i, prev_val = 0, gen.next()
      ...     for i, val in enumerate(gen, start=1):
      ...         yield prev_i, prev_val
      ...         prev_i, prev_val = i, val
      ...     yield '-1', prev_val
      >>> for i, val in annotate(iter(range(4))):
      ...     print i, val
      ... 
      0 0
      1 1
      2 2
      -1 3
      

      它无法判断传递给它的生成器是否“新鲜”,但它仍然会告诉你何时结束:

      >>> used_iter = iter(range(5))
      >>> used_iter.next()
      0
      >>> for i, val in annotate(used_iter):
      ...     print i, val
      ... 
      0 1
      1 2
      2 3
      -1 4
      

      一旦迭代器用完,它就会像往常一样引发StopIteration

      >>> annotate(used_iter).next()
      Traceback (most recent call last):
        File "<stdin>", line 1, in <module>
        File "<stdin>", line 2, in annotate
      StopIteration
      

      【讨论】:

      • 事实上,当传递一个没有next() 方法的对象时会出错。为避免这种情况,您可以在annotate 的开头添加gen = iter(gen)
      【解决方案7】:

      我这样做的方式与此处的其他一些答案类似 - 但我这样做是出于偏好。也许它也适合您的喜好。

      使用下面的函数,我可以编写如下代码:

      values = [10, 11, 12, 13]
      for i, val, isfirst, islast in enumerate2(values):
        if isfirst:
          print 'BEGIN...', val
        elif islast:
          print val, '... END'
        else:
          print val
      

      这里是函数定义:

      def enumerate2(iterable_):
        it = iter(iterable_)
        try:
          e = it.next()
          isfirst = True
          i = 0
          try:
            while True:
              next_e = it.next()
              yield (i, e, isfirst, False)
              i += 1
              isfirst = False
              e = next_e
          except StopIteration:
            yield (i, e, isfirst, True)
        except StopIteration:
          pass
      

      【讨论】:

        猜你喜欢
        • 2014-06-19
        • 1970-01-01
        • 1970-01-01
        • 2020-12-18
        • 1970-01-01
        • 2023-03-25
        • 1970-01-01
        • 2010-12-10
        • 2015-04-20
        相关资源
        最近更新 更多