【问题标题】:Optimization of python code for calculating list of squared divisors计算平方除数列表的python代码优化
【发布时间】:2017-06-03 07:28:52
【问题描述】:

我在 codewars 网站上参加了一个 python 挑战。我遇到了以下挑战:

42 的除数是:1、2、3、6、7、14、21、42。这些除数的平方是:1、4、9、36、49、196、441、1764。平方和除数是 2500 即 50 * 50,一个正方形!

给定两个整数 m, n (1

结果将是一个数组数组,每个子数组有两个元素,第一个是平方除数为平方的数字,然后是平方除数之和。

输出应该是:

list_squared(1, 250) --> [[1, 1], [42, 2500], [246, 84100]]
list_squared(42, 250) --> [[42, 2500], [246, 84100]]
list_squared(250, 500) --> [[287, 84100]]

我编写了以下代码,其中包含两个附加功能:一个对应于确定一个数字的所有因数,另一个对应于检查一个数字是否是完全平方。

确定所有因素的功能:

def fact(m):
    return [i for i in range(1,m+1) if m%i == 0]

函数检查一个数是否是完全平方,如果不是则返回0,否则返回平方根

def square_root(x):
    ans = 0
    while ans < x // 2 + 1:
        ans = ans + 1

        if ans*ans == x:
            return ans
            break;
    return 0

计算所需结果的函数

def list_squared(m, n):
    # your code
    fac=[]
    for i in range(m,n+1):
        sq_sum = sum(list(map(lambda x: x**2,fact(i))))
        if square_root(sq_sum) != 0:
            fac.append([i,sq_sum])
    return fac

这段代码给了我正确的结果,但是它太长了。我能够通过所有测试结果,但我花了大约 6000 毫秒。当我尝试提交代码时,网页提交返回算法效率低下,耗时超过 1200 毫秒,这是最大值。

如果有人能为此指出更好的算法,我将不胜感激。

【问题讨论】:

  • 两个挑剔:在return ans 之后不需要break,因为这将结束函数。 ans = ans+1 也可以缩短为 ans+=1
  • also sum(list(map(lambda x: x**2,fact(i)))) => sum(map(lambda x: x**2,fact(i))) 无需转换为列表即可传递给 sum
  • ans +=1 比 ans = ans + 1 慢吗?按照@numbermaniac 的建议,我将后者更改为前者。现在我的示例测试运行时间变得慢了很多。
  • 我不认为它会更慢。您是否也根据 Jean 的建议更改了 sum?如果有,不知道是不是这个原因。

标签: python arrays algorithm


【解决方案1】:

您的代码有几处优化,但最大的优化是在ans*ans 变得大于x 时停止:

def square_root(x):
    ans = 0
    while True:
        ans += 1
        sqans = ans*ans
        if sqans == x:
            return ans
        elif sqans > x:
            return 0

while 中的条件可以删除,因为现在测试是在平方值上完成的。

通过优化,250, 500 案例的时间从 8 秒下降到 0.07 秒。

但这仍然不能令人满意。一般情况下,包含break或return条件的算法至少是O(n),即使可以节省时间,复杂度也太高了。

你可以通过简单地检查圆角平方根的平方来做得更好:

def square_root(x):
    ans = int(x**0.5 + 0.5)  # rounded just in case it goes below the actual value (float inaccuracy)
    sqans = ans*ans
    return 0 if sqans !=x else x

我将执行时间再除以 2(由 Optimized way to find if a number is a perfect square 确认)

除此之外(速度没有那么快,但值得一提):

无需在sum 中将map 转换为list

sq_sum = sum(map(lambda x: x**2,fact(i)))

fact 也可以避免循环到最大数量。循环到最大数除以 2 并将最大数添加到列表中是等效的。最大数/2以上不再存在除数

def fact(m):
    return [i for i in range(1,m//2+1) if m%i == 0] + [m]

最终编辑:由于fact 中使用的列表理解,这仍然很慢。我可以通过使用生成器来大幅缩短时间,并在其外部添加m*m

def sqfact(m):
    return (i*i for i in range(1,m//2+1) if m%i == 0)

最终代码,现在运行得如此之快,我得到了 0 秒。

def sqfact(m):
    return (i*i for i in range(1,m//2+1) if m%i == 0)

def square_root(x):
    ans = int(x**0.5 + 0.5)
    return 0 if ans*ans !=x else x

def list_squared(m, n):
    # your code
    fac=[]
    for i in range(m,n+1):
        sq_sum = sum(sqfact(i)) + i*i  # add i square outside
        if square_root(sq_sum):
            fac.append([i,sq_sum])
    return fac

【讨论】:

  • 感谢您的 cmets。我可以通过将 square_root 函数修改大约 500 次来减少运行时间,尽管方式不同。关于 fact() 函数的注释也很有用,但它不会显着减少运行时间。即使有了这些改进,代码的效率似乎也不足以让 Web 提交接受它。
  • 检查我的编辑。检查正方形变得更加简单。
  • 感谢您的 cmets 和努力。尽管我仍然无法在代码战中提交网络提交,但我已赞成并接受了这个答案。它仍然需要更多优化。
  • 确实,还有其他一些大的优化。检查我的最终编辑。感谢您的接受,现在应该可以了。
  • 再次感谢。然而,这仍然是非常低效的。我现在已经提出了一个现在非常有效并且能够通过挑战的答案。请通过我的回答。感谢您的回复。
【解决方案2】:

我更新了效率非常低的事实函数。现在,我没有迭代到m 的全部值来找到它的因子,而是只上升到sqrt(m)。这极大地减少了运行时间。这背后的逻辑是微不足道的,所以我不详细说明。以下是对我有用的新代码。

def fact(m):

     #determining the lower factors i.e., smaller than sqrt(m)
    fac = [i for i in range(1, int(m**0.5) + 1) if m%i == 0]

     #determining the higher factors i.e., larger than sqrt(m)
    fac = fac + [m//i for i in range(1, int(m**0.5) + 1) if m%i == 0] 

    return sorted(list(set(fac))) #in order to get rid of duplicate factors

【讨论】:

  • 我已经测试过(没有列表理解)和 square_root 函数相形见绌的速度差异,我放弃了它。我想我应该用更大的数字进行测试,其中的差异很明显。你仍然需要我对square_root 函数的优化。 a,d 顺便说一句,我认为除非 m 是正方形,否则不能存在重复的因子,因此将 1 减去您的循环结束边界之一,您可以不用 set 的东西。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-09-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-12-04
  • 2015-08-19
  • 1970-01-01
相关资源
最近更新 更多