【问题标题】:Iterating over every two elements in a list [duplicate]迭代列表中的每两个元素[重复]
【发布时间】:2015-12-25 12:24:15
【问题描述】:

如何创建 for 循环或列表推导式,以便每次迭代都给我两个元素?

l = [1,2,3,4,5,6]

for i,k in ???:
    print str(i), '+', str(k), '=', str(i+k)

输出:

1+2=3
3+4=7
5+6=11

【问题讨论】:

  • 下次避免只命名为“l”。它可以快速与一些 1 或 I 或 | 混淆将其命名为 List ... 或 L(如果您想节省空间 facepalm)。
  • 只需使用一个包含两个变量的循环:for i,k in zip(range(1,7)[0::2], range(1,7)[1::2]):打印 str(i), '+', str(k), '=', str(i+k)

标签: python list


【解决方案1】:

您需要一个pairwise()(或grouped())实现。

def pairwise(iterable):
    "s -> (s0, s1), (s2, s3), (s4, s5), ..."
    a = iter(iterable)
    return zip(a, a)

for x, y in pairwise(l):
   print("%d + %d = %d" % (x, y, x + y))

或者,更一般地说:

def grouped(iterable, n):
    "s -> (s0,s1,s2,...sn-1), (sn,sn+1,sn+2,...s2n-1), (s2n,s2n+1,s2n+2,...s3n-1), ..."
    return zip(*[iter(iterable)]*n)

for x, y in grouped(l, 2):
   print("%d + %d = %d" % (x, y, x + y))

在 Python 2 中,您应该导入 izip 来替代 Python 3 的内置 zip() 函数。

martineau his answermy question 的所有功劳,我发现这非常有效,因为它只在列表上迭代一次,并且在此过程中不会创建任何不必要的列表。

注意:这不应与 Python 自己的 itertools documentation 中的 pairwise recipe 混淆,后者产生 s -> (s0, s1), (s1, s2), (s2, s3), ...,正如 cmets 中的 @lazyr 所指出的那样。

对于那些希望在 Python 3 上使用 mypy 进行类型检查的人来说,这是一个小小的补充:

from typing import Iterable, Tuple, TypeVar

T = TypeVar("T")

def grouped(iterable: Iterable[T], n=2) -> Iterable[Tuple[T, ...]]:
    """s -> (s0,s1,s2,...sn-1), (sn,sn+1,sn+2,...s2n-1), ..."""
    return zip(*[iter(iterable)] * n)

【讨论】:

  • 不要与itertools recipes 部分中建议的成对函数混淆,后者产生s -> (s0,s1), (s1,s2), (s2, s3), ...
  • 它做了不同的事情。与具有相同名称的 itertools 配方函数相比,您的版本只产生一半的对数。当然你的更快...
  • 小心!使用这些函数会使您面临无法迭代可迭代对象的最后一个元素的风险。示例:list(grouped([1,2,3],2)) >>> [(1, 2)] .. 当你期望 [(1,2),(3,)]
  • @Erik49:在问题中指定的情况下,拥有“不完整”元组是没有意义的。如果你想包含一个不完整的元组,你可以使用izip_longest() 而不是izip()。例如:list(izip_longest(*[iter([1, 2, 3])]*2, fillvalue=0)) --> [(1, 2), (3, 0)]。希望这会有所帮助。
  • 但是 to 与同一文档中的 grouper 配方混淆。绝对值得了解它是如何工作的——这就是您可以决定如何处理锯齿组(跳过剩余值、填充填充值或返回短组)的方式。
【解决方案2】:

又一次尝试更清洁的解决方案

def grouped(itr, n=2):
    itr = iter(itr)
    end = object()
    while True:
        vals = tuple(next(itr, end) for _ in range(n))
        if vals[-1] is end:
            return
        yield vals

更多自定义选项

from collections.abc import Sized

def grouped(itr, n=2, /, truncate=True, fillvalue=None, strict=False, nofill=False):
    if strict:
        if isinstance(itr, Sized):
            if len(itr) % n != 0:
                raise ValueError(f"{len(itr)=} is not divisible by {n=}")
    itr = iter(itr)
    end = object()
    while True:
        vals = tuple(next(itr, end) for _ in range(n))
        if vals[-1] is end:
            if vals[0] is end:
                return
            if strict:
                raise ValueError("found extra stuff in iterable")
            if nofill:
                yield tuple(v for v in vals if v is not end)
                return
            if truncate:
                return
            yield tuple(v if v is not end else fillvalue for v in vals)
            return
        yield vals

【讨论】:

  • 就像在我的解决方案中一样,您现在需要捕获StopIteration
  • @mic_e 我使用默认值的next,它不会引发StopIteration
【解决方案3】:

拆包:

l = [1,2,3,4,5,6]
while l:
    i, k, *l = l
    print(f'{i}+{k}={i+k}') 

注意:这将消耗l,之后将其留空。

【讨论】:

  • 哇!为什么我不能考虑这个:) 你只需要处理没有绝对对(奇数条目)的情况
  • 又好又聪明:)
【解决方案4】:

这是一个简单的解决方案,它使用范围函数从元素列表中选择替代元素。

注意:这仅对偶数列表有效。

a_list = [1, 2, 3, 4, 5, 6]
empty_list = [] 
for i in range(0, len(a_list), 2):
    empty_list.append(a_list[i] + a_list[i + 1])   
print(empty_list)
# [3, 7, 11]

【讨论】:

  • 尝试提供解释而不仅仅是代码。
  • @LuisArgüelles 的建议在这里尤其重要,因为这个问题已有十年之久,已经有二十个答案。想象一下,试图对这些答案进行分类以找出哪个是相关的?尝试解释是什么让您的答案与众不同,以及您的方法何时可能更受欢迎。您是否依赖其他答案中未解决的新语法?你的方法更快或更有效吗?它是否更适合特定类型的数据(例如,较小的数组)?
【解决方案5】:

itertoolsrecipes之一中给出了完善的Python3解决方案:

import itertools

def grouper(iterable, n, fillvalue=None):
    "Collect data into fixed-length chunks or blocks"
    # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return itertools.zip_longest(*args, fillvalue=fillvalue)

【讨论】:

    【解决方案6】:

    虽然使用zip 的所有答案都是正确的,但我发现自己实现该功能会导致代码更具可读性:

    def pairwise(it):
        it = iter(it)
        while True:
            try:
                yield next(it), next(it)
            except StopIteration:
                # no more elements in the iterator
                return
    

    it = iter(it) 部分确保it 实际上是一个迭代器,而不仅仅是一个可迭代对象。如果it 已经是一个迭代器,则此行是空操作。

    用法:

    for a, b in pairwise([0, 1, 2, 3, 4, 5]):
        print(a + b)
    

    【讨论】:

    • 这个解决方案允许泛化到元组的大小 > 2
    • 如果it 只是一个迭代器而不是一个可迭代的,这个解决方案也可以工作。其他解决方案似乎依赖于为序列创建两个独立迭代器的可能性。
    • 在看到这个答案之前,我在*.com/a/16815056/2480481 找到了这种方法。比处理 zip() 更干净、更容易。
    • 我喜欢它可以避免将内存使用量增加三倍作为接受的答案。
    • 这不适用于 Python 3.5+ 中的 for 循环,因为 PEP 479 会用 RuntimeError 替换生成器中引发的任何 StopIteration
    【解决方案7】:

    有很多方法可以做到这一点。例如:

    lst = [1,2,3,4,5,6]
    [(lst[i], lst[i+1]) for i,_ in enumerate(lst[:-1])]    
    >>>[(1, 2), (2, 3), (3, 4), (4, 5), (5, 6)]
    
    [i for i in zip(*[iter(lst)]*2)]    
    >>>[(1, 2), (3, 4), (5, 6)]
    

    【讨论】:

    • 这个解决方案被低估了。没有外部方法和管理赔率的情况。先生,为你加油。
    【解决方案8】:

    我希望这将是一种更优雅的方式。

    a = [1,2,3,4,5,6]
    zip(a[::2], a[1::2])
    
    [(1, 2), (3, 4), (5, 6)]
    

    【讨论】:

    • 注意长度奇数的列表!将省略最后一个元素
    • 漂亮,我完全同意你的看法。简短甜美优雅。我确实有一些问题,因为我对某些 python 库有点陌生。邮编就是其中之一。首先在 a[::2] - 如果我理解正确,这将为从列表中的第一个值开始的每次迭代添加 2 个空格。 1,3,5等现在,在 [1::2] -
    • 现在,在 a[1::2] - 这将从第一个值 1+1 = 2 中添加 +1。然后为所有其他迭代添加 +2。这是正确的还是我错过了什么?
    • 对于奇数长度使用from itertools import zip_longest。它将返回[(1, 2), (3, 4), (5, 6), (7, None)]
    【解决方案9】:

    如果您对性能感兴趣,我做了一个小型基准测试(使用我的库 simple_benchmark)来比较解决方案的性能,并包含了我的一个包中的一个函数:iteration_utilities.grouper

    from iteration_utilities import grouper
    import matplotlib as mpl
    from simple_benchmark import BenchmarkBuilder
    
    bench = BenchmarkBuilder()
    
    @bench.add_function()
    def Johnsyweb(l):
        def pairwise(iterable):
            "s -> (s0, s1), (s2, s3), (s4, s5), ..."
            a = iter(iterable)
            return zip(a, a)
    
        for x, y in pairwise(l):
            pass
    
    @bench.add_function()
    def Margus(data):
        for i, k in zip(data[0::2], data[1::2]):
            pass
    
    @bench.add_function()
    def pyanon(l):
        list(zip(l,l[1:]))[::2]
    
    @bench.add_function()
    def taskinoor(l):
        for i in range(0, len(l), 2):
            l[i], l[i+1]
    
    @bench.add_function()
    def mic_e(it):
        def pairwise(it):
            it = iter(it)
            while True:
                try:
                    yield next(it), next(it)
                except StopIteration:
                    return
    
        for a, b in pairwise(it):
            pass
    
    @bench.add_function()
    def MSeifert(it):
        for item1, item2 in grouper(it, 2):
            pass
    
    bench.use_random_lists_as_arguments(sizes=[2**i for i in range(1, 20)])
    benchmark_result = bench.run()
    mpl.rcParams['figure.figsize'] = (8, 10)
    benchmark_result.plot_both(relative_to=MSeifert)
    

    因此,如果您想要没有外部依赖的最快解决方案,您可能应该只使用 Johnysweb 提供的方法(在撰写本文时,这是最受支持和接受的答案)。

    如果您不介意额外的依赖,那么来自iteration_utilitiesgrouper 可能会快一点。

    其他想法

    有些方法有一些限制,这里没有讨论。

    例如,一些解决方案仅适用于序列(即列表、字符串等),例如 Margus/pyanon/taskinoor 解决方案使用索引,而其他解决方案适用于任何可迭代(即序列和 生成器、迭代器),例如 Johnysweb/mic_e/my Solutions。

    然后,Johnysweb 还提供了一个适用于 2 以外的其他尺寸的解决方案,而其他答案则不适用(好吧,iteration_utilities.grouper 还允许将元素数量设置为“组”)。

    还有一个问题,如果列表中有奇数个元素会发生什么。是否应该取消剩余的项目?是否应该填充列表以使其大小均匀?剩余的物品是否应该作为单件退回?另一个答案没有直接解决这一点,但是如果我没有忽略任何事情,他们都会遵循应该取消剩余项目的方法(taskinoors 答案除外 - 这实际上会引发异常)。

    使用grouper,您可以决定要做什么:

    >>> from iteration_utilities import grouper
    
    >>> list(grouper([1, 2, 3], 2))  # as single
    [(1, 2), (3,)]
    
    >>> list(grouper([1, 2, 3], 2, truncate=True))  # ignored
    [(1, 2)]
    
    >>> list(grouper([1, 2, 3], 2, fillvalue=None))  # padded
    [(1, 2), (3, None)]
    

    【讨论】:

      【解决方案10】:

      一种简单的方法:

      [(a[i],a[i+1]) for i in range(0,len(a),2)]
      

      如果你的数组是 a 并且你想成对地迭代它,这很有用。 要迭代三元组或更多,只需更改“范围”步骤命令,例如:

      [(a[i],a[i+1],a[i+2]) for i in range(0,len(a),3)]
      

      (如果你的数组长度和步长不合适,你必须处理多余的值)

      【讨论】:

        【解决方案11】:

        同时使用zipiter 命令:

        我发现这个使用iter 的解决方案非常优雅:

        it = iter(l)
        list(zip(it, it))
        # [(1, 2), (3, 4), (5, 6)]
        

        我在Python 3 zip documentation 中找到的。

        it = iter(l)
        print(*(f'{u} + {v} = {u+v}' for u, v in zip(it, it)), sep='\n')
        
        # 1 + 2 = 3
        # 3 + 4 = 7
        # 5 + 6 = 11
        

        一次泛化为N 元素:

        N = 2
        list(zip(*([iter(l)] * N)))
        # [(1, 2), (3, 4), (5, 6)]
        

        【讨论】:

          【解决方案12】:

          使用打字,以便您可以使用mypy静态分析工具验证数据:

          from typing import Iterator, Any, Iterable, TypeVar, Tuple
          
          T_ = TypeVar('T_')
          Pairs_Iter = Iterator[Tuple[T_, T_]]
          
          def legs(iterable: Iterator[T_]) -> Pairs_Iter:
              begin = next(iterable)
              for end in iterable:
                  yield begin, end
                  begin = end
          

          【讨论】:

            【解决方案13】:

            我需要将列表除以一个数字并像这样固定。

            l = [1,2,3,4,5,6]
            
            def divideByN(data, n):
                    return [data[i*n : (i+1)*n] for i in range(len(data)//n)]  
            
            >>> print(divideByN(l,2))
            [[1, 2], [3, 4], [5, 6]]
            
            >>> print(divideByN(l,3))
            [[1, 2, 3], [4, 5, 6]]
            

            【讨论】:

              【解决方案14】:

              您可以使用more_itertools 包。

              import more_itertools
              
              lst = range(1, 7)
              for i, j in more_itertools.chunked(lst, 2):
                  print(f'{i} + {j} = {i+j}')
              

              【讨论】:

                【解决方案15】:

                这个问题的标题具有误导性,您似乎在寻找连续的配对,但如果您想遍历所有可能配对的集合,那么这将起作用:

                for i,v in enumerate(items[:-1]):
                        for u in items[i+1:]:
                

                【讨论】:

                  【解决方案16】:

                  对于任何可能有帮助的人,这里有一个类似问题的解决方案,但有重叠对(而不是互斥对)。

                  来自 Python itertools documentation

                  from itertools import izip
                  
                  def pairwise(iterable):
                      "s -> (s0,s1), (s1,s2), (s2, s3), ..."
                      a, b = tee(iterable)
                      next(b, None)
                      return izip(a, b)
                  

                  或者,更一般地说:

                  from itertools import izip
                  
                  def groupwise(iterable, n=2):
                      "s -> (s0,s1,...,sn-1), (s1,s2,...,sn), (s2,s3,...,sn+1), ..."
                      t = tee(iterable, n)
                      for i in range(1, n):
                          for j in range(0, i):
                              next(t[i], None)
                      return izip(*t)
                  

                  【讨论】:

                    【解决方案17】:

                    在这里,我们可以有 alt_elem 方法,它可以适合您的 for 循环。

                    def alt_elem(list, index=2):
                        for i, elem in enumerate(list, start=1):
                            if not i % index:
                               yield tuple(list[i-index:i])
                    
                    
                    a = range(10)
                    for index in [2, 3, 4]:
                        print("With index: {0}".format(index))
                        for i in alt_elem(a, index):
                           print(i)
                    

                    输出:

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

                    注意:考虑到在 func 中执行的操作,上述解决方案可能效率不高。

                    【讨论】:

                      【解决方案18】:

                      认为这是分享我对 n>2 的概括的好地方,这只是一个可迭代的滑动窗口:

                      def sliding_window(iterable, n):
                          its = [ itertools.islice(iter, i, None) 
                                  for i, iter
                                  in enumerate(itertools.tee(iterable, n)) ]                               
                      
                          return itertools.izip(*its)
                      

                      【讨论】:

                        【解决方案19】:
                        for (i, k) in zip(l[::2], l[1::2]):
                            print i, "+", k, "=", i+k
                        

                        zip(*iterable) 返回一个元组,其中包含每个可迭代对象的下一个元素。

                        l[::2] 返回列表的第 1 个、第 3 个、第 5 个等元素:第一个冒号表示切片从开头开始,因为它后面没有数字,第二个冒号只有在需要时才需要一个“切片中的步骤”(在本例中为 2)。

                        l[1::2] 做同样的事情,但从列表的第二个元素开始,因此它返回 original 列表的第二个、第四个、第六个等元素。

                        【讨论】:

                        • 这个答案马格斯两年前就已经给出了。 *.com/questions/5389507/…
                        • 1 用于解释 [number::number] 语法的工作原理。对不经常使用python的人有帮助
                        【解决方案20】:
                        >>> l = [1,2,3,4,5,6]
                        
                        >>> zip(l,l[1:])
                        [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6)]
                        
                        >>> zip(l,l[1:])[::2]
                        [(1, 2), (3, 4), (5, 6)]
                        
                        >>> [a+b for a,b in zip(l,l[1:])[::2]]
                        [3, 7, 11]
                        
                        >>> ["%d + %d = %d" % (a,b,a+b) for a,b in zip(l,l[1:])[::2]]
                        ['1 + 2 = 3', '3 + 4 = 7', '5 + 6 = 11']
                        

                        【讨论】:

                        • @HamidRohani zip 在 Python 3 中返回一个 zip 对象,该对象不可下标。需要先转换成序列(listtuple等),但是“不工作”有点牵强。
                        【解决方案21】:

                        你需要 2 个元素的元组,所以

                        data = [1,2,3,4,5,6]
                        for i,k in zip(data[0::2], data[1::2]):
                            print str(i), '+', str(k), '=', str(i+k)
                        

                        地点:

                        • data[0::2] 表示创建 (index % 2 == 0) 的元素的子集集合
                        • zip(x,y) 从 x 和 y 集合相同的索引元素创建一个元组集合。

                        【讨论】:

                        • 如果需要两个以上的元素,这也可以扩展。例如for i, j, k in zip(data[0::3], data[1::3], data[2::3]):
                        • 比引入导入和定义函数要干净得多!
                        • @kmarsh:但这仅适用于序列,该函数适用于任何可迭代;这使用 O(N) 额外空间,函数不使用;另一方面,这通常更快。有充分的理由选择其中一个;害怕import 不是其中之一。
                        • @abarnert itertools.islice 救援:for i,k in zip(islice(data, 0, None, 2), islice(data, 1, None, 2):。而且,如果您担心“不遍历可迭代对象的最后一个元素”,请将 zip 替换为 itertools.zip_longest 并使用对您有意义的 fillvalue
                        • 我需要得到s -> (s0, s1), (s1, s2), (s2, s3), ...,我用这个得到了它> for i,k in zip(data[0::1], data[1::1]):
                        【解决方案22】:

                        一个简单的解决方案。

                        l = [1, 2, 3, 4, 5, 6] 对于范围内的 i (0, len(l), 2): 打印 str(l[i]), '+', str(l[i + 1]), '=', str(l[i] + l[i + 1])

                        【讨论】:

                        • 如果您的列表不是偶数,而您只想按原样显示最后一个数字怎么办?
                        • @HansdeJong 没找到你。请多解释一下。
                        • 谢谢。我已经想好怎么做了。问题是,如果您有一个列表中没有偶数数量的数字,则会出现索引错误。尝试解决:除了:
                        • ((l[i], l[i+1])for i in range(0, len(l), 2)) 用于生成器,可以轻松修改更长的元组。