拥有一个相对简单的代码块,该代码块通过两个数组循环、相乘和累计相加:
import numpy as np
a = np.array([1, 2, 4, 6, 7, 8, 9, 11])
b = np.array([0.01, 0.2, 0.03, 0.1, 0.1, 0.6, 0.5, 0.9])
c = []
d = 0
for i, val in enumerate(a):
d += val
c.append(d)
d *= b[i]
有没有一种不用迭代就可以做到这一点的方法?我想可以使用cumsum/cumprod,但我很难弄清楚如何使用。当你一步一步地分解正在发生的事情时,看起来是这样的:
# 0: 0 + a[0]
# 1: ((0 + a[0]) * b[0]) + a[1]
# 2: ((((0 + a[0]) * b[0]) + a[1]) * b[1]) + a[2]
编辑澄清:我对列表(或数组)C感兴趣。
最佳答案
在每个迭代中,您都有-
d[n+1] = d[n] + a[n]
d[n+1] = d[n+1] * b[n]
因此,本质上-
d[n+1] = (d[n] + a[n]) * b[n]
即-
d[n+1] = (d[n]* b[n]) + K[n] #where `K[n] = a[n] * b[n]`
现在,使用这个公式,如果您写下until
n = 2
cases的表达式,您将-D[1]=D[0]*B[0]+K[0]
D[2]=D[0]*B[0]*B[1]+K[0]*B[1]+K[1]
D[3]=D[0]*B[0]*B[1]*B[2]+K[0]*B[1]*B[2]+K[1]*B[2]+K[2]
Scalars : b[0]*b[1]*b[2] b[1]*b[2] b[2] 1
Coefficients : d[0] K[0] K[1] K[2]
因此,您需要将cumProd反转为
b
,使用K
数组执行元素相乘。最后,要获得c
,请执行cumsum
,因为在按c
缩小之前存储了b
,所以您需要按cumsum
的反向cumprod缩小b
版本。最终实现如下-
# Get reversed cumprod of b and pad with `1` at the end
b_rev_cumprod = b[::-1].cumprod()[::-1]
B = np.hstack((b_rev_cumprod,1))
# Get K
K = a*b
# Append with 0 at the start, corresponding starting d
K_ext = np.hstack((0,K))
# Perform elementwsie multiplication and cumsum and scale down for final c
sums = (B*K_ext).cumsum()
c = sums[1:]/b_rev_cumprod
运行时测试和验证输出
函数定义-
def original_approach(a,b):
c = []
d = 0
for i, val in enumerate(a):
d = d+val
c.append(d)
d = d*b[i]
return c
def vectorized_approach(a,b):
b_rev_cumprod = b[::-1].cumprod()[::-1]
B = np.hstack((b_rev_cumprod,1))
K = a*b
K_ext = np.hstack((0,K))
sums = (B*K_ext).cumsum()
return sums[1:]/b_rev_cumprod
运行时间和验证
案例1:OP样本案例
In [301]: # Inputs
...: a = np.array([1, 2, 4, 6, 7, 8, 9, 11])
...: b = np.array([0.01, 0.2, 0.03, 0.1, 0.1, 0.6, 0.5, 0.9])
...:
In [302]: original_approach(a,b)
Out[302]:
[1,
2.0099999999999998,
4.4020000000000001,
6.1320600000000001,
7.6132059999999999,
8.7613205999999995,
14.256792359999999,
18.128396179999999]
In [303]: vectorized_approach(a,b)
Out[303]:
array([ 1. , 2.01 , 4.402 , 6.13206 ,
7.613206 , 8.7613206 , 14.25679236, 18.12839618])
案例2:大型输入案例
In [304]: # Inputs
...: N = 1000
...: a = np.random.randint(0,100000,N)
...: b = np.random.rand(N)+0.1
...:
In [305]: np.allclose(original_approach(a,b),vectorized_approach(a,b))
Out[305]: True
In [306]: %timeit original_approach(a,b)
1000 loops, best of 3: 746 µs per loop
In [307]: %timeit vectorized_approach(a,b)
10000 loops, best of 3: 76.9 µs per loop
请注意,对于非常巨大的输入数组情况,如果
b
元素是如此小的分数,由于累积运算,b_rev_cumprod
的初始数字可能会出现在这些初始位置上,从而导致zeros
。