在列表中迭代每两个元素

310

如何编写 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

6
针对重叠的一对元素:https://dev59.com/p2035IYBdhLWcg3wZ_Qt这个问题询问如何在Python中以一对一对的形式迭代一个列表。在迭代时,包括当前元素和下一个元素作为一对,以便可以在每个对之间进行比较或操作。 - user202729
1
下次避免将某些东西命名为'l',因为它很容易与数字1、字母I或竖线混淆。最好将其命名为List或L(如果你想节省空间)。 - Nadu
1
只需使用两个变量的循环: for i,k in zip(range(1,7)[0::2], range(1,7)[1::2]): print str(i), '+', str(k), '=', str(i+k) - Shrm
正如我在下面由@johnysweb选择的答案中提到的那样,现在可以使用海象运算符(3.8+)以简洁的方式来完成这个操作:for i, k in zip(_x := iter(mylist), _x): ... - Alex Just Alex
22个回答

336
你需要一个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()函数的替代品。
所有信用归功于martineaumy questionhis answer,我发现这非常有效,因为它只在列表上迭代一次,并且在此过程中不创建任何不必要的列表。
N.B:这不应与Python自己的itertools documentation中的pairwise recipe混淆,后者产生s -> (s0, s1), (s1, s2), (s2, s3), ...,如评论中@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)

32
不要与itertools中的建议使用的成对函数混淆,该函数返回s -> (s0,s1),(s1,s2),(s2,s3), ... - Lauritz V. Thaulow
1
它做了不同的事情。与具有相同名称的itertools配方函数相比,您的版本仅产生一半数量的对。当然,你的更快... - Sven Marnach
11
小心!使用这些函数会导致您无法迭代可迭代对象的最后一个元素。例如: list(grouped([1,2,3],2))
[(1, 2)] ..而您期望得到的是[(1,2),(3,)]。
- egafni
5
在问题中指定的情况下,拥有“不完整”的元组是没有意义的。如果您想要包含一个不完整的元组,您可以使用izip_longest()代替izip()。例如:list(izip_longest(*[iter([1, 2, 3])]*2, fillvalue=0)) --> [(1, 2), (3, 0)]。希望这可以帮到您。 - johnsyweb
1
但不要与同一文档中的“grouper”配方混淆。了解它的工作原理绝对是值得的——这样您就可以决定如何处理不规则分组(跳过剩余值、使用填充值填充或返回短分组)。 - abarnert
显示剩余9条评论

277

你需要一个包含两个元素的元组,因此

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] 表示创建由索引为偶数的元素组成的子集合集合。
  • zip(x,y) 从 x 和 y 两个集合中,将相同索引位置的元素打包成元组(tuple)的集合。

9
如果需要超过两个元素,这个方法也可以扩展。例如:for i, j, k in zip(data[0::3], data[1::3], data[2::3]): - lifebalance
28
比引入导入和定义函数要干净得多! - kmarsh
20
@kmarsh:但这仅适用于序列,该函数适用于任何可迭代对象;此方法使用了额外的O(N)空间,而该函数则没有;另一方面,这种方法通常更快。选择一种或另一种方法有很好的理由;害怕“import”不是其中之一。 Translated: @kmarsh: 但这仅适用于序列,该函数适用于任何可迭代对象;此方法使用了额外的O(N)空间,而该函数则没有;另一方面,这种方法通常更快。选择一种或另一种方法有很好的理由;害怕“import”不是其中之一。 - abarnert
2
@abarnert,itertools.islice 来拯救:for i,k in zip(islice(data, 0, None, 2), islice(data, 1, None, 2):。如果你担心“不迭代可迭代对象的最后元素”,可以将 zip 替换为 itertools.zip_longest 并使用一个对你有意义的 fillvalue - Escape0707
我需要获取s -> (s0, s1), (s1, s2), (s2, s3), ...,并使用以下代码实现:for i,k in zip(data[0::1], data[1::1]): - Kapil Marwaha
这会创建两个新的列表吗? - Enzo Dtz

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

11
在Python 3中,zip函数返回一个不可下标访问的zip对象。需要先将其转换为序列(例如listtuple等),但是说“不起作用”可能有些过分。 - vaultah
只需将 zip 对象转换为元组列表,如下所示:output = list(zip_object) - Rob Irwin

78
一个简单的解决方案。
l = [1, 2, 3, 4, 5, 6]
for i in range(0, len(l), 2): print str(l[i]), '+', str(l[i + 1]), '=', str(l[i] + l[i + 1])
一个简单的解决方案。

4
如果你的列表长度是奇数,而你只想展示最后一个数字,不做任何更改,该怎么办? - Hans de Jong
@HansdeJong 没有理解你的意思,请再详细解释一下。 - taskinoor
2
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Hans de Jong
2
或者对于生成器,可以使用((l[i], l[i+1])for i in range(0, len(l), 2)),并且可以轻松地修改为更长的元组。 - Basel Shishani

59

虽然使用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
此解决方案允许将元组大小推广到大于2。 - guilloptero
1
如果 it 只是一个迭代器而不是可迭代对象,此解决方案同样适用。其他解决方案似乎依赖于在序列中创建两个独立的迭代器的可能性。 - skyking
在看到这个答案之前,我在https://dev59.com/GWQn5IYBdhLWcg3wcm97#16815056上发现了这种方法。它比处理zip()更简洁、更容易。 - m3nda
3
我喜欢它可以避免三倍的内存使用,就像被接受的答案一样。 - Kentzo
@sidney 谢谢你的提示,但实际上这只发生在 Python 3.7+ 版本。我会相应地调整回复。 - mic_e
显示剩余2条评论

43

我希望这将是更加优雅的做法。

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

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

2
注意列表长度为奇数的情况!将省略最后一个元素。 - lode
太好了,我完全同意你的观点。简短、简洁而优雅。由于我对Python的一些库还比较陌生,所以我有一些问题。其中之一就是Zip。首先,在a [:: 2]上-如果我理解正确,这将在列表中从第一个值开始每次迭代添加2个空格。1,3,5等。现在,在a [1 :: 2]上- - Jiraheta
现在,在a[1::2]中 - 这将从第一个值添加+1 1+1 = 2。然后为所有其他迭代添加+2。这正确吗,还是我漏掉了什么? - Jiraheta
2
对于奇数长度,请使用 from itertools import zip_longest。它将返回 [(1, 2), (3, 4), (5, 6), (7, None)] - egvo

33
如果您对性能有兴趣,我做了一个小基准测试(使用我的库 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解决方案。

然后,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)]

20

同时使用zipiter命令:

我认为使用iter的这种解决方案相当优雅:

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

我在 Python 3 zip 文档 中找到了它。

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

11
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] 做同样的事情,但是从列表的第二个元素开始,因此它返回原始列表的第2、第4、第6等元素。

4
这个答案已经在两年前被Margus给出。https://dev59.com/MW435IYBdhLWcg3wfQF9 - cababunga
1
  1. 解释 [number::number] 语法的工作原理。对于不经常使用 Python 的人很有帮助。
- Alby

9

通过解包:

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

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

哇!为什么我想不到呢 :) 你只需要处理没有绝对配对(奇数项)的情况。 - Saurav Kumar
很好,聪明 :) - a3k

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接