Python中的高效求和

Ada*_*dam 32 python performance sum cumsum

我正在尝试在Python中有效地计算求和的总和:

WolframAlpha能够计算出过高的 n 值: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)
Run Code Online (Sandbox Code Playgroud)

Kel*_*ndy 61

这是一个非常快速的方法:

\n
result = ((((12 * n + 45) * n + 50) * n + 15) * n - 2) * n // 120\n
Run Code Online (Sandbox Code Playgroud)\n

我是如何到达那里的:

\n
    \n
  1. 将内部总和重写为众所周知的x*(x+1)//2。于是整个事情就变成了sum(x**2 * x*(x+1)//2 for x in range(n+1))
  2. \n
  3. 重写为sum(x**4 + x**3 for x in range(n+1)) // 2.
  4. \n
  5. 查找和的公式sum(x**4)sum(x**3)
  6. \n
  7. 将由此产生的混乱简化(12*n**5 + 45*n**4 + 50*n**3 + 15*n**2 - 2*n) // 120为.
  8. \n
  9. 霍纳吧。
  10. \n
\n

如果在步骤 1. 和 2. 之后您知道它是 5 次多项式,则可以采用另一种推导方法:

\n
    \n
  1. 通过简单的实现计算六个值。
  2. \n
  3. 根据具有六个未知数(多项式系数)的六个方程计算多项式。我的做法与类似,但A与此相比,我的矩阵是左右镜像的,我称之为 y-vector b
  4. \n
\n

代码:

\n
from fractions import Fraction\nimport math\nfrom functools import reduce\n\ndef naive(n):\n    return sum(x**2 * sum(range(x+1)) for x in range(n+1))\n\ndef lcm(ints):\n    return reduce(lambda r, i: r * i // math.gcd(r, i), ints)\n\ndef polynomial(xys):\n    xs, ys = zip(*xys)\n    n = len(xs)\n    A = [[Fraction(x**i) for i in range(n)] for x in xs]\n    b = list(ys)\n    for _ in range(2):\n        for i0 in range(n):\n            for i in range(i0 + 1, n):\n                f = A[i][i0] / A[i0][i0]\n                for j in range(i0, n):\n                    A[i][j] -= f * A[i0][j]\n                b[i] -= f * b[i0]\n        A = [row[::-1] for row in A[::-1]]\n        b.reverse()\n    coeffs = [b[i] / A[i][i] for i in range(n)]\n    denominator = lcm(c.denominator for c in coeffs)\n    coeffs = [int(c * denominator) for c in coeffs]\n    horner = str(coeffs[-1])\n    for c in coeffs[-2::-1]:\n        horner += \' * n\'\n        if c:\n            horner = f"({horner} {\'+\' if c > 0 else \'-\'} {abs(c)})"\n    return f\'{horner} // {denominator}\'\n\nprint(polynomial((x, naive(x)) for x in range(6)))\n
Run Code Online (Sandbox Code Playgroud)\n

输出(在线尝试!):

\n
((((12 * n + 45) * n + 50) * n + 15) * n - 2) * n // 120\n
Run Code Online (Sandbox Code Playgroud)\n

  • @Adam是的,我认为在这样的情况下,你已经简化了你的实际问题,在问题中解释给定的公式只是一个例子非常重要,但你的目标实际上是弄清楚如何快速计算总和,而不是获得该特定公式的实际答案。否则,您将面临获得像这样的解决方案的风险,这无疑是解决您提出的问题的最佳方法,但对您真正遇到的问题没有任何帮助。 (16认同)
  • @Adam 我想这解释了为什么你在非 NumPy 解决方案中使用所有这些函数,这看起来确实很奇怪。也许更一般的情况仍然允许类似的优化,但这取决于更一般的程度。也许用通用公式问另一个问题,因为你确实有它?就像,用“f(x)”和“g(y)”代替“x^2”和“y”左右,其中“f”和“g”是未知函数(尽管某些属性可能是已知的并且可以被利用)。 (2认同)

dan*_*444 20

(最快的方法 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)
Run Code Online (Sandbox Code Playgroud)

您的快速解决方案比慢速解决方案要快得多。是的,对于较大的 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)
Run Code Online (Sandbox Code Playgroud)
Slow method: 0.06906533241271973
Fast method: 0.008007287979125977
Run Code Online (Sandbox Code Playgroud)

编辑:更快的方法(由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))
Run Code Online (Sandbox Code Playgroud)
Faster, pure python: 0.0009944438934326172
Run Code Online (Sandbox Code Playgroud)

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)
Run Code Online (Sandbox Code Playgroud)

  • 我也会尝试等效的非 NumPy 版本,您可能会发现它比 NumPy 版本“更快”。例如 `result1 = sum(x*x * ysum for x, ysum in enumerate(itertools.accumulate(range(n+1))))` 或 `ysum = 0; 结果1 = sum(x*x * (ysum := ysum + x) for x in range(n+1))` (6认同)
  • @diggusbickus 因为 `np.int64` 只有 64 位来存储整数,所以 Python `int` 可以与 RAM 允许的一样大。通过使用“通用”“np.object”,您可以确保 numpy 不会将“int”转换为“np.int64”。 (4认同)
  • 那么为什么 np.object 而不是 np.int64 呢? (3认同)
  • 我认为它不适合我的(因为我只是在谈论我的方法并且希望保持这种方式),但它会让你的方法变得更好。 (3认同)
  • 纯 python 版本在比较中有点作弊,它像其他方法一样使用“x*x”而不是“x**2”。对于 numpy 解决方案,更改为“x*x”,这是较大 n 的最快方法(在我的计算机上)。 (3认同)
  • @Forss 嗯,对。我出于习惯而使用了“x*x”,尽管这种习惯部分是因为速度。既然你指出了,我很失望 NumPy 不只是弄清楚它是一个乘法并将该顿悟应用于整个数组。但后来我想起我们正在使用“object”,我想 NumPy 不会在那里进行假设/类型分析。如果没有“dtype”,它确实可以同样快地执行“x*x”和“x**2”。对于使用“x*x”的两种解决方案,NumPy 在 n=1000 时对我来说要快一些,在 n=10000 时大约同样快,而在 n=100000 时则慢一些。 (2认同)

Peq*_*que 8

像这样将 Python 与 WolframAlpha 进行比较是不公平的,因为 Wolfram 在计算之前会简化方程。

\n

幸运的是,Python 生态系统没有限制,因此您可以使用SymPy

\n
from sympy import summation\nfrom sympy import symbols\n\nn, x, y = symbols("n,x,y")\neq = summation(x ** 2 * summation(y, (y, 0, x)), (x, 0, n))\neq.evalf(subs={"n": 1000})\n
Run Code Online (Sandbox Code Playgroud)\n

它几乎会立即计算出预期结果:100375416791650。这是因为 SymPy 为您简化了方程,就像 Wolfram 一样。查看 的值eq

\n

在此输入图像描述

\n

@Kelly Bundy\'s 的答案很棒,但如果你像我一样使用计算器来计算2 + 2,那么你会喜欢 SymPy \xe2\x9d\xa4。正如您所看到的,只需 3 行代码即可获得相同的结果,并且该解决方案也适用于其他更复杂的情况。

\n