以下是 Project Euler 问题#12的解决方案:
def factor this_number, number=nil, *factors
number = this_number if number.nil?
m=2
loop do
break factors << m if number % m == 0
m+=1
end
arr = factors.flatten
arr.inject(:*) != this_number ? factor(this_number, (number/factors.last), factors) : arr.uniq.collect {|k| arr.count(k)+1 }.inject(:*)
end
def highly_divisible_triangular_number number
n = 2
loop do
num = (n*(n+1)/2)
r = factor(num)
break num if (r >= number)
n+=1
end
end
puts Benchmark.measure { p highly_divisible_triangular_number(n) }
这个解决方案是由Zachary Denton提供的:
require 'mathn'
class Integer
def divisors
return [1] if self == 1
primes, powers = self.prime_division.transpose
exponents = powers.map{|i| (0..i).to_a}
divisors = exponents.shift.product(*exponents).map do |powers|
primes.zip(powers).map{|prime, power| prime ** power}.inject(:*)
end
divisors.sort.map{|div| [div, self / div]}
end
end
triangles = Enumerator.new do |yielder|
i = 1
loop do
yielder.yield i * (i + 1) / 2
i += 1
end
end
puts Benchmark.measure { p triangles.detect { |t| t.divisors.count > n } }
基准测试结果:
__projecteuler: ruby 'problem_12a.rb' 100
73920
0.010000 0.000000 0.010000 ( 0.009514) [MINE]
73920
0.020000 0.000000 0.020000 ( 0.028339)
__projecteuler: ruby 'problem_12a.rb' 200
2031120
0.120000 0.000000 0.120000 ( 0.123996) [MINE]
2031120
0.250000 0.010000 0.260000 ( 0.251311)
__projecteuler: ruby 'problem_12a.rb' 300
2162160
0.120000 0.000000 0.120000 ( 0.122242) [MINE]
2162160
0.260000 0.000000 0.260000 ( 0.259200)
__projecteuler: ruby 'problem_12a.rb' 400
17907120
0.730000 0.000000 0.730000 ( 0.725883) [MINE]
17907120
1.050000 0.000000 1.050000 ( 1.057079)
__projecteuler: ruby 'problem_12a.rb' 500
76576500
2.650000 0.010000 2.660000 ( 2.657921) [MINE]
76576500
2.790000 0.000000 2.790000 ( 2.795859)
__projecteuler: ruby 'problem_12a.rb' 600
103672800
3.470000 0.010000 3.480000 ( 3.484551) [MINE]
103672800
3.420000 0.000000 3.420000 ( 3.419714)
__projecteuler: ruby 'problem_12a.rb' 700
236215980
7.430000 0.010000 7.440000 ( 7.438317) [MINE]
236215980
6.020000 0.020000 6.040000 ( 6.046869)
__projecteuler: ruby 'problem_12a.rb' 800
842161320
24.040000 0.020000 24.060000 ( 24.062911) [MINE]
842161320
14.780000 0.000000 14.780000 ( 14.781805)
问题
从基准测试结果中我可以看出,我的解决方案在N 500
之前更快,但在&gt; N时被消灭。有人能帮助我理解为什么吗?
的 更新
对于那些认为瓶颈与递归有关的人,请再试一次。没有递归的factor
方法和基准确实不显示改进:
__projecteuler: ruby 'problem_12a.rb' 800
842161320
24.960000 0.020000 24.980000 ( 24.973017) [MINE (w/o recursion)]
842161320
14.780000 0.030000 14.810000 ( 14.807774)
def factor this_number
number,arr=this_number,[]
done = false
until done
m=2
loop do
break arr << m if number % m == 0
m+=1
end
arr.inject(:*) != this_number ? number = number/arr.last : done = true
end
arr.uniq.collect {|k| arr.count(k)+1 }.inject(:*)
end
答案 0 :(得分:1)
Ruby以递归表现糟糕而臭名昭着。您也许可以阅读this post以获得有关如何解决它的一些指示。
答案 1 :(得分:1)
您的瓶颈在于您的分解方法。 (您的highly_divisible_triangular_number
循环和他的triangles.detect
循环基本相同。)
您的分解可以在计算上更快
(抱歉,我只需要重构你的代码。一切都在计算上都是一样的。)
def num_factors(number, dividend=nil, prime_factors_found=[])
dividend = number if dividend.nil?
smallest_prime_factor = (2..dividend).find { |divisor| dividend % divisor == 0 } # 1.
prime_factors_found << smallest_prime_factor
all_prime_factors_found = prime_factors_found.inject(:*) == number
if !all_prime_factors_found
return num_factors(number, dividend / prime_factors_found.last, prime_factors_found)
end
prime_factor_powerset_size = prime_factors_found.uniq.map do |prime_factor|
prime_factors_found.count(prime_factor) + 1 # 2.
end.inject(:*)
return prime_factor_powerset_size
end
divisor
为11(素数)时,您将完成整个循环,以便再次发现11确实是素数。最坏情况,对于n
素数,您最多可以n^2
验证所有素数。map
是一个完整的素数因子。但是每次传递,count
操作也需要通过数组。这意味着对于包含10个项目(O(n)
)的数组,您可以在数组中查找值100次(O(n^2)
)。检查事件可以在一次通过中完成。 他的因子分解可以节省计算(以及不计算的地方)
您可能想知道为什么他的代码更快,因为它包含以下嵌套循环:
divisors = exponents.shift.product(*exponents).map do |powers|
primes.zip(powers).map{|prime, power| prime ** power}.inject(:*)
end
与#2一样,他对另一个线性传递中的每个元素进行了线性传递工作,即计算的这部分也是O(n^2)
。当所有需要的是最终将会有的总数时,他通过实际计算所有除数来浪费计算。但无论如何,最终看来这一部分在渐近性能方面是相同的。
事实证明,最终让他突破的版本之间的差异是#1 - 主要测试人员/发电机。虽然您有一个自定义循环,一遍又一遍地检查每个数字,但他使用了a prime tester/generator in the Ruby library。如果您想了解它是如何实现的,您可以在计算机上找到安装了ruby的源<ruby root>/lib/ruby/<version>/prime.rb
。
以下是使用prime_division method
:
require 'prime'
def num_factors2(number, dividend=nil, prime_factors_found=[])
dividend = number if dividend.nil?
smallest_prime_factor = dividend.prime_division.first.first # this is the only change
prime_factors_found << smallest_prime_factor
all_prime_factors_found = prime_factors_found.inject(:*) == number
if !all_prime_factors_found
return num_factors2(number, dividend / prime_factors_found.last, prime_factors_found)
end
prime_factor_powerset_size = prime_factors_found.uniq.map do |prime_factor|
prime_factors_found.count(prime_factor) + 1
end.inject(:*)
return prime_factor_powerset_size
end
我系统的基准测试:
Benchmark.measure { (2..50000).map { |i| num_factors(i) } }
=> 17.050000 0.020000 17.070000 ( 17.079428)
Benchmark.measure { (2..50000).map { |i| num_factors2(i) } }
=> 2.630000 0.010000 2.640000 ( 2.672317)
<强>附加强>
使用两种方法的组合,我发现了一种很酷的方式来表达问题,就像红宝石中的单行一样! (那也很快!)
n = (2..(1.0/0)).find { |i| (i*(i+1)/2).prime_division.inject(1) { |p,i| p*i[1]+p } > 500 }
其中n
是n
三角形数字,即triangle(n) = n * (n + 1) / 2
。