読者です 読者をやめる 読者になる 読者になる

素数計算で言語の練習

Ruby

id:shawshank99に、素数を計算するプログラムを書いてみてよ、といわれ、素数ってなんだっけ?と聞き返すという駄目さ極まるところを見せてしまったので、題材は素数計算。ものすごく基礎的でガッカリですが、やらない事は忘れるので、基礎からナメまわすことにしました。それに、RSA暗号では素数使いますしね。問題としては、入力された数nまでの素数を出力するプログラムを書いてみます。

このコードでは、愚直にn回ループ内に2〜n回ループですから、計算量はO(n^2)です。

FIRST_PRIME = 2
gets.to_i.times do |i|
    if (FIRST_PRIME...i).each{|n|
      if i%n == 0
        break(false)
      end
    } && i >= FIRST_PRIME
     puts i
  end
end

変な書き方のif式ですね。なんとeachを条件式に使っています。これは、eachに戻り値があるため可能な動作です。ブロックの中では、調べたい値を順に割り、その余りが0ならば、breakしています。ここでbreakに引数falseを渡す事で、breakしたときのeachの戻り値がfalseになります。さらにRubyではfalseとnil以外はすべて真という解釈をするので、すべて割り切れなかったとき、すなわち素数のとき、真が帰る仕組みになっています。
しかし、これではちょっと気持ち悪い書き方です。次のようにメソッドに分離した方がいいでしょう。

FIRST_PRIME = 2
def is_prime(i)
  (FIRST_PRIME...i).each do |n|
      break(false) if i%n == 0 
  end && i >= FIRST_PRIME
end
gets.to_i.times do |i|
  if is_prime(i) 
     puts i
  end
end

is_primeメソッドの中身、each~endの最後に&&がついててキモイという人もいるかもしれません。これは次のように1行であると見ると、多少分かり易いでしょう.rubyではメソッドの最後に書いた式がそのメソッドの戻り値になるため、これでtrue or falseが返ります。

def is_prime(i)
  (FIRST_PRIME...i).each {|n| break(false) if i%n == 0 } && i >= FIRST_PRIME
end

1-linerだとこうなります。

gets.to_i.times {|i|   if ((2...i).each{|n| break(false) if i%n == 0} && i>= 2) then puts i end}

ついでなので、Fixnumクラスにis_prime?メソッドを追加してみます。そうすると、数.is_prime?と呼ぶだけでtrueかfalseか分かって便利。

class Fixnum
  FIRST_PRIME = 2
  def is_prime?
    (FIRST_PRIME...self).each {|n| break(false) if self%n == 0 } && self >= FIRST_PRIME
  end
end

gets.to_i.times do |i|
     puts i if i.is_prime?
end 

puts 3.is_prime?  # これはtrue
puts 50.is_prime? # これはfalse

しかし、このままでは遅すぎて使い物になりません。6桁以上のnを入力するとすべて計算するのにひどく待たされます。素数の計算で有名なアルゴリズムに、エラトステネスの篩というものがあるので、実装してみましょう。

#受け取った数列の先頭を素数列に追加し、その倍数にあたる数を削除して返す。
def push_and_delete(nums, primes)
  num = nums.shift
  return nums.delete_if{|x| x%num == 0}, primes << num
end

nums, primes = push_and_delete((2..gets.to_i).to_a,[])

while(nums.max > (primes.max**2))
    nums, primes = push_and_delete(nums, primes)
end

puts (primes + nums).join(",")

愚直バージョンに比べて劇的に早くなりました。
勉強のために、愚直バージョンをCやJavaで実装してみることにします。