Python:查找列表元素之间的差异

163
给定一个数字列表,如何找到每个(i)元素与其(i + 1)元素之间的差异?
使用lambda表达式还是列表解析更好呢?
例如:
给定一个列表t=[1,3,6,...],目标是找到一个列表v=[2,3,…],因为3-1=2,6-3=3等。
12个回答

199
>>> t
[1, 3, 6]
>>> [j-i for i, j in zip(t[:-1], t[1:])]  # or use itertools.izip in py2k
[2, 3]

17
如果你需要得到绝对差值,可以使用[abs(j-i) for i,j in zip(t, t[1:])]这个代码。其中,t是一个列表或数组。 - Anil
如果您想使其更有效率:list(itertools.starmap(operator.sub, zip(t[1:], t)))(在导入itertoolsoperator之后)。 - blhsing
7
实际上,只需使用 list(map(operator.sub, t[1:], t[:-1])) 即可。 - blhsing

150

其他答案是正确的,但如果你在进行数字计算工作,你可能需要考虑使用numpy。使用numpy,答案是:

v = numpy.diff(t)

1
非常有帮助!谢谢! np.diff([2,4,9]) 将会是 [2,5] - TravelTrader
4
这个方法比使用 zip 更有效吗? - user760900

43

如果您不想使用numpyzip,您可以使用以下解决方案:

>>> t = [1, 3, 6]
>>> v = [t[i+1]-t[i] for i in range(len(t)-1)]
>>> v
[2, 3]

21

Python 3.10 开始,使用新的 pairwise 函数,可以滑动处理元素对并在其中映射滚动对:

from itertools import pairwise

[y-x for (x, y) in pairwise([1, 3, 6, 7])]
# [2, 3, 1]

中间结果为:

pairwise([1, 3, 6, 7])
# [(1, 3), (3, 6), (6, 7)]

哈哈,我已经在我的通用函数中有类似的东西了。我把我的实现称为“块状器”,但是“pairwise”听起来很好。非常有用。 - SurpriseDog

16

您可以使用 itertools.teezip 来高效地构建结果:

from itertools import tee
# python2 only:
#from itertools import izip as zip

def differences(seq):
    iterable, copied = tee(seq)
    next(copied)
    for x, y in zip(iterable, copied):
        yield y - x

或者使用itertools.islice代替:

from itertools import islice

def differences(seq):
    nexts = islice(seq, 1, None)
    for x, y in zip(seq, nexts):
        yield y - x
您也可以避免使用itertools模块:
def differences(seq):
    iterable = iter(seq)
    prev = next(iterable)
    for element in iterable:
        yield element - prev
        prev = element

如果您不需要存储所有结果并支持无限可迭代对象,则这些解决方案都可以在常量空间中运行。


以下是一些解决方案的微基准测试:

In [12]: L = range(10**6)

In [13]: from collections import deque
In [15]: %timeit deque(differences_tee(L), maxlen=0)
10 loops, best of 3: 122 ms per loop

In [16]: %timeit deque(differences_islice(L), maxlen=0)
10 loops, best of 3: 127 ms per loop

In [17]: %timeit deque(differences_no_it(L), maxlen=0)
10 loops, best of 3: 89.9 ms per loop

而其他提出的解决方案:

In [18]: %timeit [x[1] - x[0] for x in zip(L[1:], L)]
10 loops, best of 3: 163 ms per loop

In [19]: %timeit [L[i+1]-L[i] for i in range(len(L)-1)]
1 loops, best of 3: 395 ms per loop

In [20]: import numpy as np

In [21]: %timeit np.diff(L)
1 loops, best of 3: 479 ms per loop

In [35]: %%timeit
    ...: res = []
    ...: for i in range(len(L) - 1):
    ...:     res.append(L[i+1] - L[i])
    ...: 
1 loops, best of 3: 234 ms per loop

注意:

  • zip(L[1:], L) 等价于 zip(L[1:], L[:-1]),因为 zip 已经在最短的输入上终止了,但它避免了对 L 的整体复制。
  • 通过索引访问单个元素非常缓慢,因为在 Python 中,每个索引访问都是一个方法调用。
  • numpy.diff 很慢,因为它必须先将 list 转换为 ndarray。显然,如果您从一个 ndarray 开始,它将会 快得多

    In [22]: arr = np.array(L)
    
    In [23]: %timeit np.diff(arr)
    100 loops, best of 3: 3.02 ms per loop
    

在第二种解决方案中,使用islice(seq, 1, None)而不是islice(seq, 1, len(seq))可以使其适用于无限迭代器。 - Braham Snyder

8
我建议使用:
v = np.diff(t)

这很简单易懂。

但如果您想让vt的长度相同,则需要:

v = np.diff([t[0]] + t) # for python 3.x

或者

v = np.diff(t + [t[-1]])

须知:这仅适用于列表。

对于NumPy数组

v = np.diff(np.append(t[0], t))

1
不错的回答,不过您也可以使用“prepend”关键字来确保长度相同,请参见下面的答案,我认为这样更整洁。 - ClimateUnboxed

6
使用 Python 3.8+ 中提供的 := 海豹运算符:
>>> t = [1, 3, 6]
>>> prev = t[0]; [-prev + (prev := x) for x in t[1:]]
[2, 3]

4

一种函数式方法:

>>> import operator
>>> a = [1,3,5,7,11,13,17,21]
>>> map(operator.sub, a[1:], a[:-1])
[2, 2, 2, 4, 2, 4, 4]

使用生成器:
>>> import operator, itertools
>>> g1,g2 = itertools.tee((x*x for x in xrange(5)),2)
>>> list(itertools.imap(operator.sub, itertools.islice(g1,1,None), g2))
[1, 3, 5, 7]

使用索引:

>>> [a[i+1]-a[i] for i in xrange(len(a)-1)]
[2, 2, 2, 4, 2, 4, 4]

4

好的,我认为我找到了正确的解决方案:

v = [x[0]-x[1] for x in zip(t[1:],t[:-1])]

2
是的,这很好,但我认为对于排序列表应该是: v = [x[0]-x[1] for x in zip(t[1:], t[:-1])] - Amitkumar Karnik

1
我猜测这就是numpy diff命令所做的事情,但为了完整起见,您可以简单地对子向量进行差分:
from numpy import array as a
a(x[1:])-a(x[:-1])

此外,我想将以下解决方案添加到问题的概括中: 带有周期边界的解决方案 有时候在数值积分中,您会希望使用周期性边界条件对列表进行差分运算(即第一个元素计算与最后一个元素的差异)。在这种情况下,可以使用numpy.roll函数:
v-np.roll(v,1)

在前面添加零的解决方案

另一个numpy的解决方案(仅供完整性)是使用

numpy.ediff1d(v)

这与numpy.diff相似,但仅适用于向量(它会将输入数组展平)。它提供了在生成的向量中添加前缀或后缀数字的能力。当处理通常是气象变量流量(例如雨水、潜热等)的累积字段时,这很有用,因为您希望得到与输入变量长度相同的结果列表,并保持第一个条目不变。
然后您可以编写
np.ediff1d(v,to_begin=v[0])

当然,您也可以使用np.diff命令来完成此操作,但在这种情况下,您需要使用prepend关键字在系列前加上零:

np.diff(v,prepend=0.0) 

所有上述解决方案都返回与输入长度相同的向量。

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