「エラトステネスの篩」で、素数を求めてみます。Ruby でやってみました。
極めて素直にコードを書いてみました。難しいことは何もないと思います。もう少し Ruby らしく書くこともできるでしょう。
eratosthenes.rb
def set_f(i, max) #素数でない数にフラグを立てる for j in 2..(Math.sqrt(max).to_i) $a[i * j] = 2 end end max = (ARGV[0] || 100).to_i $a = Array.new(max) for i in 2..max if $a[i] != 2 $a[i] = 1 #iは素数 set_f(i, max) end end for i in 2..max print i, " " if $a[i] == 1 end
結果
>ruby eratosthenes.rb 1000 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101 103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 211 223 227 229 233 239 241 251 257 263 269 271 277 281 283 293 307 311 313 317 331 337 347 349 353 359 367 373 379 383 389 397 401 409 419 421 431 433 439 443 449 457 461 463 467 479 487 491 499 503 509 521 523 541 547 557 563 569 571 577 587 593 599 601 607 613 617 619 631 641 643 647 653 659 661 673 677 683 691 701 709 719 727 733 739 743 751 757 761 769 773 787 797 809 811 821 823 827 829 839 853 857 859 863 877 881 883 887 907 911 919 929 937 941 947 953 967 971 977 983 991 997
1000万以下くらいの素数なら、充分速く求められると思います。
上のコードの最終 3行を以下のように替えれば、最大の素数だけ表示します。(無駄なループが殆どなので、改善の余地が大いにあります。)
for i in 2..max n = i if $a[i] == 1 end puts n
結果
>ruby eratosthenes.rb 10000000 9999991
最後を、こんな風に改善できるかな。結果は同じですが。
while $a[max] == 2 do max -= 1 end puts max
1000万以下の最大の素数を求めるのの、ベンチマークをやってみました。結果は
user system total real 6.536000 0.032000 6.568000 ( 6.601378)
だいたい 6.54秒ということですか。改善前より 1.2秒ほど速くなります。Benchmark モジュールを使ったのですが、何度もやると、多少のブレが出るのはどうしてなのだろう。まあ極めて微小な差ですが。
※追記(2017/6/16)
いまならこうも書くでしょう。Ruby は記述力が高くて楽しいですな。
def eratosthenes(n) ar = (0..n).to_a 2.upto(Math.sqrt(n).to_i) do |i| next if ar[i].zero? 2.upto(n / i) {|j| ar[i * j] = 0} end ar[2..-1].reject {|x| x.zero?} end p eratosthenes(1000)
ベンチマークしてみました。(2017/7/27)
require 'benchmark' Benchmark.bm do |x| x.report {eratosthenes(1000_0000)} end
結果。
user system total real 3.000000 0.010000 3.010000 ( 3.009530)
1000万以下の素数で、3秒ほどですか。以前の結果よりも速いですね。半分くらいの時間で済んでいますか。
※再追記(2017/10/16)
Ruby FFI を使って求めてみました。
※再々追記(2018/10/12)
こんなスマートな求め方もあります。関数型プログラミングっぽいですね。ただし、時間もすごくかかりますし、ちょっと大きい数を入れるとスタックオーバーフローします。
def sieve(x, *xs) ys = xs.reject {|y| (y % x).zero?} [x] + (ys.empty? ? [] : sieve(*ys)) end p sieve(*(2..50)) #=>[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
※再々々追記(2022/1/8)
def eratosthenes(n) sieve = [*0..n] sieve[0] = sieve[1] = nil (2..Integer.sqrt(n)).each do |i| next unless sieve[i] (i * i).step(n, i) { sieve[_1] = nil } end sieve.compact end p eratosthenes(1000)