【问题标题】:Python: Elegant way of dual/multiple iteration over the same listPython:在同一个列表上进行双重/多重迭代的优雅方式
【发布时间】:2010-10-26 11:01:21
【问题描述】:

我已经编写了一些类似以下的代码来将项目与列表中的其他项目进行比较。这种双重迭代是否有更优雅的模式?

jump_item_iter = (j for j in items if some_cond)
try:
    jump_item = jump_item_iter.next()
except StopIteration:
    return
for item in items:
    if jump_item is item:
        try:
            jump_item = jump_iter.next()
        except StopIteration:
            return
    # do lots of stuff with item and jump_item

我不认为“except StopIteration”很优雅

编辑:

为了更清楚,我想访问列表中的每个项目,并将其与列表中满足 some_cond 的下一个项目 (jump_item) 配对。

【问题讨论】:

  • 请提供您期望的输入/输出,而不是(或除了)代码。
  • 如果items = range(10) and some_cond=lambda x: x %2,那么输出应该是:[(0, 1), (1, 3), (2, 3), (3, 5), (4, 5), (5, 7), (6, 7), (7, 9), (8, 9)]
  • 是的,odwl,这将是预期的输出。

标签: python iterator


【解决方案1】:

据我所知,任何现有的解决方案都适用于一般的一次性,可能是无限的迭代器ator,它们似乎都需要一个迭代器able

这是一个解决方案。

def batch_by(condition, seq):
    it = iter(seq)
    batch = [it.next()]
    for jump_item in it:
        if condition(jump_item):
            for item in batch:
                yield item, jump_item
            batch = []
        batch.append(jump_item)

这将很容易在无限迭代器上工作:

from itertools import count, islice
is_prime = lambda n: n == 2 or all(n % div for div in xrange(2,n))
print list(islice(batch_by(is_prime, count()), 100))

这将打印前 100 个整数及其后的素数。

【讨论】:

  • 看起来是一个很好的解决方案。但批处理不是迭代器。
【解决方案2】:

我不知道compare() 在做什么,但在 80% 的情况下,你可以用一本普通的字典或一对字典来做到这一点。在列表中跳转是一种线性搜索。线性搜索 - 在可能的范围内 - 应始终替换为直接引用(即 dict)或树搜索(使用 bisect 模块)。

【讨论】:

  • 我已经删除了对 compare() 的引用,因为它令人困惑——我实际上用 item 和 jump_item 做了很多事情—— compare 是一个占位符。这是线性搜索,因为items 绝对是一个序列。
【解决方案3】:

以下迭代器节省时间和内存:

def jump_items(items):
    number_to_be_returned = 0
    for elmt in items:
        if <condition(elmt)>:
            for i in range(number_to_be_returned):
                yield elmt
            number_to_be_returned = 1
        else:
            number_to_be_returned += 1

for (item, jump_item) in zip(items, jump_items(items)):
    # do lots of stuff

请注意,您实际上可能希望将第一个 number_to_be_returned 设置为 1...

【讨论】:

    【解决方案4】:

    编写一个生成器函数:

    def myIterator(someValue):
        yield (someValue[0], someValue[1])
    
    for element1, element2 in myIterator(array):
         # do something with those elements.
    

    【讨论】:

      【解决方案5】:
      paired_values = []
      for elmt in reversed(items):
          if <condition>:
              current_val = elmt
          try:
              paired_values.append(current_val)
          except NameError:  # for the last elements of items that don't pass the condition
              pass
      paired_values.reverse()
      
      for (item, jump_item) in zip(items, paired_values):  # zip() truncates to len(paired_values)
          # do lots of stuff
      

      如果 items 的第一个元素匹配,则将其用作 jump_item。这是与原始代码的唯一区别(您可能想要这种行为)。

      【讨论】:

        【解决方案6】:

        我不知道您想用该代码做什么。但我有 99% 的把握,无论它是什么,都可能在 2 行中完成。我也觉得 '==' 运算符应该是一个 'is' 运算符,否则 compare() 函数在做什么?如果从第二个 jump_iter.next 调用返回的项目也等于“项目”,会发生什么?由于您将比较第二个而不是第一个,因此该算法似乎会做错事。

        【讨论】:

        • 谢谢,我已将 '==' 更新为 'is',这样更正确。 compare 函数是一大块与问题没有太大关系的代码的占位符。
        【解决方案7】:

        因此,您想比较同一列表中的项目对,该对中的第二个项目必须满足某些条件。通常,当您想比较列表中的对时,请使用zip(或itertools.izip):

        for item1, item2 in zip(items, items[1:]):
            compare(item1, item2)
        

        弄清楚如何将您的some_cond 放入其中:)

        【讨论】:

        • 我没有将 items[i] 与 items[i+1] 进行比较,只要 some_cond 为 false,item2 就可以跳过列表中的多个位置。
        • 这就是为什么我要讨论如何适应some_cond。类似others = [item for item in items[1:] if some_cond] 然后zip(items, others)
        • 他应该“弄清楚”的部分不是真正的难点吗?为什么你的方法适合这个?
        • 嗨 Virgil - 如果我 zip(items, others) 那么每个 item2 将只与一个 item1 配对;在我当前的实现中,可以将多个 item1 与同一个 item2 配对(直到 item1 到达 item2 时 item2 再次向前跳跃)
        【解决方案8】:

        您基本上是在尝试将迭代器中的每个项目与原始列表中的每个其他项目进行比较吗?

        在我看来,这应该只是使用两个循环的情况,而不是试图将其组合成一个。

        filtered_items = (j for j in items if some_cond) for filtered in filtered_items: for item in items: if filtered != item: compare(filtered, item)

        【讨论】:

        • 我试图避免嵌套循环,因为我只需要访问每个“项目”一次。
        【解决方案9】:
        for i in range( 0, len( items ) ):
            for j in range( i+1, len( items ) ):
                if some_cond:
                    #do something
                    #items[i] = item, items[j] = jump_item
        

        【讨论】:

          【解决方案10】:

          使用 itertools.groupby 会更好:

          def h(lst, cond):
            remain = lst
            for last in (l for l in lst if cond(l)):
              group = itertools.groupby(remain, key=lambda x: x < last)
              for start in group.next()[1]:
                yield start, last
              remain = list(group.next()[1])
          

          用法: lst = 范围(10) 条件 = λ x: x%2 打印列表(h(lst, cond))

          将打印

          [(0, 1), (1, 3), (2, 3), (3, 5), (4, 5), (5, 7), (6, 7), (7, 9), (8, 9)]
          

          【讨论】:

            【解决方案11】:

            只有迭代器

            def(lst, some_cond):
                  jump_item_iter = (j for j in lst if som_cond(j))
                  pairs = itertools.izip(lst, lst[1:])
                  for last in jump_item_iter:
                    for start, start_next in itertools.takewhile(lambda pair: pair[0] < last, pairs):
                      yield start, last
                    pairs = itertools.chain([(start_next, 'dummy')], pairs)
            

            输入:range(10) 和 some_cond = lambda x : x % 2 给出 [(0, 1), (1, 3), (2, 3), (3, 5), (4, 5), (5, 7), (6, 7), (7, 9), (8, 9)] (和你的例子一样)

            【讨论】:

            • 在这里,即使是“批处理”(参见 Ants Aasma 解决方案)也是可迭代的。
            【解决方案12】:

            你可以把你的循环体写成:

            import itertools, functools, operator
            
            for item in items:
                jump_item_iter = itertools.dropwhile(functools.partial(operator.is_, item), 
                                                     jump_item_iter)
            
                # do something with item and jump_item_iter
            

            dropwhile 将返回一个迭代器,它会跳过所有符合条件的迭代器(此处为“is item”)。

            【讨论】:

            • 你是否错过了循环体前的jump_item_iter = items 行?
            • 应该和循环体前的问题一样。即 jump_item_iter = "(j for j in items if some_cond)"
            • 我认为这不起作用,因为无法知道何时调用 jump_item_iter.next() 以及何时重用 jump_item_iter.next() 的先前值
            • jump_item_iter 将被替换为一个迭代器,该迭代器将包装前一个 - 调用 next() 应该与在循环中应用检查具有相同的效果,返回下一个项目,跳过相同的项目.但是,我可能会误解您的代码 - 我刚刚重读它并注意到您在循环体中使用 jump_iter,我将其读为 jump_item_iter。如果这确实是不同的东西,那么我想我误解了这个问题。
            【解决方案13】:

            您可以将整个迭代放入一个单独的 try 结构中,这样会更清晰:

            jump_item_iter = (j for j in items if some_cond)
            try:
                jump_item = jump_item_iter.next()
                for item in items:
                    if jump_item is item:
                        jump_item = jump_iter.next()
            
                # do lots of stuff with item and jump_item
            
             except StopIteration:
                 pass
            

            【讨论】:

            • 我想我可以更好地提出这个问题:我想访问列表中的每个项目并将其与列表中满足特定条件的下一个项目配对。我会更新问题
            【解决方案14】:

            这是一个简单的解决方案,可能看起来更简洁:

            for i, item in enumerate(items):
                for next_item in items[i+1:]:
                    if some_cond(next_item):
                        break
                # do some stuff with both items
            

            缺点是要多次检查 next_item 的条件。但是您可以轻松地对此进行优化:

            cond_items = [item if some_cond(item) else None for item in items]
            for i, item in enumerate(items):
                for next_item in cond_items[i+1:]:
                    if next_item is not None:
                        break
                # do some stuff with both items
            

            但是,这两种解决方案都比问题中的原始解决方案带来更多开销。当你开始使用计数器来解决这个问题时,我认为最好直接使用迭代器接口(就像在原始解决方案中一样)。

            【讨论】:

              【解决方案15】:

              你可以这样做:

              import itertools
              
              def matcher(iterable, compare):
                  iterator= iter(iterable)
                  while True:
                      try: item= iterator.next()
                      except StopIteration: break
                      iterator, iterator2= itertools.tee(iterator)
                      for item2 in iterator2:
                          if compare(item, item2):
                              yield item, item2
              

              但它相当复杂(实际上效率不高),如果你只是做一个会更简单

              items= list(iterable)
              

              然后在items上写两个循环。

              显然,这不适用于无限迭代,但您的规范只能用于有限迭代。

              【讨论】:

                【解决方案16】:

                如果我理解正确,您希望主 for item in items: 在过滤掉某些项目的迭代器之后“追逐”。好吧,您无能为力,除了可以将其包装到 chase_iterator(iterable, some_cond) 生成器中,这将使您的主代码更具可读性。

                也许更易读的方法是“累加器方法”(如果 compare() 的顺序无关紧要的话),例如:

                others = []
                for item in items:
                    if some_cond(item):
                        for other in others:
                            compare(item, other)
                        others = []
                    else:
                        others.append(item)
                

                【讨论】:

                  【解决方案17】:
                  l = [j for j in items if some_cond]
                  for item, jump_item in zip(l, l[1:]):
                      # do lots of stuff with item and jump_item
                  

                  如果 l = [j for j in range(10) if j%2 ==0] 则迭代结束:[(0, 2),(2, 4),(4, 6),(6 , 8)]。

                  【讨论】:

                  • 永远不会太晚,但这不是 OP 想要的
                  • 使用 items = range(10) 和 some_cond=lambda x %2,这将给出 (1 3), (3 5), (5,7), (7,9) 我认为例如,这不是您缺少 0,1 的问题
                  • 怎么回事?第一行获取满足 some_cond 的所有项目,并且循环在成对的此类项目上。即满足 cond 的每个元素都与满足 cond 的下一个元素配对。
                  猜你喜欢
                  • 2012-03-11
                  • 1970-01-01
                  • 1970-01-01
                  • 2018-04-29
                  • 2021-01-06
                  • 2017-05-25
                  • 1970-01-01
                  • 2011-10-15
                  • 2014-02-01
                  相关资源
                  最近更新 更多