トップ «前の日記(2012-06-25) 最新 次の日記(2012-07-25)» 編集

Route 477



2012-07-23

[ruby] 円周率を頭から順に計算するやつをRuby 2.0に移植してみた

Ruby 2.0に入る予定のEnumerable#lazyを使って移植してみた。*1

def stream(nxt, safe, prod, cons, seed, strm)
  Enumerator.new{|yielder|
    loop do
      y = nxt[seed]
      if safe[seed, y]
        seed = prod[seed, y]
        yielder << y
      else
        seed = cons[seed, strm.next]
      end
    end
  }
end

def extr(a, x)
  q, r, s, t = *a
  return (Rational(q) * x + Rational(r)) / (Rational(s) * x + Rational(t))
end

UNIT = [1, 0, 0, 1]

def comp(a, b)
  q, r, s, t = *a
  u, v, w, x = *b
  return [q*u+r*w, q*v+r*x, s*u+t*w, s*v+t*x]
end

def pi
  init = UNIT
  lfts = (1..Float::INFINITY).lazy.map{|k| [k, 4*k+2, 0, 2*k+1]}
  nxt = ->(z){ extr(z, 3).floor }  # Integer -> Integer
  safe = ->(z, n){ n == extr(z, 4).floor }  # Integer, Integer -> bool
  prod = ->(z, n){ comp([10, -10*n, 0, 1], z) }
  cons = ->(z, zz){ comp(z, zz)}
  return stream(nxt, safe, prod, cons, init, lfts)
end

p pi.take(100)

ほぼHaskell版の素移植。最初、「%」をmodだと勘違いしていてちょっと手間取った。

 lfts = (1..Float::INFINITY).lazy.map{|k| [k, 4*k+2, 0, 2*k+1]}

という行がlazyの使用例で、1..∞という無限Rangeをmapして無限列を作っている。

で、streamというメソッドがこの無限列を円周率の各桁を表す無限列([3,1,4,1,5,...])に変換するのだけど、 こっちはなんかうまく書けなくてEnumerator.newを使った。最初はlazy.mapで書けるのかと思ったのだが、 1桁求めるのに元の列を複数個消費するので、mapではだめそうだった。

*1 なお、アルゴリズムの中身については一切理解してない