【问题标题】:Iterate over pairs in a list (circular fashion) in Python在 Python 中以列表(循环方式)迭代对
【发布时间】:2010-11-18 10:46:54
【问题描述】:

问题很简单,我想迭代列表的每个元素和下一个成对的元素(用第一个包裹最后一个)。

我想到了两种非pythonic的方法:

def pairs(lst):
    n = len(lst)
    for i in range(n):
        yield lst[i],lst[(i+1)%n]

和:

def pairs(lst):
    return zip(lst,lst[1:]+[lst[:1]])

预期输出:

>>> for i in pairs(range(10)):
    print i

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

有什么关于更pythonic的方法的建议吗?也许有一个我没有听说过的预定义函数?

还有一个更通用的 n-fold(使用三重奏、四重奏等,而不是对)版本可能会很有趣。

【问题讨论】:

  • 你的第一个解决方案已经足够好了!
  • 在第二个版本中,将lst[0] 更改为lst[:1] 以使其适用于空序列。代码也变得更加对称。

标签: list iteration tuples python


【解决方案1】:
def pairs(lst):
    i = iter(lst)
    first = prev = item = i.next()
    for item in i:
        yield prev, item
        prev = item
    yield item, first

适用于任何非空序列,无需索引。

【讨论】:

  • 我喜欢它的流质量。并且对 iter() 的使用进行了优化。
  • @Darius,如果您需要支持空seq,请在设置sentinel = object()后使用next(i, sentinel)代替老式的i.next();并且,if first is sentinel: return 就在 for 之前。
  • @Darius:我已经澄清该序列必须是非空的。对于空序列应该做什么值得商榷:OP 的版本 1 产生一个空序列,而版本 2 引发 IndexError。
  • 单元素序列崩溃:调用i.next()后序列为空,因此循环不绑定item,因此yield item, first引发UnboundLocalError
  • 对于 python 3:将 i.next() 替换为 next(i),一切就绪。它实际上也适用于 python 2。
【解决方案2】:

我自己编写了元组通用版本,我喜欢第一个版本,因为它简洁优雅,我看的越多,我就越觉得它是 Pythonic……毕竟,有什么比一个更 Pythonic 的了带有 zip、星号参数扩展、列表推导、列表切片、列表连接和“范围”的衬里?

def ntuples(lst, n):
    return zip(*[lst[i:]+lst[:i] for i in range(n)])

即使对于大型列表,itertools 版本也应该足够高效...

from itertools import *
def ntuples(lst, n):
    return izip(*[chain(islice(lst,i,None), islice(lst,None,i)) for i in range(n)])

还有一个用于不可索引序列的版本:

from itertools import *
def ntuples(seq, n):
    iseq = iter(seq)
    curr = head = tuple(islice(iseq, n))
    for x in chain(iseq, head):
        yield curr
        curr = curr[1:] + (x,)

无论如何,谢谢大家的建议! :-)

【讨论】:

  • 您的第一个答案(在您的问题中)比其中任何一个都容易理解一百万倍。这使它在我的书中更加pythonic:-/
  • 试试这个:“for a in ntuples(count(), 3): print a;”我认为您需要使用 itertools.tee() 才能使其正常工作。
  • count 不是可索引的序列,所以它不适用于这种广泛使用切片的方法是正常的......
  • 呵呵,喜欢讽刺!
【解决方案3】:

我一如既往地喜欢 tee:

from itertools import tee, izip, chain

def pairs(iterable):
    a, b = tee(iterable)
    return izip(a, chain(b, [next(b)]))

【讨论】:

    【解决方案4】:

    这可能是令人满意的:

    def pairs(lst):
        for i in range(1, len(lst)):
            yield lst[i-1], lst[i]
        yield lst[-1], lst[0]
    
    >>> a = list(range(5))
    >>> for a1, a2 in pairs(a):
    ...     print a1, a2
    ...
    0 1
    1 2
    2 3
    3 4
    4 0
    

    如果你喜欢这类东西,请查看wordaligned.org 上的 python 文章。作者对python中的生成器情有独钟。

    【讨论】:

    • 是的,我注意到了这一点,并且我已经修复了它——在它被否决的时间里。我的错,真的。
    【解决方案5】:

    我会这样做(主要是因为我能读懂这个):

    class Pairs(object):
        def __init__(self, start):
            self.i = start
        def next(self):
            p, p1 = self.i, self.i + 1
            self.i = p1
            return p, p1
        def __iter__(self):
            return self
    
    if __name__ == "__main__":
        x = Pairs(0)
        y = 1
        while y < 20:
            print x.next()
            y += 1
    

    给予:

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

    【讨论】:

    • +1 - 就我个人而言,我更喜欢这个解决方案,而不是基于“产量”的解决方案。
    【解决方案6】:
    [(i,(i+1)%len(range(10))) for i in range(10)]
    

    将 range(10) 替换为您想要的列表。

    一般来说,“循环索引”在 python 中很容易;只需使用:

    a[i%len(a)] 
    

    【讨论】:

      【解决方案7】:

      回答关于解决一般情况的问题:

      import itertools
      
      def pair(series, n):
          s = list(itertools.tee(series, n))
          try:
              [ s[i].next() for i in range(1, n) for j in range(i)]
          except StopIteration:
              pass
          while True:
              result = []
              try:
                  for j, ss in enumerate(s):
                      result.append(ss.next())
              except StopIteration:
                  if j == 0:
                      break
                  else:
                      s[j] = iter(series)
                      for ss in s[j:]:
                          result.append(ss.next())
              yield result
      

      输出是这样的:

      >>> for a in pair(range(10), 2):
      ...     print a
      ...
      [0, 1]
      [1, 2]
      [2, 3]
      [3, 4]
      [4, 5]
      [5, 6]
      [6, 7]
      [7, 8]
      [8, 9]
      [9, 0]
      >>> for a in pair(range(10), 3):
      ...     print a
      ...
      [0, 1, 2]
      [1, 2, 3]
      [2, 3, 4]
      [3, 4, 5]
      [4, 5, 6]
      [5, 6, 7]
      [6, 7, 8]
      [7, 8, 9]
      [8, 9, 0]
      [9, 0, 1]
      

      【讨论】:

      • 好吧,pair() 现在名字错了;)
      • 是的,命名错误,不漂亮,并且在 OP 的 pythoned-to-the-max 自我回答后 2 分钟。嗯嗯。
      【解决方案8】:

      这无限循环,无论好坏,但在算法上非常清楚。

      from itertools import tee, cycle
      
      def nextn(iterable,n=2):
          ''' generator that yields a tuple of the next n items in iterable.
          This generator cycles infinitely '''
          cycled = cycle(iterable)
          gens = tee(cycled,n)
      
          # advance the iterators, this is O(n^2)
          for (ii,g) in zip(xrange(n),gens):
              for jj in xrange(ii):
                  gens[ii].next()
      
          while True:
              yield tuple([x.next() for x in gens])
      
      
      def test():
          data = ((range(10),2),
              (range(5),3),
              (list("abcdef"),4),)
          for (iterable, n) in data:
              gen = nextn(iterable,n)
              for j in range(len(iterable)+n):
                  print gen.next()            
      
      
      test()
      

      给予:

      (0, 1)
      (1, 2)
      (2, 3)
      (3, 4)
      (4, 5)
      (5, 6)
      (6, 7)
      (7, 8)
      (8, 9)
      (9, 0)
      (0, 1)
      (1, 2)
      (0, 1, 2)
      (1, 2, 3)
      (2, 3, 4)
      (3, 4, 0)
      (4, 0, 1)
      (0, 1, 2)
      (1, 2, 3)
      (2, 3, 4)
      ('a', 'b', 'c', 'd')
      ('b', 'c', 'd', 'e')
      ('c', 'd', 'e', 'f')
      ('d', 'e', 'f', 'a')
      ('e', 'f', 'a', 'b')
      ('f', 'a', 'b', 'c')
      ('a', 'b', 'c', 'd')
      ('b', 'c', 'd', 'e')
      ('c', 'd', 'e', 'f')
      ('d', 'e', 'f', 'a')
      

      【讨论】:

      • 我喜欢使用 itertools.cycle()。我不确定为什么您的测试代码会执行 range(len(iter) + n)。我认为 range(len(iter)) 是正确的。您用于推进迭代器的 n**2 代码就像我的代码:[ s[i].next() for i in range(1, n) for j in range(i)].
      • re: len(iter)+n... 我想证明它确实可以无限循环。毕竟,它只是在测试代码中。你前进的单线更短/更好。无论如何,那是代码中绝对需要注释的那一行,因为它是算法的关键:)
      【解决方案9】:

      Fortran 的 zip * range 解决方案的更短版本(这次使用 lambda;):

      group = lambda t, n: zip(*[t[i::n] for i in range(n)])
      
      group([1, 2, 3, 3], 2)
      

      给予:

      [(1, 2), (3, 4)]
      

      【讨论】:

      • 很好,但不太正确,我需要的是成对迭代,但重复元素和包装,例如,输出应该是 [(1,2),(2,3),(3,4),(4,1)]
      【解决方案10】:

      这是一个支持可选起始索引的版本(例如返回 (4, 0) 作为第一对,使用 start = -1:

      import itertools
      
      def iterrot(lst, start = 0):
      
          if start == 0:
              i = iter(lst)
          elif start > 0:
              i1 = itertools.islice(lst, start, None)
              i2 = itertools.islice(lst, None, start)
              i = itertools.chain(i1, i2)
          else:
              # islice doesn't support negative slice indices so...
              lenl = len(lst)
              i1 = itertools.islice(lst, lenl + start, None)
              i2 = itertools.islice(lst, None, lenl + start)
              i = itertools.chain(i1, i2)
          return i
      
      
      def iterpairs(lst, start = 0):
      
          i = iterrot(lst, start)     
      
          first = prev = i.next()
          for item in i:
              yield prev, item
              prev = item
          yield prev, first
      
      
      def itertrios(lst, start = 0):
      
          i = iterrot(lst, start)     
      
          first = prevprev = i.next()
          second = prev = i.next()
          for item in i:
              yield prevprev, prev, item
              prevprev, prev = prev, item
      
          yield prevprev, prev, first
          yield prev, first, second
      

      【讨论】:

        【解决方案11】:
        def pairs(ex_list):
            for i, v in enumerate(ex_list):
                if i < len(list) - 1:
                    print v, ex_list[i+1]
                else:
                    print v, ex_list[0]
        

        Enumerate 返回一个带有索引号和值的元组。我打印列表ex_list[i+1] 的值和以下元素。 if i &lt; len(list) - 1 表示如果 v 不是列表的最后一个成员。如果是:打印 v 和列表的第一个元素print v, ex_list[0]

        编辑:

        你可以让它返回一个列表。只需将打印的元组附加到列表并返回即可。

        def pairs(ex_list):
            result = []
            for i, v in enumerate(ex_list):
                if i < len(list) - 1:
                    result.append((v, ex_list[i+1]))
                else:
                    result.append((v, ex_list[0]))
            return result
        

        【讨论】:

        • 你能解释一下你的答案吗?
        【解决方案12】:

        当然,您始终可以使用 deque

        from collections import deque
        from itertools import *
        
        def pairs(lst, n=2):
            itlst = iter(lst)
            start = list(islice(itlst, 0, n-1))
            deq = deque(start, n)
            for elt in chain(itlst, start):
                deq.append(elt)
                yield list(deq)
        

        【讨论】:

          【解决方案13】:
          i=(range(10))
          
          for x in len(i):
              print i[:2]
              i=i[1:]+[i[1]]
          

          比这更 Pythonic 是不可能的

          【讨论】:

          • less pythonic 比这个其实很难。
          • 如果它真的工作会有所帮助:TypeError: 'int' object is not iterable
          猜你喜欢
          • 2021-04-11
          • 2011-03-16
          • 1970-01-01
          • 2014-10-31
          • 2014-01-13
          • 2016-09-04
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多