Python中类似于Ruby的#each_cons方法的等效方法是什么?

20
有没有Python中等价于Ruby的#each_cons的功能?
在Ruby中,你可以这样做:
array = [1,2,3,4]
array.each_cons(2).to_a
=> [[1,2],[2,3],[3,4]]

你为什么需要这样做呢?我只是好奇 ;) - Blender
我正在对列表执行移动平均。在Ruby中,我会使用#each_cons来实现它,所以我想知道Pythonistas如何做到这一点。 - maxhawkins
1
要想真正实现与 Ruby 的 each_cons 等效的功能,请使用 toolz.itertoolz.sliding_window() - Elias Dorneles
9个回答

23

我认为并没有一个现成的工具,我查看了内置模块itertools,这也是我预期会有该功能的地方。不过你可以自己创建一个:

def each_cons(xs, n):
    return [xs[i:i+n] for i in range(len(xs)-n+1)]

2
我的代码适用于 2,但是在修改为任意 cons 后,它看起来和你的一样。 - Blender
通常,适用于可迭代对象的解决方案更可取,但是对于序列来说也可以。挑剔一点:x 是一个集合,所以最好使用 xs(命名非常重要,即使在示例中也是如此。我甚至会说在示例中它更加重要 :))。 - tokland

11

如果你需要这样的功能,itertools 就是你应该查看的模块:

from itertools import tee, izip

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

那么:

>>> list(pairwise([1, 2, 3, 4]))
[(1, 2), (2, 3), (3, 4)]

对于更为通用的解决方案,可以考虑这个:

def split_subsequences(iterable, length=2, overlap=0):
    it = iter(iterable)
    results = list(itertools.islice(it, length))
    while len(results) == length:
        yield results
        results = results[length - overlap:]
        results.extend(itertools.islice(it, length - overlap))
    if results:
        yield results

这允许任意长度的子序列和任意重叠。用法:

>> list(split_subsequences([1, 2, 3, 4], length=2))
[[1, 2], [3, 4]]
>> list(split_subsequences([1, 2, 3, 4], length=2, overlap=1))
[[1, 2], [2, 3], [3, 4], [4]]

Python 3:http://docs.python.org/py3k/library/itertools.html - Py2:http://docs.python.org/library/itertools.html - Ned Deily
这里的通用解决方案在序列长度不足时不像each_cons那样表现(each_cons返回nil)。在snipsnipsnip的答案中的实现在这方面似乎更为适当。 - Elias Dorneles
list(split_subsequences([1, 2, 3, 4, 5, 6], length=3, overlap=1)) 应该返回 [[1,2,3],[2,3,4],[3,4,5],[4,5,6]] 而不是 [[1, 2, 3], [3, 4, 5], [5, 6]] - Eric Duminil

6

我对列表的解决方案(Python2):

import itertools
def each_cons(xs, n):
    return itertools.izip(*(xs[i:] for i in xrange(n)))

编辑: 在Python 3中,itertools.izip已经不存在了,所以你需要使用普通的zip函数:

def each_cons(xs, n):
    return zip(*(xs[i:] for i in range(n)))

如果参数xs是一个生成器,那么这个解决方案就不起作用了,而且由于切片xs[i:]的存在,它并不是真正的惰性。 - Elias Dorneles
你说得对,我可以写成islice(xs, i, None)而不是xs[i:]。但出于以下原因,我更喜欢后者:a) 这个问题是关于列表的。b) 我大多数情况下都使用each_cons来处理列表。c) 如果xs是一个列表,切片后的列表将具有共享内存,因此可能比惰性处理更节省内存。 - snipsnipsnip
是的,你说得对,我知道。=)只是Ruby的#each_cons适用于所有情况,所以我想指出这一点。我已经为那些需要的人发布了一个懒惰的解决方案。 - Elias Dorneles
一度我删除了我的回答,因为您的方法使用islice可以适用于xrange()。不过它在普通生成器上仍然失败了。这段小代码非常漂亮,再次感谢分享。 - Elias Dorneles

5

Python肯定可以做到这一点。如果您不想那么急切地做,可以使用itertool的islice和izip。另外,要记住,普通的切片会创建一个副本,所以如果内存使用很重要,您还应该考虑使用itertool的等效方法。

each_cons = lambda l: zip(l[:-1], l[1:])


5

更新:忽略下面我的回答,直接使用toolz.itertoolz.sliding_window()即可,它会做正确的事情。


如果想要实现真正的懒加载,并且在序列/生成器长度不足时仍保留 Ruby 的 each_cons 行为,请使用 toolz.itertoolz.sliding_window()

import itertools
def each_cons(sequence, n):
    return itertools.izip(*(itertools.islice(g, i, None)
                          for i, g in
                          enumerate(itertools.tee(sequence, n))))

示例:

>>> print(list(each_cons(xrange(5), 2)))
[(0, 1), (1, 2), (2, 3), (3, 4)]
>>> print(list(each_cons(xrange(5), 5)))
[(0, 1, 2, 3, 4)]
>>> print(list(each_cons(xrange(5), 6)))
[]
>>> print(list(each_cons((a for a in xrange(5)), 2)))
[(0, 1), (1, 2), (2, 3), (3, 4)]

请注意,应用于izip参数的元组解包应用于大小为n的元组,结果是itertools.tee(xs, n)(即“窗口大小”),而不是我们要迭代的序列。

5
一句简短的话:
a = [1, 2, 3, 4]

out = [a[i:i + 2] for i in range(len(a) - 1)]

2

接近于@Blender的解决方案,但有一个修复:

最初的回答

a = [1, 2, 3, 4]
n = 2
out = [a[i:i + n] for i in range(len(a) - n + 1)]
# => [[1, 2], [2, 3], [3, 4]]

最初的回答
或者
a = [1, 2, 3, 4]
n = 3
out = [a[i:i + n] for i in range(len(a) - n + 1)]
# => [[1, 2, 3], [2, 3, 4]]

1

Elias的代码的Python 3版本

from itertools import islice, tee

def each_cons(sequence, n):
    return zip(
        *(
            islice(g, i, None)
            for i, g in
            enumerate(tee(sequence, n))
        )
    )

$ ipython

...    

In [2]: a_list = [1, 2, 3, 4, 5]

In [3]: list(each_cons(a_list, 2))
Out[3]: [(1, 2), (2, 3), (3, 4), (4, 5)]

In [4]: list(each_cons(a_list, 3))
Out[4]: [(1, 2, 3), (2, 3, 4), (3, 4, 5)]

In [5]: list(each_cons(a_list, 5))
Out[5]: [(1, 2, 3, 4, 5)]

In [6]: list(each_cons(a_list, 6))
Out[6]: []

0
这是一个使用 collections.deque 实现的例子。它支持任意生成器。
from collections import deque

def each_cons(it, n):
    # convert it to an iterator
    it = iter(it)

    # insert first n items to a list first
    deq = deque()
    for _ in range(n):
        try:
            deq.append(next(it))
        except StopIteration:
            for _ in range(n - len(deq)):
                deq.append(None)
            yield tuple(deq)
            return

    yield tuple(deq)

    # main loop
    while True:
        try:
            val = next(it)
        except StopIteration:
            return
        deq.popleft()
        deq.append(val)
        yield tuple(deq)

使用方法:

list(each_cons([1,2,3,4], 2))
# => [(1, 2), (2, 3), (3, 4)]

# This supports generators
list(each_cons(range(5), 2))
# => [(0, 1), (1, 2), (2, 3), (3, 4)]

list(each_cons([1,2,3,4], 10))
# => [(1, 2, 3, 4, None, None, None, None, None, None)]

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