Python 中的高效求和
Efficient summation in Python
我正在尝试有效地计算 Python 中求和的求和:
WolframAlpha is able to compute it too a high n value: sum of sum.
我有两种方法:for 循环方法和 np.sum 方法。我认为 np.sum 方法会更快。但是,它们在 n 变大之前是相同的,之后 np.sum 出现溢出错误并给出错误的结果。
我正在尝试找到计算此总和的最快方法。
import numpy as np
import time
def summation(start,end,func):
sum=0
for i in range(start,end+1):
sum+=func(i)
return sum
def x(y):
return y
def x2(y):
return y**2
def mysum(y):
return x2(y)*summation(0, y, x)
n=100
# method #1
start=time.time()
summation(0,n,mysum)
print('Slow method:',time.time()-start)
# method #2
start=time.time()
w=np.arange(0,n+1)
(w**2*np.cumsum(w)).sum()
print('Fast method:',time.time()-start)
(最快的方法3和4在最后)
在快速 NumPy 方法中,您需要指定 dtype=np.object
,以便 NumPy 不会将 Python int
转换为它自己的数据类型(np.int64
或其他)。它现在会给你正确的结果(最多检查 N=100000)。
# method #2
start=time.time()
w=np.arange(0, n+1, dtype=np.object)
result2 = (w**2*np.cumsum(w)).sum()
print('Fast method:', time.time()-start)
您的快速解决方案比慢速解决方案快得多。是的,对于大 N,但已经在 N=100 时快了 8 倍:
start=time.time()
for i in range(100):
result1 = summation(0, n, mysum)
print('Slow method:', time.time()-start)
# method #2
start=time.time()
for i in range(100):
w=np.arange(0, n+1, dtype=np.object)
result2 = (w**2*np.cumsum(w)).sum()
print('Fast method:', time.time()-start)
Slow method: 0.06906533241271973
Fast method: 0.008007287979125977
编辑:更快的方法(由 KellyBundy,南瓜)是使用纯 python。事实证明 NumPy 在这里没有优势,因为它没有 np.objects
.
的矢量化代码
# method #3
import itertools
start=time.time()
for i in range(100):
result3 = sum(x*x * ysum for x, ysum in enumerate(itertools.accumulate(range(n+1))))
print('Faster, pure python:', (time.time()-start))
Faster, pure python: 0.0009944438934326172
EDIT2:Forss 注意到 numpy 快速方法可以通过使用 x*x
而不是 x**2
来优化。对于 N > 200
它比纯 Python 方法更快。对于 N < 200
它比纯 Python 方法慢(边界的确切值可能取决于机器,我的是 200,最好自己检查):
# method #4
start=time.time()
for i in range(100):
w = np.arange(0, n+1, dtype=np.object)
result2 = (w*w*np.cumsum(w)).sum()
print('Fast method x*x:', time.time()-start)
这是一个非常快速的方法:
result = ((((12 * n + 45) * n + 50) * n + 15) * n - 2) * n // 120
我是如何到达那里的:
- 将内和重写为众所周知的
x*(x+1)//2
。所以整个事情变成了 sum(x**2 * x*(x+1)//2 for x in range(n+1))
.
- 重写为
sum(x**4 + x**3 for x in range(n+1)) // 2
。
- 在 formulas 中查找
sum(x**4)
和 sum(x**3)
。
- Simplify 造成的混乱
(12*n**5 + 45*n**4 + 50*n**3 + 15*n**2 - 2*n) // 120
.
- Horner吧。
如果在步骤 1. 和 2. 之后您知道它是 5 次多项式,则另一种推导方法:
- 用简单的实现计算六个值。
- 计算六个方程的多项式,六个未知数(多项式系数)。我做的与 this 类似,但我的矩阵
A
与之相比是左右镜像的,我称我的 y 向量为 b
.
代码:
from fractions import Fraction
import math
from functools import reduce
def naive(n):
return sum(x**2 * sum(range(x+1)) for x in range(n+1))
def lcm(ints):
return reduce(lambda r, i: r * i // math.gcd(r, i), ints)
def polynomial(xys):
xs, ys = zip(*xys)
n = len(xs)
A = [[Fraction(x**i) for i in range(n)] for x in xs]
b = list(ys)
for _ in range(2):
for i0 in range(n):
for i in range(i0 + 1, n):
f = A[i][i0] / A[i0][i0]
for j in range(i0, n):
A[i][j] -= f * A[i0][j]
b[i] -= f * b[i0]
A = [row[::-1] for row in A[::-1]]
b.reverse()
coeffs = [b[i] / A[i][i] for i in range(n)]
denominator = lcm(c.denominator for c in coeffs)
coeffs = [int(c * denominator) for c in coeffs]
horner = str(coeffs[-1])
for c in coeffs[-2::-1]:
horner += ' * n'
if c:
horner = f"({horner} {'+' if c > 0 else '-'} {abs(c)})"
return f'{horner} // {denominator}'
print(polynomial((x, naive(x)) for x in range(6)))
输出(Try it online!):
((((12 * n + 45) * n + 50) * n + 15) * n - 2) * n // 120
像这样将 Python 与 WolframAlpha 进行比较是不公平的,因为 Wolfram 会在计算之前简化方程式。
幸运的是,Python 生态系统没有限制,因此您可以使用 SymPy:
from sympy import summation
from sympy import symbols
n, x, y = symbols("n,x,y")
eq = summation(x ** 2 * summation(y, (y, 0, x)), (x, 0, n))
eq.evalf(subs={"n": 1000})
它将几乎立即计算出预期结果:100375416791650
。这是因为 SymPy 为您简化了方程式,就像 Wolfram 所做的那样。查看eq
的值:
很棒,但如果你像我一样使用计算器计算 2 + 2
,那么你会爱上 SymPy ❤。如您所见,只需 3 行代码即可获得相同的结果,并且该解决方案也适用于其他更复杂的情况。
所有答案都使用数学来简化或实现 python 中的循环,试图达到 cpu 最优,但它们不是内存最优。
这是一个简单的实现,没有使用任何内存效率高的数学简化
def function5():
inner_sum = float()
result = float()
for x in range(0, n + 1):
inner_sum += x
result += x ** 2 * inner_sum
return result
相对于 dankal444 的其他解决方案,它相当慢:
method 2 | 31 µs ± 2.06 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
method 3 | 116 µs ± 538 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
method 4 | 91 µs ± 356 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
function 5 | 217 µs ± 1.14 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
顺便说一句,如果你用 numba 来 jit 函数(可能有更好的选择):
from numba import jit
function5 = jit(nopython=True)(function5)
你得到
59.8 ns ± 0.209 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
在评论中,您提到实际上是 f(x) 和 g(y) 而不是 x2 和 y。如果你只需要那个和的近似值,你可以假设和是中点黎曼和,这样你的和就可以用二重积分来近似 ∫-.5n+.5 f(x) ∫-.5x+.5 g(y) dy dx.
用你原来的 f(x)=x2 和 g(y)=y,这简化为 n5/10 +3n4/8+n3/2+5n2/16+3n/32+ 1/160,与正确结果相差n3/12+3n2/16+53n/480+1/160.
基于此,我怀疑 (actual-integral)/actual 会是 max(f'',g'')*O(n-2),但是我无法证明这一点。
我正在尝试有效地计算 Python 中求和的求和:
WolframAlpha is able to compute it too a high n value: sum of sum.
我有两种方法:for 循环方法和 np.sum 方法。我认为 np.sum 方法会更快。但是,它们在 n 变大之前是相同的,之后 np.sum 出现溢出错误并给出错误的结果。
我正在尝试找到计算此总和的最快方法。
import numpy as np
import time
def summation(start,end,func):
sum=0
for i in range(start,end+1):
sum+=func(i)
return sum
def x(y):
return y
def x2(y):
return y**2
def mysum(y):
return x2(y)*summation(0, y, x)
n=100
# method #1
start=time.time()
summation(0,n,mysum)
print('Slow method:',time.time()-start)
# method #2
start=time.time()
w=np.arange(0,n+1)
(w**2*np.cumsum(w)).sum()
print('Fast method:',time.time()-start)
(最快的方法3和4在最后)
在快速 NumPy 方法中,您需要指定 dtype=np.object
,以便 NumPy 不会将 Python int
转换为它自己的数据类型(np.int64
或其他)。它现在会给你正确的结果(最多检查 N=100000)。
# method #2
start=time.time()
w=np.arange(0, n+1, dtype=np.object)
result2 = (w**2*np.cumsum(w)).sum()
print('Fast method:', time.time()-start)
您的快速解决方案比慢速解决方案快得多。是的,对于大 N,但已经在 N=100 时快了 8 倍:
start=time.time()
for i in range(100):
result1 = summation(0, n, mysum)
print('Slow method:', time.time()-start)
# method #2
start=time.time()
for i in range(100):
w=np.arange(0, n+1, dtype=np.object)
result2 = (w**2*np.cumsum(w)).sum()
print('Fast method:', time.time()-start)
Slow method: 0.06906533241271973
Fast method: 0.008007287979125977
编辑:更快的方法(由 KellyBundy,南瓜)是使用纯 python。事实证明 NumPy 在这里没有优势,因为它没有 np.objects
.
# method #3
import itertools
start=time.time()
for i in range(100):
result3 = sum(x*x * ysum for x, ysum in enumerate(itertools.accumulate(range(n+1))))
print('Faster, pure python:', (time.time()-start))
Faster, pure python: 0.0009944438934326172
EDIT2:Forss 注意到 numpy 快速方法可以通过使用 x*x
而不是 x**2
来优化。对于 N > 200
它比纯 Python 方法更快。对于 N < 200
它比纯 Python 方法慢(边界的确切值可能取决于机器,我的是 200,最好自己检查):
# method #4
start=time.time()
for i in range(100):
w = np.arange(0, n+1, dtype=np.object)
result2 = (w*w*np.cumsum(w)).sum()
print('Fast method x*x:', time.time()-start)
这是一个非常快速的方法:
result = ((((12 * n + 45) * n + 50) * n + 15) * n - 2) * n // 120
我是如何到达那里的:
- 将内和重写为众所周知的
x*(x+1)//2
。所以整个事情变成了sum(x**2 * x*(x+1)//2 for x in range(n+1))
. - 重写为
sum(x**4 + x**3 for x in range(n+1)) // 2
。 - 在 formulas 中查找
sum(x**4)
和sum(x**3)
。 - Simplify 造成的混乱
(12*n**5 + 45*n**4 + 50*n**3 + 15*n**2 - 2*n) // 120
. - Horner吧。
如果在步骤 1. 和 2. 之后您知道它是 5 次多项式,则另一种推导方法:
- 用简单的实现计算六个值。
- 计算六个方程的多项式,六个未知数(多项式系数)。我做的与 this 类似,但我的矩阵
A
与之相比是左右镜像的,我称我的 y 向量为b
.
代码:
from fractions import Fraction
import math
from functools import reduce
def naive(n):
return sum(x**2 * sum(range(x+1)) for x in range(n+1))
def lcm(ints):
return reduce(lambda r, i: r * i // math.gcd(r, i), ints)
def polynomial(xys):
xs, ys = zip(*xys)
n = len(xs)
A = [[Fraction(x**i) for i in range(n)] for x in xs]
b = list(ys)
for _ in range(2):
for i0 in range(n):
for i in range(i0 + 1, n):
f = A[i][i0] / A[i0][i0]
for j in range(i0, n):
A[i][j] -= f * A[i0][j]
b[i] -= f * b[i0]
A = [row[::-1] for row in A[::-1]]
b.reverse()
coeffs = [b[i] / A[i][i] for i in range(n)]
denominator = lcm(c.denominator for c in coeffs)
coeffs = [int(c * denominator) for c in coeffs]
horner = str(coeffs[-1])
for c in coeffs[-2::-1]:
horner += ' * n'
if c:
horner = f"({horner} {'+' if c > 0 else '-'} {abs(c)})"
return f'{horner} // {denominator}'
print(polynomial((x, naive(x)) for x in range(6)))
输出(Try it online!):
((((12 * n + 45) * n + 50) * n + 15) * n - 2) * n // 120
像这样将 Python 与 WolframAlpha 进行比较是不公平的,因为 Wolfram 会在计算之前简化方程式。
幸运的是,Python 生态系统没有限制,因此您可以使用 SymPy:
from sympy import summation
from sympy import symbols
n, x, y = symbols("n,x,y")
eq = summation(x ** 2 * summation(y, (y, 0, x)), (x, 0, n))
eq.evalf(subs={"n": 1000})
它将几乎立即计算出预期结果:100375416791650
。这是因为 SymPy 为您简化了方程式,就像 Wolfram 所做的那样。查看eq
的值:
2 + 2
,那么你会爱上 SymPy ❤。如您所见,只需 3 行代码即可获得相同的结果,并且该解决方案也适用于其他更复杂的情况。
所有答案都使用数学来简化或实现 python 中的循环,试图达到 cpu 最优,但它们不是内存最优。
这是一个简单的实现,没有使用任何内存效率高的数学简化
def function5():
inner_sum = float()
result = float()
for x in range(0, n + 1):
inner_sum += x
result += x ** 2 * inner_sum
return result
相对于 dankal444 的其他解决方案,它相当慢:
method 2 | 31 µs ± 2.06 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
method 3 | 116 µs ± 538 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
method 4 | 91 µs ± 356 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
function 5 | 217 µs ± 1.14 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
顺便说一句,如果你用 numba 来 jit 函数(可能有更好的选择):
from numba import jit
function5 = jit(nopython=True)(function5)
你得到
59.8 ns ± 0.209 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
在评论中,您提到实际上是 f(x) 和 g(y) 而不是 x2 和 y。如果你只需要那个和的近似值,你可以假设和是中点黎曼和,这样你的和就可以用二重积分来近似 ∫-.5n+.5 f(x) ∫-.5x+.5 g(y) dy dx.
用你原来的 f(x)=x2 和 g(y)=y,这简化为 n5/10 +3n4/8+n3/2+5n2/16+3n/32+ 1/160,与正确结果相差n3/12+3n2/16+53n/480+1/160.
基于此,我怀疑 (actual-integral)/actual 会是 max(f'',g'')*O(n-2),但是我无法证明这一点。