【问题标题】:Speeding up greatest prime factor program in Ruby加速 Ruby 中最大的素数程序
【发布时间】:2017-01-13 07:32:42
【问题描述】:

一个在 Ruby 中求最大素因数的简单程序,由 2 个方法组成:

def is_prime?(n)
  (2..n).select {|number| n % number == 0}.length == 1 ? true : false
end

def prime_factors(number)
  (1..number).select {|m| number % m == 0 && is_prime?(m) == true}.max
end

它适用于像 100 这样的小数字。但是,我正在尝试解决 Project Euler 上的问题,它使用数字 600851475143。尝试这个时,问题甚至不会运行,我最终在大约 a 后取消它分钟。

我怎样才能改变它以提高性能?

【问题讨论】:

  • 您的第二个方法名称具有误导性。

标签: ruby algorithm performance primes


【解决方案1】:

使用 Ruby 的Prime#prime_division 方法:

require 'prime'

Prime.prime_division(600851475143).max_by(&:first).first
  #=> 6857

三个步骤:

a = Prime.prime_division(600851475143)
  #=> [[71, 1], [839, 1], [1471, 1], [6857, 1]]
  # Note (71**1)*(839**1)*(1471**1)*(6857**1) #=> 600851475143  
b = a.max_by(&:first)
  #=> [6857, 1] 
b.first
  #=> 6857 

【讨论】:

  • 对于非常大的数字,Prime::EratosthenesGenerator 可能更快(例如Prime.prime_division(600851475143, Prime::EratosthenesGenerator.new)——不,我收回这一点,看起来默认生成器更快。也许 600851475143 不是很大? :-)
  • 在您的解释中,您使用 max 作为最后一步,而您在原始解决方案中使用了 first
  • @mwp,Cary,idk,什么时候只需输入wolframalpha.com/input/?i=greatest+prime+factor+of+600851475143lol
  • @גלעדברקן 如果您只需要一次性找到一个数的最大素因数,Alpha 就可以了,但是如果您在更大的过程中找到多个素因数,并且需要以编程方式执行此操作,我们到了。
  • @mwp 当然,我是在开玩笑,但是对于欧拉问题,试图提出更全面的算法解决方案不是重点吗?
【解决方案2】:

关于您的is_prime? 方法:

  1. Ruby 约定建议您将其命名为 prime?,而不是 is_prime?
  2. 它不能正确处理 n 为 1 或更少的情况
  3. #select 将在找到匹配项后继续迭代;为了获得最佳性能,它应该尽快跳出循环
  4. 一个快速的优化是对任何可以被二整除的数字返回 false
  5. 您的循环可以从三个开始,并且只迭代奇数
  6. 您的循环只需迭代到n 的平方根,而不是n

最近有一个类似的讨论here,我发布了一个更快的prime? 方法here

【讨论】:

  • 您说您在哪里发布了一种更快的方法,方法是单击我希望被带到一个受人尊敬的期刊的链接。 :-)
  • @CarySwoveland 哈哈!我正在为在那里使用什么动词而苦苦挣扎。您是否建议 Stack Overflow 的答案不被高度重视? ;)
  • 我只是说我不倾向于将我的 SO 答案添加到我的 履历
  • @CarySwoveland 但他们是经过同行评审的!
【解决方案3】:

有一种更有效的分解方法,它也只找到素因数,消除了测试素数的需要。跳到最后查看或通读以了解我是如何到达那里的。

我重构了您的解决方案,以便更容易思考,提取 factors 方法,删除不必要的布尔值显式使用并重命名一些东西:

def greatest_prime_factor(n)
  factors(n).select { |c| prime?(c) }.max
end

def factors(n)
  (1..n).select { |c| n % c == 0 }
end

def prime?(n)
  (2..n).count { |c| n % c == 0 } == 1
end

factors 效率低下:即使我们知道数字 c(代表“候选人”)是一个因素,我们仍会测试大于 n/c 的数字。为了解决这个问题,当我们找到一个因子时,在我们寻找下一个因子之前将 n 除以它:

def factors(n)
  if n == 1
    []
  else
    f = (2..n).find { |c| n % c == 0 }
    [f] + factors(n / f)
  end
end

(其他方法保持不变。)随着这一变化,整个程序在几毫秒内分解为 600851475143(不包括 Ruby 进程启动时间)。

为什么这种改变如此有效?不仅factors 很慢;原始程序在prime? 上花费了大约 75% 的时间。但是,虽然factors 的原始版本返回了一个数字的所有因数,包括素数和非素数,但factors 的新版本只返回素数。 (这是因为它累积了最小的剩余因子,并且最小的素因子总是小于最小的非素因子。)所以我们现在不仅分解更快,我们也有更少的因子来测试素数,我们是素数测试更小,测试素数所需的时间更短。

更重要的是,由于 factors 现在返回素数,我们根本不需要测试素数,所以让我们重命名 factors 以明确它返回素数并消除 prime?

def greatest_prime_factor(n)
  prime_factors(n).max
end

def prime_factors(n)
  if n == 1
    []
  else
    f = (2..n).find { |c| n % c == 0 }
    [f] + prime_factors(n / f)
  end
end

除了更短之外,它还更快,在我的机器上将 600851475143 分解为不到 1 毫秒(同样,不计算 Ruby 进程启动时间)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多