我有一个列表 A=[a,b,c,d]
。需要根据 A
中每个项目之间的操作计算出一个新列表 B
。
B= [a, b-(a), c-(a+(b-a)), d-(a+(b-a)+(c-(a+(b-a)))) ]
有没有一种Pythonic的方式来完成这个任务?List A并不总是一个由4个项组成的列表,所以解决方案需要适用于任意长度的列表。提前致谢。
所有的术语都会被消除(c-(a+(b-a))
简化为 c - b
,d-(a+(b-a)+(c-(a+(b-a))))
简化为 d - c
),所以这里真正的算法是每个术语等于匹配术语减去前一个术语。这样可以大大简化事情:
B = [A[0]] # Initial term has no prior to subtract from it
B += [x - y for x, y in zip(A[1:], A)] # All other terms computed by subtracting term n - 1 from term n
如果你想简化这个代码(忽略引用库),你可以插入一个虚拟的 0
来得到第一个元素的结果,而无需显式地进行特殊处理:
from itertools import chain # At top of file
B = [x - y for x, y in zip(A, chain([0], A))]
如果你喜欢使用map
等方法进行微优化,你可以将后者替换为:
from operator import sub # At top of file
B = [*map(sub, A, chain([0], A))]
B = [a, b-a, c-b, d-c]
考虑到这一点,我们可以使用列表推导式:
[y - x for x, y in zip([0] + data, data)]
data = [1, 2, 7, 6]
result = [y - x for x, y in zip([0] + data, data)]
print(result)
输出:
[1, 1, 5, -1]
有两种解决方案,它们不假设事物“抵消”(因为这对于标准类型如float
和Counter
已经是错误的,如下所示)。
如果我正确理解了模式,第一个B值应该是第一个A值,然后每个下一个B值始终是前面所有B值的总和减去下一个A值。一种方法是:
B = []
for a in A:
if not B:
b = sumB = a
else:
b = a - sumB
sumB = sumB + b
B.append(b)
使用 itertools.accumulate
和 operator.sub
的有趣方法:
B = A[:1]
B += map(sub, A[1:], accumulate(B))
测试:
A = [31, 41, 59, 26]
reference [31, 10, 18, -33]
subtract_neighbors correct [31, 10, 18, -33]
loop correct [31, 10, 18, -33]
fun correct [31, 10, 18, -33]
A = [1, 1, 1e-20, 1e-20]
reference [1, 0, -1.0, 1e-20]
subtract_neighbors wrong [1, 0, -1.0, 0.0]
loop correct [1, 0, -1.0, 1e-20]
fun correct [1, 0, -1.0, 1e-20]
A = [Counter(), Counter({None: 1}), Counter(), Counter({None: 1})]
reference [Counter(), Counter({None: 1}), Counter(), Counter()]
subtract_neighbors wrong [Counter(), Counter({None: 1}), Counter(), Counter({None: 1})]
loop correct [Counter(), Counter({None: 1}), Counter(), Counter()]
fun correct [Counter(), Counter({None: 1}), Counter(), Counter()]
执行上述检查的代码(在线尝试!):
def reference(A):
a, b, c, d = A
return [a, b-(a), c-(a+(b-a)), d-(a+(b-a)+(c-(a+(b-a)))) ]
def subtract_neighbors(A):
a, b, c, d = A
return [a, b-a, c-b, d-c]
def loop(A):
B = []
for a in A:
if not B:
b = sumB = a
else:
b = a - sumB
sumB = sumB + b
B.append(b)
return B
def fun(A):
B = A[:1]
B += map(sub, A[1:], accumulate(B))
return B
from collections import Counter
from itertools import accumulate
from operator import sub
funcs = [
reference,
subtract_neighbors,
loop,
fun,
]
tests = [
[31, 41, 59,26],
[1, 1, 1e-20, 1e-20],
[Counter(), Counter([None])] * 2,
]
for A in tests:
print(' A =', A)
for func in funcs:
result = func(A)
if func is reference:
expect = result
correctness = ' '
else:
correctness = 'correct' if result == expect else 'wrong '
print(f'{func.__name__:19}', correctness, result)
print()
根据评论区的建议,这是最简单和最快的解决方案:
A = [5, 9, 3, 8]
B = [x - y for x, y in zip(A, [0] + A)]
这将输出:
B
[5, 4, -6, 5]
sum()
会导致二次运行时间。 - BrokenBenchmarksum()
。请看我的回答。 - BrokenBenchmarkfor
循环的每次迭代中更新它。抱歉之前没有提供解决方案。 - BrokenBenchmark你也可以这样做
A = [1,2,3,4]
B = [A[0]]+[A[i+1]-A[i] for i in range(len(A)-1)]
double
的精度不足,未简化的表达式正在获得不同的结果。很少有情况下你会希望浮点数的缺陷使你的结果偏离数学上正确的答案,但我认为在极为奇怪的情况下或许会出现这种情况。 - ShadowRangerfloat
,而且例如对于Counter
也偏离了参考代码,请参见我的答案中的测试。甚至可以考虑一些自定义类型。问题并没有说与好数字有任何关系。整个问题中唯一出现的数字是指向列表长度的“4”。我们所知道的关于这些值的信息只是它们支持+
和-
。 - Kelly Bundy