为什么Project Euler#10的Ruby代码如此之慢?

时间:2013-08-09 10:41:46

标签: ruby performance primes sieve-of-eratosthenes

我对Ruby比较陌生,但就语言而言似乎很简单。我正在使用Ruby进行Euler项目,我在以下方面遇到了很大的问题:

  

低于10的素数之和为2 + 3 + 5 + 7 = 17。   找出200万以下所有素数的总和。

我的代码:

beginning_time = Time.now
(1..10000).each { |i| i }

def isPrime(num)
    factors = 0
    primecount = 1
    while primecount <= num
        if (num%primecount == 0)
            factors += 1
        end
        if (factors > 2)
            return false
        end
        primecount += 1
    end
    return true 
end

def make_sieve(num)
    sieve = Array.new(num)
    summation = 0
    for i in 1..num
            if(isPrime(i) == true)
                summation += i
                puts i
                for x in i..num
                    if x%i == 0
                    # Go through entire array and make all multiples of i False
                        sieve[x] = false
                    else
                        sieve[i] = true                     
                    end
                end
            else
                # If i is NOT prime, move to the next number. in the For Loop
                next
            end
    end
    puts summation
end

make_sieve(2000000)


end_time = Time.now
puts "Time elapsed #{(end_time - beginning_time)*1000} milliseconds"

我认为我对筛子有正确的想法,但我真的不知道是什么让这个程序运行得如此之慢。我用20,000运行它需要大约15秒,这看起来仍然很慢,尽管输出速度比我输入2,000,000时快得多。

我在逻辑上或语法上或两者上都是错误的吗?

2 个答案:

答案 0 :(得分:2)

你的isPrime()测试在素数上非常慢;但你甚至不需要它。筛选的关键是,最初所有数字都标记为素数;然后对于每个素数,我们标记它的所有倍数。因此,当我们在筛子中找到某个条目时,我们已经知道它是否是素数 - 它是否标记为true是否为素数,或者是否标记为false为复合(a一些较小的素数的倍数。)

根本没有必要对它进行测试。

为了找到倍数,我们只计算:对于5,它是每个第5个条目之后; 7 - 每7。无需使用%运算符对其进行测试,只需立即设置为false即可。无需将其中任何一个设置为true,因为所有数字在开始时都设置为true

答案 1 :(得分:2)

你似乎在Ruby中编写JavaScript代码,并且缺少使Ruby如此优雅的微妙之处。你应该看看像Ruby Best Practices这样的东西,这是一个很轻松的阅读,但处理使用Ruby习语而不是强加另一种语言的概念。

正如已经说过的,Eratosthenes筛子的重点在于你只需从列表中删除所有化合物编号,只留下素数。没有必要检查每个元素的完整性。

这是一个Rubyish解决方案。它运行大约1.5秒。由数组元素N表示的数字N-1稍微复杂一点,因此(i+i+1 .. num).step(i+1)相当于(n * 2 .. num).step(n)

def make_sieve(num)

  sieve = Array.new(num, true)

  sieve.each_with_index do |is_prime, i|
    next if i == 0 or not is_prime
    (i+i+1 .. num).step(i+1) { |i| sieve[i] = false }
  end

  puts sieve.each_index.select { |i| sieve[i] }.map { |i| i+1 }.inject(:+)

end

make_sieve(2_000_000)

<强>输出

142913828923