LoginSignup
24
21

More than 5 years have passed since last update.

Ruby 「エラトステネスの篩」を通してアルゴリズムの威力を体感する

Last updated at Posted at 2014-05-05

Ruby で実装するエラトステネスのふるい 小波秀雄(pdf)
」の良質な記事を参照している

エラトステネスの篩とは素数を見つけ出すためのアルゴリズムである。
まず最初の素数である2の倍数を削除していく、次に残った数の最初に来る数、3の倍数を削除していく。次にくる数、5の倍数を削除していく。
同じような動作を延々と続けて素数を見つける。

この記事はエラトステネスの篩を通してアルゴリズムの威力を体感する事が目的

require 'benchmark'
def erato_1(n)
  numbers = (2..n).to_a # 2 ~ n の配列を用意
  primes = [] # 素数を放り込むための配列
  loop do
    d = numbers.shift # 配列の先頭要素を取り出して
  break if d == nil # nil なら終了
    primes << d # 素数の配列に追加。
    to_delete = [] # 取り除く候補を入れる配列
    numbers.each do |x|
      to_delete << x if x % d == 0 # d の倍数を取り除く候補に入れる
    end
    numbers -= to_delete # d の倍数がそっくり引かれる
  end
  return primes
end

puts Benchmark.measure{ erato_1(10000) }

=>  0.120000   0.000000   0.120000 (  0.120805)

rubyのプロファイラでチェックする、ファイルの先頭に
#!/usr/local/bin/ruby # -r profileを加えてコードを実行する.


time   seconds   seconds    calls  ms/call  ms/call  name
 57.31    26.15     26.15   777862     0.03     0.16  Object#erato_1
 25.68    37.87     11.72     1230     9.53    36.90  Array#each
  8.99    41.97      4.10   777860     0.01     0.01  Fixnum#==
  7.69    45.48      3.51   776631     0.00     0.00  Fixnum#%
  0.15    45.55      0.07     9999     0.01     0.01  Array#<<
  0.11    45.60      0.05     1229     0.04     0.04  Array#-
....下記省略

if x % d == 0の条件判断に時間がかかっている、
またprime = [ ]のところがnumbersにデータが入っているので不要である

require 'benchmark'
def erato_2(n)
  numbers = (2..n).to_a
  i = 0
  loop do
  break unless d = numbers[i]
    to_delete = []
    (2*d..n).step(d){|e| to_delete << e}
    numbers -= to_delete
    i += 1
  end
  return numbers
end

puts Benchmark.measure{
  erato_2(10000) }
=> 0.090000   0.010000   0.100000 (  0.097825)

少しだけはやくなった
ただ#erato_2にはまだ無駄がある、例えば100までの素数を調べたいとして、2,3,5,7の倍数を削除した時点で100までの素数はすべてそろっている状態であるがそのあとの11、13、、以降の倍数を削除している処理が無駄である

def erato_3(n)
  sqrt = Math.sqrt(n).floor
  numbers = (2...n).to_a
  i = 0
  loop do
    break if i >= sqrt
    d = numbers[i]
    to_delete = []
    (2*d..n).step(d){|e| to_delete << e}
    numbers -= to_delete
    i += 1
  end
  return numbers
end

puts Benchmark.measure{ erato_3(10000) }
=> 0.020000   0.000000   0.020000 (  0.026519)

3倍近く早くなった、だがまだ早くする余地がある
numbers -= to_deleteの箇所で処理量が増えているのでここの箇所をなくす

def erato_4(n)
  numbers = (0..n).to_a
  numbers[0],numbers[1] = nil
  numbers.each do |d|
    next if d == nil
    break if (Math.sqrt(n) < d)
    (2*d..n).step(d){|e| numbers[e] = nil}
  end
  numbers.compact!
  return numbers
end
puts Benchmark.measure{ erato_4(10000) }
=> 0.010000   0.000000   0.010000 (  0.003047)

8倍ぐらい早くなった、アルゴリズムは深い、、、、

24
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
21