0

我的问题不是关于我的代码,而是一般的 python。我们在家里进行了一场编码比赛,我的父母分别用 C# 和 javascript 编写了他们的这个项目的版本。我认为 Python 的本意就是要与其他语言一样高效,甚至更高。我们最终编写了相同的代码(显然不同的语法),但他们的文件在几毫秒内运行(221 个来自我父亲的 Javascript 和 500 个来自我妈妈的 C#),而我的文件在大约 5 分钟内运行。这是一个荒谬的区别,让我质疑 Python 究竟是如何应用于现实世界的数据处理和算法求解的。


我已经使用毕达哥拉斯数三元组解决了一个问题。问题是,如果 a 平方 + b 平方 = c 平方,则只有 a、b 和 c 的一个组合相加等于一千。最后打印出 1000 所需的数字,然后一起计时。

for c in range(1000, 0, -1):
        for a in range(1000, 0, -1):
            for b in range(1000, 0, -1):
                if (a*a)+(b*b)==(c*c):
                    if a+b+c == 1000:
                        print("I have found it")
                        print(a*b*c)
                        quit()
4

4 回答 4

6

与静态类型的语言相比,Python 通常效率较低。除了动态类型之外,Python 在其他几个方面也非常动态,例如某些操作的长链回退for、适用于任何可迭代的循环、上下文管理器等,它们可以适用于非常广泛的对象集。这些功能使编程变得方便,但通常会带来一定的成本。因此,经过优化的 Python 程序经常会被 C++、Haskell、Java 等中的等效程序所超越。

但我认为主要问题是你的算法效率不高。这里有三个循环,每个循环超过 999 个项目。因此,这意味着内部循环最多执行 997'002'999 次。我们可以重写算法,使其最多需要 499'500 次迭代,如下所示:

for c in range(1000, 0, -1):
    for a in range(999-c, 0, -1):
        b = 1000-a-c
        if a*a + b*b == c*c:
            print("I have found it")
            print(a*b*c)
            quit()

b事实上,我们可以通过从 1000-ac 中获得它来计算有效值。此外,我们可以通过从 的范围开始迭代来限制999-c范围a

如果我们省略printing 和quit()ing,当我们运行函数 1'000 次时,我们会得到以下结果:

>>> timeit(f, number=1000)
27.56889909002348

因此,这在 27.6 毫秒内运行。

于 2019-09-22T20:52:14.037 回答
2

您可以通过使用毕达哥拉斯三元组的平方和关系进一步加快该过程。在这个实现中看到https://www.geeksforgeeks.org/generate-pythagorean-triplets/

我自己用下面的代码试了一下,发现它在十分之二毫秒内运行。

import math
import time

def pythagoreanTriplets(limits) : 
    c, m = 0, 2

    # Limiting c would limit  
    # all a, b and c 
    while c < limits : 

        # Now loop on n from 1 to m-1 
        for n in range(1, m) : 
            a = m * m - n * n 
            b = 2 * m * n 
            c = m * m + n * n 

            # if c is greater than 
            # limit then break it 
            if limits%(a+b+c) ==0: 
              #print(1000/(a+b+c)*a,1000/(a+b+c)*b,1000/(a+b+c)*c, "Found")
              return

            print(a, b, c) 

        m = m + 1

start = time.process_time() 
pythagoreanTriplets(1000)
elapsed = time.process_time() 
elapsed = elapsed - start
print("Time spent in (function name) is: ", elapsed)

正如 Willem 所建议的那样,高效的代码是这里最大的因素。 在此处输入图像描述

用 Pete 的见解更新了上面的代码。

我用 print 语句运行了一次代码以确保它的正确性,一次没有看到速度,它显示减少了大约 20 倍!

在此处输入图像描述

于 2019-09-22T21:29:44.133 回答
2

我们可以让 Python for 循环更快。

发帖人的问题是,为什么 Python“for 循环”相对于 C# 和 JavaScript 如此缓慢。提出一种减少“for 循环”需求的不同算法并不能解决这个问题(特别是因为 C# 和 JavaScript 版本使用修改后的算法也会更快)。一般来说,编程语言使用类似的算法进行比较,它们的区别在于语言特性使它们能够在任务中表现出色——https ://benchmarksgame-https: //benchmarksgame-team.pages.debian.net/benchmarksgame/index 。 html

在这种情况下,任务是使用 Python 语言功能更快地搜索超过十亿个数字(三个嵌套 for 循环的大小)?

尝试了两种方法来加速代码:Cython 和 Numba(分别)。

设置:

  • Jupyter 笔记本
  • Python 3.5.2 操作系统
  • Windows 10 64 位
  • 处理器 i7

结果:

  1. Python 3.5.2 => 387.356 秒
  2. Cython => 117.223 秒
  3. Cython(改进)=> 0.63 秒(使用@CodeSurgeon 建议)
  4. Numba => 0.5 秒

Numba 版本堪比海报的 JavaScript 和 C# 时代。Numba 使用相同的“次优”算法比纯 Python 提供了 774 的加速。请注意,所有三个实现都使用基本相同的代码,如下所示。

  1. Python 3.5.2 代码

    import time

    def solve():
        for c in range(1000, 0, -1):
            for a in range(1000, 0, -1):
                for b in range(1000, 0, -1):
                    if (a*a)+(b*b)==(c*c):
                        if a+b+c == 1000:
                            return a*b*c


    to = time.time()
    print('I have found it {}'.format(solve()))
    print("Elapsed Time\n", time.time()- t0)

Python 3.5.2 输出

I have found it 31875000
Elapsed Time
 387.3550021648407
  1. Cython 代码(参见https://ipython-books.github.io/55-accelerating-python-code-with-cython/

    %load_ext cython
    %%cython -a

    import time

    def solve():
        for c in range(1000, 0, -1):
            for a in range(1000, 0, -1):
                for b in range(1000, 0, -1):
                    if (a*a)+(b*b)==(c*c):
                        if a+b+c == 1000:
                            return a*b*c


    t0 = time.time()
    print('I have found it {}'.format(solve()))
    print("Elapsed Time\n", time.time()- t0)

赛通输出

I have found it 31875000
('Elapsed Time\n', 117.22299981117249)
  1. Cython(与 Cdef https://buildmedia.readthedocs.org/media/pdf/notes-on-cython/latest/notes-on-cython.pdf

    %%cython -a

    进口时间

    cdef int solve(): cdef int a, b, c

    for c in range(1000, 0, -1):
        for a in range(1000, 0, -1):
            for b in range(1000, 0, -1):
                if (a*a)+(b*b)==(c*c):
                    if a+b+c == 1000:
                        return a*b*c
    

    t0 = time.time() print('我找到了{}'.format(solve())) print("Elapsed Time\n", time.time()- t0)

输出(使用 Cdef 的 Cython)

I have found it 31875000
('Elapsed Time\n', 0.6340005397796631)
  1. Numba 代码(参见https://numba.pydata.org/

    import time
    from numba import jit

    @jit(nopython=True)    # only need to add Numba decorator to get speed up
    def solve():
        for c in range(1000, 0, -1):
            for a in range(1000, 0, -1):
                for b in range(1000, 0, -1):
                    if (a*a)+(b*b)==(c*c):
                        if a+b+c == 1000:
                            return a*b*c


    t0 = time.time()
    print('I have found it {}'.format(solve()))
    print("Elapsed Time\n", time.time()- t0)

Numba 输出

I have found it 31875000
Elapsed Time
 0.5209996700286865
于 2019-09-23T04:18:16.467 回答
1

似乎有很多重复的计算可以从循环中取出,所以我决定预先计算平方。 sqrs作为 dict 执行三个任务:

  1. 它允许通过迭代来循环 x 和 x**2 sqrs.items()
  2. 它允许查找 a**2 + b**2 本身是否为正方形。
  3. 它允许查找c前一个任务是否为真。

这是带有计时的代码:

def original():
    for c in range(1000, 0, -1):
        for a in range(1000, 0, -1):
            for b in range(1000, 0, -1):
                if (a*a)+(b*b)==(c*c):
                    if a+b+c == 1000:
                        print("I have found it")
                        print(a*b*c)
                        return

%time original()
I have found it
31875000
Wall time: 2min 27s

def pre_compute_sqrs():
    sqrs = {x**2:x for x in range(1, 1001)}
    for aa, a in sqrs.items():
        for bb, b in sqrs.items():
            ab, aabb = a + b, aa + bb
            if ab >= 1000 or aabb >= 1_000_000:
                break
            if aabb not in sqrs:
                continue
            c = sqrs[aabb]
            if a+b+c == 1000:
                print("I have found it")
                print(a*b*c)
                return

%time pre_compute_sqrs()
I have found it
31875000
Wall time: 46.9 ms

Python慢​​吗?有可能。

通常是不是太慢了?不,通常不会。

于 2019-09-24T17:30:53.950 回答