【发布时间】:2011-01-24 22:38:12
【问题描述】:
给定一个数字列表,如何找出第 (i) 个元素与其第 (i+1) 个元素之间的差异?
使用lambda 表达式或列表推导式更好吗?
例如:
给定一个列表t=[1,3,6,...],目标是找到一个列表v=[2,3,...],因为3-1=2、6-3=3等
【问题讨论】:
给定一个数字列表,如何找出第 (i) 个元素与其第 (i+1) 个元素之间的差异?
使用lambda 表达式或列表推导式更好吗?
例如:
给定一个列表t=[1,3,6,...],目标是找到一个列表v=[2,3,...],因为3-1=2、6-3=3等
【问题讨论】:
从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,因为列表基本上是一个向量或一维数组。
【讨论】:
好的。我想我找到了正确的解决方案:
v = [x[0]-x[1] for x in zip(t[1:],t[:-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)
上述所有解决方案都返回一个与输入长度相同的向量。
【讨论】:
我建议使用
v = np.diff(t)
这简单易读。
但如果您希望v 的长度与t 相同,那么
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+ 中可用的 := walrus 运算符:
>>> t = [1, 3, 6]
>>> prev = t[0]; [-prev + (prev := x) for x in t[1:]]
[2, 3]
【讨论】:
如果您不想使用numpy 或zip,可以使用以下解决方案:
>>> t = [1, 3, 6]
>>> v = [t[i+1]-t[i] for i in range(len(t)-1)]
>>> v
[2, 3]
【讨论】:
您可以使用itertools.tee 和zip 来高效地构建结果:
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 的整个副本。numpy.diff 是 slow 因为它必须首先将 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)) 使其适用于无限迭代
函数式方法:
>>> 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 = [1,2,3,4,5]
>>>[v[i] - v[i-1] for i, value in enumerate(v[1:], 1)]
[1, 1, 1, 1]
【讨论】:
enumerate 是一种浪费,因为你没有使用value。见stackoverflow.com/a/16714453/832230
其他答案是正确的,但如果您正在做数值工作,您可能需要考虑 numpy.使用numpy,答案是:
v = numpy.diff(t)
【讨论】:
np.diff([2,4,9]) 将是 [2,5]
zip 版本更高效吗?
>>> t
[1, 3, 6]
>>> [j-i for i, j in zip(t[:-1], t[1:])] # or use itertools.izip in py2k
[2, 3]
【讨论】:
[abs(j-i) for i,j in zip(t, t[1:])]
list(itertools.starmap(operator.sub, zip(t[1:], t)))(在导入itertools和operator之后)。
list(map(operator.sub, t[1:], t[:-1]))就行了。