sieve.rb

「そうねえ。素数を遅延評価で求めたりしてるわね」

よくわかる現代魔法 (集英社スーパーダッシュ文庫)

30分シリーズ、その10。今回は、遅延評価で素数を求めてみる。

$ ruby sieve.rb
[2, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33, 
 35, 37, 39, 41, 43, 45, 47, 49, 51, 53, 55, 57, 59, 61, 63, 65,
 67, 69, 71, 73, 75, 77, 79, 81, 83, 85, 87, 89, 91, 93, 95, 97,
 99, 101, 103, 105, 107, 109, 111, 113, 115, 117, 119, 121, 123,
 125, 127, 129, 131, 133, 135, 137, 139, 141, 143, 145, 147, 149,
 151, 153, 155, 157, 159, 161, 163, 165, 167, 169, 171, 173, 175,
 177, 179, 181, 183, 185, 187, 189, 191, 193, 195, 197, 199]

遅延評価は、SICPのストリームと同じ方法を使っている。

まず、Rubyで単方向リストを実現してみる。

Cons = Struct.new 'Cons',:car,:cdr
 
# [1,2]
Cons.new(1,Cons.new(2,nil))

次に、これを遅延ストリームにする。遅延ストリームは、cdr部分を関数にする。そして、値が必要になったときに、その関数を評価すると実現できる。

class Cons
  def initialize(car,cdr)
    @car = car
    @cdr = cdr
  end
 
  def car
    @car    
  end
 
  def cdr
    @cdr.call
  end  
end
 
# [1,2]
Cons.new(1,
         lambda{ Cons.new(2,
                          lambda{nil })})

あとは、このConsやcar、cdrをつかって適宜mapやfilterを作れば、遅延ストリームでプログラムができる。(今回はfilterだけ)

あと、素数の計算はエラトステネスの篩(ふるい)を使って求めている。

class Cons
  attr_reader :car
  def initialize(car,null=false,&cdr)
    @car = car
    @cdr = cdr
    @null = null
  end
  
  def null?
    @null
  end
 
  def cdr
    @cdr.call
  end
  
  def get(n)
    if n == 0 then
      car
    else
      self.cdr.get(n-1)
    end
  end
 
  def filter(&f)
    if null? then
      self
    elsif f.call car then
      Cons.new(car){ cdr.filter(&f) }
    else
      cdr.filter(&f)
    end
  end
  
  def to_a
    if null? then
      []
    else
      [@car] + cdr.to_a
    end
  end
 
  def slice(from,to)
    if from == to
      []
    elsif from > 0
      cdr.slice(from-1,to-1)
    else
      [@car] + cdr.slice(0,to-1)
    end
  end
end
 
# null要素を作る
$null = Cons.new nil,true
  
# 範囲を指定して、整数のリストを生成
def range(from,to)
  if from > to then
    $null
  else
    Cons.new(from){ range(from+1,to) }
  end
end
 
# 無限の整数リストの生成
def range_infty(from)
  Cons.new(from){ range_infty(from+1) }
end
 
# エラトステネスの篩
def sieve(list)
  Cons.new(list.car){
    list.cdr.filter{|x| x % list.car != 0 }
  }
end

p sieve(range_infty(2)).slice(0,100)
  • 素直にHaskell使った方が楽ですよ?
  • Schemeのマクロはすばらしい。これだとあまりに遅延ストリームの中身が露出しすぎている