【问题标题】:Which method is faster and why?哪种方法更快,为什么?
【发布时间】:2021-08-03 02:44:46
【问题描述】:

我正在解决找到最大数字的问题。我知道这个问题的解决方案,但我无法理解连接两个数字的不同方法是否会对运行时间产生任何影响。我正在使用 python 3.7。

方法一

将两个数字转换为字符串并将它们连接起来,然后将它们转换回整数。

def concatenate(num1, num2):
    num1 = str(num1)
    num2 = str(num2)
    num1 += num2
    return int(num1)

方法二

计算其中一个数字的位数,并在另一个数字中添加相同数量的零。

def concatenate(num1, num2):
    digits = len(str(num2))
    num1 = num1 * (10**digits)
    num1 += num2
    return num1

技术上的差异有什么不同吗?转换字符串或算术运算(乘法)中的数字哪个更快,为什么?更改为另一种语言是否会产生任何影响,例如基于编译器的语言(C++)?

编辑:

方法一——执行时间6.604194641113281e-05秒

方法 2 - 执行时间 5.245208740234375e-06 秒

数据类型转换所花费的时间更多,但为什么会这样?它只适用于像 python 这样的解释型语言,而不适用于像 C++ 这样的基于编译器的语言?

【问题讨论】:

  • 自己运行看看?
  • 这段代码应该到底是做什么的?我能想到像这样连接两个数字的唯一原因是将两个n-bit 数字转换为2n-bit 数字,在这种情况下,您通常会提前知道n 是什么,并且您可以使用位操作:return num1 << n | num2.

标签: python-3.x string algorithm type-conversion concatenation


【解决方案1】:

当然会有不同!自己看很容易:

import timeit

def concatenate1(num1, num2):
    num1 = str(num1)
    num2 = str(num2)
    num1 += num2
    return int(num1)

def concatenate2(num1, num2):
    digits = len(str(num2))
    num1 = num1 * (10**digits)
    num1 += num2
    return num1


num1 = int("1"*10000)
num2 = int("2"*10000)

t1 = timeit.timeit('concatenate1(num1, num2)', 'from __main__ import concatenate1, num1, num2', number=100)
t2 = timeit.timeit('concatenate2(num1, num2)', 'from __main__ import concatenate2, num1, num2', number=100)

print("t1=", t1, "; t2=", t2)
# Output:
# t1= 0.772663099996862 ; t2= 0.30340000000433065

很明显,t1t2 的 2 倍。也许大的时间槽正在将数字串起来?让我们尝试另一个使用math.log10 计算位数的实现:

import math
def concatenate3(num1, num2):
    l = math.log10(num2)
    digits = int(l) + 1
    num1 = num1 * (10**digits)
    num1 += num2
    return num1

将此函数与其他两个函数定时给出:

t1= 0.8134238999919035 ; t2= 0.32444419999956153 ; t3= 0.11973479999869596

这似乎支持了将整数转换为字符串需要很多时间的论点。

为什么会这样?因为将整数转换为字符串涉及使用十进制数字解释存储中的二进制值,然后为每个数字分配字符。将两个数字相加时,只需将它们的二进制数字相加即可。

您将在编译的解释语言中看到这种行为。但是,不同的语言会根据其内部工作方式的不同,对不同的方法有不同程度的减速。

【讨论】:

    【解决方案2】:

    虽然@pranav-hosangadi 提供的答案很棒,但我赞同他的说法,即会有不同。

    我想指出的是,对于一组输入来说,“最好/最快”的解决方案可能并不是对所有输入都是最好的。在寻找更好/更快的解决方案时,了解输入数据的“外观”可能很重要。

    对于@pranav-hosangadi 选择的输入(非常大的数字字符串),他的concatenate3() 显然是最快的。考虑到问题是关于确定“大”数字的某些内容,他们对输入的选择似乎非常合理。

    但是,如果定义大数的问题是最多十位数的问题(在某些情况下仍然很大),那么他们的 concatenate3() 方法实际上比前两种方法和第四种方法慢:

    def concatenate4(num1, num2):
        return int(f"{num1}{num2}")
    

    比这三个都快(至少在我的笔记本电脑上)。

    同样重要的是,这个新的concatenate4() 并不是普遍更快,并且对于@pranav-hosangadi 使用的范围内的输入,它的性能“很差”。而他们的concatenate3() 则粉碎了其他人。

    import timeit
    import math
    
    def concatenate1(num1, num2):
        num1 = str(num1)
        num2 = str(num2)
        num1 += num2
        return int(num1)
    
    def concatenate2(num1, num2):
        digits = len(str(num2))
        num1 = num1 * (10**digits)
        num1 += num2
        return num1
    
    def concatenate3(num1, num2):
        l = math.log10(num2)
        digits = int(l) + 1
        num1 = num1 * (10**digits)
        num1 += num2
        return num1
    
    def concatenate4(num1, num2):
        return int(f"{num1}{num2}")
    
    num1 = int("1"*10)
    num2 = int("2"*10)
    
    t1 = timeit.timeit('concatenate1(num1, num2)', 'from __main__ import concatenate1, num1, num2', number=1000)
    t2 = timeit.timeit('concatenate2(num1, num2)', 'from __main__ import concatenate2, num1, num2', number=1000)
    t3 = timeit.timeit('concatenate3(num1, num2)', 'from __main__ import concatenate3, num1, num2', number=1000)
    t4 = timeit.timeit('concatenate4(num1, num2)', 'from __main__ import concatenate4, num1, num2', number=1000)
    
    print("t1=", 100_000*t1, "; t2=", 100_000*t2, "; t3=", 100_000*t3, "; t4=", 100_000*t4)
    

    结果:

    t1= 63.25999999999971 ; t2= 58.6299999999998 ; t3= 64.32999999999994 ; t4= 40.889999999999674
    

    在我的笔记本电脑上,concatinate3() 只有在组合每个超过 35 位的数字时才会变得最快。

    让我们尝试回答原因:

    让我们来看看concatenate3()concatenate4() 的各个步骤的大致时间安排。请注意,各个步骤的真实成本被函数调用开销所掩盖,因此是粗略的近似值,但它们仍然基于真实的时序测试并说明了一个有趣的点。

    对于超过 1000 次运行的 10 位数字concatenate3()

    def concatenate3(num1, num2):  # 59 time units (accurate)
        l = math.log10(num2)       # 18 time units
        digits = int(l) + 1        # 14 time units
        num1 = num1 * (10**digits) # 12 time units
        num1 += num2               #  7 time units
        return num1
    

    对于超过 1000 次运行的 10 位数字concatenate4()

    def concatenate4(num1, num2):  # 45 time units (accurate)
        tmp = f"{num1}{num2}"      # 30 time units
        tmp = int(tmp)             # 20 time units
        return tmp
    

    对于超过 1000 次运行的 1000 位数字concatenate3()

    def concatenate3(num1, num2):  # 1400 time units (accurate)
        l = math.log10(num2)       #   23 time units
        digits = int(l) + 1        #   15 time units
        num1 = num1 * (10**digits) # 1341 time units
        num1 += num2               # 2119 time units
        return num1
    

    对于超过 1000 次运行的 1000 位数字concatenate4()

    def concatenate4(num1, num2):  # 5700 time units (accurate)
        tmp = f"{num1}{num2}"      # 3500 time units
        tmp = int(tmp)             # 3100 time units
        return tmp
    

    那么,我们能看到什么?首先,乘法和加法本质上比连接和转换要快。这应该不足为奇。

    有趣的是,虽然计算 digits 的成本与输入的大小无关,并且随着输入的增加而消失在舍入误差中,但对于较小的输入,此成本成为总成本的重要组成部分。

    【讨论】:

    • 关于下划线数字文字的事情。整洁!
    • 为什么会这样?据我所知,格式使用了 f 字符串(我可能是错的)。那不也是转换吗?
    • @sajankumar 你是对的,def concatenate5(num1, num2): return int(f"{num1}{num2}") 对于 10 位字符串来说甚至更快。
    • IDK 为什么投反对票。我能想到的唯一可以改进此答案的方法是,如果您在 timeit() 调用中对这些情况使用更多循环(而不是乘以结果),因为您希望每个循环花费更少的时间。例如,使用number=1_000_000 得到t1= 1.1858979999960866 ; t2= 1.1826948999951128 ; t3= 1.256787299993448 ; t4= 0.7797783999994863——t1, t2, t3 始终处于同一个球场,t4 明显更小。无论如何,这个答案不应该是-1所以在这里,有一个upvote :)
    • @PranavHosangadi 好主意,我会把它增加到 1000 个循环
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-11-15
    • 1970-01-01
    • 2021-04-24
    • 2019-11-22
    • 1970-01-01
    • 1970-01-01
    • 2013-01-28
    相关资源
    最近更新 更多