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

给定一个数字列表,如何找到每个(i)-th 元素和它的(i+1)-th 元素之间的差异?

使用 abc0表达式或列表内涵表达式更好吗?

例如:

给定一个列表 t=[1,3,6,...],目标是找到一个列表 v=[2,3,...],因为 3-1=26-3=3等等。

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

好吧,我想我找到了正确的解决办法:

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

其他的答案是正确的,但是如果你正在做数值计算,你可能会考虑麻木。使用 numpy,答案是:

v = numpy.diff(t)

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

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

您可以使用 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
    

用我的方式

>>>v = [1,2,3,4,5]
>>>[v[i] - v[i-1] for i, value in enumerate(v[1:], 1)]
[1, 1, 1, 1]

功能性方法:

>>> 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]

我建议使用

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))

使用 Python 3.8 + 中提供的 :=海象运算符:

>>> t = [1, 3, 6]
>>> prev = t[0]; [-prev + (prev := x) for x in t[1:]]
[2, 3]

我怀疑 numpy diff 命令就是这么做的,但是为了完整起见,你可以简单地区分子向量:

from numpy import array as a
a(x[1:])-a(x[:-1])

此外,我想把这些解决方案添加到这个问题的概括中:

具有周期边界的解

有时候使用数值积分,你会希望用周期性边界条件来区分列表(因此第一个元素会计算到最后一个元素的差值)。在这种情况下,numpy.roll 函数很有帮助:

v-np.roll(v,1)

预置零的解

另一个 麻木不仁解决方案(仅仅为了完整性)是使用

numpy.ediff1d(v)

这个函数作为 numpy.diff 工作,但只在一个向量上工作(它使输入数组变平)。它提供了在结果向量之前添加或追加数字的能力。这在处理气象变量(例如降雨、潜热等)中通常情况下的累积字段时非常有用,因为您希望得到一个与输入变量长度相同的结果列表,第一个条目保持不变。

然后你会写信

np.ediff1d(v,to_begin=v[0])

当然,您也可以使用 np.diff 命令来完成这项工作,但是在本例中,您需要使用 prepend 关键字为级数添加0:

np.diff(v,prepend=0.0)

上述所有解都返回一个与输入长度相同的向量。

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)]

你亦可使用以下转移矩阵,把差异转换为易读的文字

v = t.reshape((c,r)).T - t.T

其中 c = 列表中的项数,r = 1,因为列表基本上是一个向量或1d 数组。