成对循环Python'for'循环


76

有没有一种很好的Pythonic方式可以遍历列表,重新调整一对元素?最后一个元素应与第一个元素配对。

因此,例如,如果我有列表[1、2、3],则希望获得以下对:

  • 1-2
  • 2-3
  • 3-1


@BhargavRao应该关闭该副本作为该副本的副本
Barry

@Barry这里的答案很好,因此我没有锤子。我需要一个人来保证反向欺骗更好。谢谢(我也在那里添加了评论)
巴尔加夫饶

Answers:


112

成对访问列表的Python方式是:zip(L, L[1:])。要将最后一项连接到第一项:

>>> L = [1, 2, 3]
>>> zip(L, L[1:] + L[:1])
[(1, 2), (2, 3), (3, 1)]

32
如果列表很大,这可能是不可取的
亚伦·霍尔

10
@AaronHall正确。但是请记住:“过早的优化是万恶之源”。如果内存有问题或要支持任意迭代(不只是序列),itertools则可以使用解决方案。
jfs

1
哦,我知道您正在链接到另一个答案。不幸的是,您链接到的答案的宽度不能扩展到2个以上。但是,在下面我确实有直接itertools类似您的建议。
亚伦·霍尔

1
@AaronHall在Python 3和2.x中仍然存在问题吗?3 zip(除其他外)非常有效。
很少'哪里有莫妮卡'Needy'Apr

5
@SeldomNeedy Python 2中的zip(*iterables)像3倍的list(zip(*iterables))-这就是为什么我建议导入izip as zip我的回答对Python的2 - stackoverflow.com/a/36918720/541136 - JF却呈Python 2米中的zip这个答案,哪些化合物一长串不必要的创作问题在记忆中。JF的答案使用的内存比列表所需的内存多200%(每项8个字节),以及每个元组的内存(每个元组64个字节的内存相对较大,请参见stackoverflow.com/a/30316760/541136),多800%,因此总计增加1000%(假设懒惰的评估。)
亚伦·霍尔

49

我会用dequewithzip来实现这一点。

>>> from collections import deque
>>>
>>> l = [1,2,3]
>>> d = deque(l)
>>> d.rotate(-1)
>>> zip(l, d)
[(1, 2), (2, 3), (3, 1)]

双端队列旋转得非常快,但也占用大量内存。
亚伦·霍尔

1
当然。对于小型解决方案,这是快速而干净的。它还演示了deque.rotate这可能非常有用。
gddc

45

pairwise将对itertools文档中的配方进行一些修改:

def pairwise_circle(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ... (s<last>,s0)"
    a, b = itertools.tee(iterable)
    first_value = next(b, None)
    return itertools.zip_longest(a, b,fillvalue=first_value)

这只会保留对第一个值的引用,并且当第二个迭代器用尽时, zip_longest将使用第一个值填充最后一个位置。

(还请注意,它可与生成器之类的迭代器以及列表/元组之类的可迭代项一起使用。)

请注意,@ Barry的解决方案与此非常相似,但是在我看来,它有点容易理解,并且更容易扩展到一个元素之外。


@GrijeshChauhan您看过@Barry的解决方案吗?
塔德格·麦克唐纳·詹森

逻辑不能扩展到超过第二个元素。
亚伦·霍尔

@AaronHall,如果我有任何进一步扩展的理由,请将其包装在中cycle
塔德麦当劳-詹森

@ TadhgMcDonald-Jensen刚检查过-很高兴学习另一个技巧。但对我来说还是itertools.zip_longest(a, b, fillvalue=first_value) 较为明显然后izip(a, chain(b, (first,))) -不需要作出额外的元组(first, )
Grijesh肖汉

38

我会itertools.cycle搭配zip

import itertools

def circular_pairwise(l):
    second = itertools.cycle(l)
    next(second)
    return zip(l, second)

cycle 返回一个迭代器,该迭代器按顺序产生其参数的值,从最后一个值循环到第一个值。

我们跳过第一个值,所以它从位置开始 1(而不是0)。

接下来,我们 zip其与原始的不变列表一起使用。 zip很好,因为它在任何参数可迭代项用尽时都会停止。

这样可以避免创建任何中间列表: cycle保留对原始文档的引用,但不进行复制。 zip以相同的方式操作。

重要的是要注意,如果输入是iterator,例如afile,(或amapzipin),因为在某个地方(通过next(second))前进将自动在其他所有地方推进迭代器。使用可以轻松解决此问题itertools.tee,它会在原始可迭代对象上生成两个独立运行的迭代器:

def circular_pairwise(it):
    first, snd = itertools.tee(it)
    second = itertools.cycle(snd)
    next(second)
    return zip(first, second)

tee 可以使用大量的额外存储空间,例如,如果一个返回的迭代器在触摸另一个迭代器之前就用完了,但是由于我们只有一步的差异,因此额外的存储空间很小。


1
@PythonNut谢谢,这就是我所钟爱的itertools,以及一般的函数式编程,它允许非常简洁的代码,同时仍然易于阅读。
RoadieRich '16

如果输入是迭代器,它将失败。
jfs

3
cycle文件明确指出,他们保存每个项目的副本输入迭代-这样你就不会在上榜的副本保存任何空间。其他一些答案可以使用chain或来避免此问题fillvalue
2016年

28

有更有效的方法(不构建临时列表),但是我认为这是最简洁的方法:

> l = [1,2,3]
> zip(l, (l+l)[1:])
[(1, 2), (2, 3), (3, 1)]

7
如果列表真的很长,这就是一个问题。它会创建另一个(l+l)两倍大小的列表。
nigel222 '16

当然; 我在此答案中寻求最大程度的简洁。
chepner '16

3
“如果名单真的很长,这是一个问题。” 创建另一个列表(长或短)本身并不是问题。这只是要注意的事实。
brian_o

22

我将使用列表理解,并利用l[-1]最后一个事实。

>>> l = [1,2,3]
>>> [(l[i-1],l[i]) for i in range(len(l))]
[(3, 1), (1, 2), (2, 3)]

您不需要那种临时列表。


2
xrange在Python 2中,您需要避免使用临时列表。创建从1到n的数字列表与创建带有n项目的列表的浅表副本一样昂贵。
chepner '16

5
我认为与您的回答太相似了,这也可能是 [(l[i-1], it) for i, it in enumerate(l)]。您可能应该注意,这两种方法都将最后请求的元素放在第一位。
porglezomp '16

6
@chepner:我倾向于未指定时呈python3;)
ATN

1
porglezomp的权利,列表的第一个元素应该是(1,2)而不是(3,1)。
亚伦·霍尔

22

成对循环Python'for'循环

如果您喜欢接受的答案,

zip(L, L[1:] + L[:1])

您可以使用以下语义相同的代码来获得更多的存储空间itertools

from itertools import islice, chain #, izip as zip # uncomment if Python 2

而且这几乎无法实现原始列表之外的内存中的任何内容(假设列表相对较大):

zip(l, chain(islice(l, 1, None), islice(l, None, 1)))

要使用,只需消耗(例如,带有列表):

>>> list(zip(l, chain(islice(l, 1, None), islice(l, None, 1))))
[(1, 2), (2, 3), (3, 1)]

可以将其扩展为任何宽度:

def cyclical_window(l, width=2):
    return zip(*[chain(islice(l, i, None), islice(l, None, i)) for i in range(width)])

和用法:

>>> l = [1, 2, 3, 4, 5]
>>> cyclical_window(l)
<itertools.izip object at 0x112E7D28>
>>> list(cyclical_window(l))
[(1, 2), (2, 3), (3, 4), (4, 5), (5, 1)]
>>> list(cyclical_window(l, 4))
[(1, 2, 3, 4), (2, 3, 4, 5), (3, 4, 5, 1), (4, 5, 1, 2), (5, 1, 2, 3)]

无限代与itertools.teecycle

您还可以tee用来避免创建冗余循环对象:

from itertools import cycle, tee
ic1, ic2 = tee(cycle(l))
next(ic2)    # must still queue up the next item

现在:

>>> [(next(ic1), next(ic2)) for _ in range(10)]
[(1, 2), (2, 3), (3, 1), (1, 2), (2, 3), (3, 1), (1, 2), (2, 3), (3, 1), (1, 2)]

这是难以置信的高效率,的预期使用iternext和优雅的使用cycleteezip

不要cycle直接传递给list除非您已保存工作并有足够的时间使计算机在内存最大时停止运行否则请它-如果幸运的话,一段时间后,操作系统会在计算机崩溃之前终止进程。 。

纯Python内置函数

最后,没有标准的lib导入,但这仅适用于原始列表的长度(否则为IndexError)。

>>> [(l[i], l[i - len(l) + 1]) for i in range(len(l))]
[(1, 2), (2, 3), (3, 1)]

您可以使用模来继续:

>>> len_l = len(l)
>>> [(l[i % len_l], l[(i + 1) % len_l]) for i in range(10)]
[(1, 2), (2, 3), (3, 1), (1, 2), (2, 3), (3, 1), (1, 2), (2, 3), (3, 1), (1, 2)]

您可能要提及的是,除非您有某种终点(在这种情况下为range(10)),否则循环永远不会结束。
塔德格·麦克唐纳·詹森

4
确保它们会结束,只需将它们传递到列表中,当内存不足时它们就会结束。:D
亚伦大厅

19

令人惊讶的是有多少种不同的方法可以解决这个问题。

还有一个 您可以使用该pairwise配方,但是可以使用zip代替bchain而是使用已弹出的第一个元素。不需要cycle的时候,我们只需要一个额外的价值:

from itertools import chain, izip, tee

def pairwise_circle(iterable):
    a, b = tee(iterable)
    first = next(b, None)
    return izip(a, chain(b, (first,)))

11

我喜欢不修改原始列表并且不将列表复制到临时存储的解决方案:

def circular(a_list):
    for index in range(len(a_list) - 1):
        yield a_list[index], a_list[index + 1]
    yield a_list[-1], a_list[0]

for x in circular([1, 2, 3]):
    print x

输出:

(1, 2)
(2, 3)
(3, 1)

我可以想象这被用于一些非常大的内存数据中。


1
这是最好的解决方案。高效,易于阅读和可重复使用。
杰克·艾德利

8

即使该列表l已占用系统的大部分内存,该列表也将起作用。(如果可以保证这种情况是不可能的,那么chepner发布的zip文件就可以了)

l.append( l[0] )
for i in range( len(l)-1):
   pair = l[i],l[i+1]
   # stuff involving pair
del l[-1] 

或更概括地说(适用于任何偏移量,nl[ (i+n)%len(l) ]

for i in range( len(l)):
   pair = l[i], l[ (i+1)%len(l) ]
   # stuff

如果您使用的是具有相当快的模除的系统(即,不是一些豆豆嵌入式系统)。

似乎经常有人认为,用整数下标索引列表是非Python的,最好避免。为什么?


2
您的最后一句话应该是一个单独的问题。答案是来自C,C ++或Java的新Python程序员for i in range(len(a_list)): print a_list[i]代替编写for x in a_list: print x
David Cullen

用整数索引为列表建立索引是非Python的,因为如果您只需要该元素,那么for el in iterable:这是可行的方法。如果还需要索引,enumerate()则是方法。因此,应避免手动索引。
rubik '16

7

这是我的解决方案,对我来说看起来像Pythonic:

l = [1,2,3]

for n,v in enumerate(l):
    try:
        print(v,l[n+1])
    except IndexError:
        print(v,l[0])

印刷品:

1 2
2 3
3 1

生成器功能版本:

def f(iterable):
    for n,v in enumerate(iterable):
        try:
            yield(v,iterable[n+1])
        except IndexError:
            yield(v,iterable[0])

>>> list(f([1,2,3]))
[(1, 2), (2, 3), (3, 1)]

6

这个怎么样?

li = li+[li[0]]
pairwise = [(li[i],li[i+1]) for i in range(len(li)-1)]

5
from itertools import izip, chain, islice

itr = izip(l, chain(islice(l, 1, None), islice(l, 1)))

(如上,@ jf-sebastian的“ zip”答案,但使用itertools。)

注意:编辑来自@ 200_success,有帮助。以前是:

itr = izip(l, chain(l[1:], l[:1]))

1
如果要使用itertools,那么@RoadieRich的解决方案更好,因为它避免了复制和切片。
200_success,2016年

好点-由于某种原因,我认为该切片是“写时复制”。编辑为使用islice。
尚克

3

再试一次

>>> L = [1,2,3]
>>> zip(L,L[1:]) + [(L[-1],L[0])]
[(1, 2), (2, 3), (3, 1)]

3

如果您不想消耗太多内存,可以尝试我的解决方案:

[(l[i], l[(i+1) % len(l)]) for i, v in enumerate(l)]

它稍微慢一点,但是消耗更少的内存。


1

L = [1、2、3] a = a中的i的zip(L,L [1:] + L [:1]):b = list(i)打印b


欢迎来到StackOverflow!请考虑在代码中添加一些说明。谢谢!
Aurasphere '16

1

在以后的版本中Python 3.10 release schedule,新pairwise功能提供了一种创建连续元素的滑动对的方法:

from itertools import pairwise

# l = [1, 2, 3]
list(pairwise(l + l[:1]))
# [(1, 2), (2, 3), (3, 1)]

或者只是简单地说,pairwise(l + l[:1])如果您不需要将结果作为list


请注意,我们pairwise在列表上附加了其head(l + l[:1]),因此滚动对是圆形的(即,因此我们也包括该(3, 1)对):

list(pairwise(l)) # [(1, 2), (2, 3)]
l + l[:1] # [1, 2, 3, 1]

-1

看来组合可以胜任这项工作。

from itertools import combinations
x=combinations([1,2,3],2)

这将产生一个发电机。然后可以这样迭代

for i in x:
  print i

结果看起来像

(1, 2)
(1, 3)
(2, 3)

您的结果与OP的示例不符。
David Cullen

真?您假设顺序很重要...我只认为组合很重要。这意味着这个答案是正确的。
Hevon Gordon

1
考虑到更长的列表,考虑[1,2,3,4,5]到结果[(1,2), (2,3), (3,4), (4,5), (5,1)]不是列表中的组合,而只是组合。
塔德麦当劳-詹森
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.