成对循环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与此非常相似,但在我看来更容易理解,并且更容易超越一个元素。

我会配对zipitertools.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中的mapzip ),那么这将会中断,因为在一个地方(通过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 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)] 

惊人的有多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)