成对循环Python for'循环
有一个很好的Pythonic方式来循环一个列表,重新调整一对元素? 最后一个元素应该与第一个配对。
举个例子,如果我有这个列表[1,2,3],我想获得以下对:
- 1 – 2
- 2 – 3
- 3 – 1
Pythonic以成对方式访问列表的方式是: zip(L, L[1:])
。 要连接最后一个项目到第一个:
>>> L = [1, 2, 3] >>> zip(L, L[1:] + L[:1]) [(1, 2), (2, 3), (3, 1)]
我会用zip
来实现这个。
>>> from collections import deque >>> >>> l = [1,2,3] >>> d = deque(l) >>> d.rotate(-1) >>> zip(l, d) [(1, 2), (2, 3), (3, 1)]
我将使用itertools
文档中的pairwise
配方稍作修改:
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的解决scheme与此非常相似,但在我看来更容易理解,并且更容易超越一个元素。
我会配对zip
与itertools.cycle
:
import itertools def circular_pairwise(l): second = itertools.cycle(l) next(second) return zip(l, second)
cycle
返回一个迭代器,按顺序生成其参数的值,从最后一个值循环到第一个值。
我们跳过第一个值,所以它从位置1
(而不是0
)开始。
接下来,我们用原始的unmutated列表zip
它。 zip
是好的,因为它的任何参数iterables耗尽时会停止。
这样做避免了创build任何中间列表: cycle
保留对原始的引用,但不复制它。 zip
以相同的方式操作。
需要注意的是,如果input是iterator
(比如file
)(或者python-3中的map
或zip
),那么这将会中断,因为在一个地方(通过next(second)
)前进会自动将迭代器推进到所有其他人。 使用itertools.tee
很容易解决这个问题,它在原始的迭代器上生成两个独立运行的迭代器:
def circular_pairwise(it): first, snd = itertools.tee(it) second = itertools.cycle(snd) next(second) return zip(first, second)
例如,如果其中一个返回的迭代器在被触摸之前被用完,但是由于我们只有一个步骤差异,额外的存储是最小的。
有更有效的方法(不build立临时列表),但我认为这是最简洁的:
> l = [1,2,3] > zip(l, (l+l)[1:]) [(1, 2), (2, 3), (3, 1)]
我将使用列表理解,并利用l[-1]
是最后一个元素的事实。
>>> l = [1,2,3] >>> [(l[i-1],l[i]) for i in range(len(l))] [(3, 1), (1, 2), (2, 3)]
你不需要这样的临时列表。
成对循环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.tee
无限cycle
您也可以使用tee
来避免创build冗余循环对象:
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)]
这是非常高效的,对next
cycle
, tee
和zip
优雅使用的预期使用。
不要把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)]
惊人的有多less种不同的方式来解决这个问题。
这里还有一个。 你可以使用pairwise
配方,而不是用b
压缩,用你已经popup的第一个元素链接起来。 当我们只需要一个额外的值时,不需要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,)))
我喜欢解决scheme不会修改原始列表,也不会将列表复制到临时存储:
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)
我可以想象这被用于一些非常大的内存数据。
即使列表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]
或更普遍地(适用于任何偏移量n
ie l[ (i+n)%len(l) ]
)
for i in range( len(l)): pair = l[i], l[ (i+1)%len(l) ] # stuff
前提是你的系统具有快速的模块划分(即不是一些embedded式系统)。
似乎有一个常常认为用整数下标索引列表是非pythonic,并最好避免。 为什么?
这是我的解决scheme,它看起来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
生成器function版本:
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)]
这个怎么样?
li = li+[li[0]] pairwise = [(li[i],li[i+1]) for i in range(len(li)-1)]
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]))
只是另一个尝试
>>> L = [1,2,3] >>> zip(L,L[1:]) + [(L[-1],L[0])] [(1, 2), (2, 3), (3, 1)]
如果你不想消耗太多的内存,你可以试试我的解决scheme:
[(l[i], l[(i+1) % len(l)]) for i, v in enumerate(l)]
速度稍慢,但消耗的内存更less。
在a:b = list(i)中,L = [1,2,3] a = zip(L,L [1:] + L [:1])print b
这似乎是组合会做这项工作。
from itertools import combinations x=combinations([1,2,3],2)
这将产生一个发电机。 这可以像这样迭代
for i in x: print i
结果会看起来像
(1, 2) (1, 3) (2, 3)