素数列を enumerable ぽく作る

下から数えて1万番目の素数を求めよ、という何を求められているのか実はよく分からないコーディング課題に対してRubyで以下のように書いてみた。PrimeSeq[n]で、n番目の素数が得られる。

class PrimeSeq
  include Enumerable

  def initialize
    @primes = [2, 3]
    @i = 3
  end

  def [](idx)
    if @primes.size >= idx
      @primes[idx - 1]
    else
      take(idx - @primes.size).last
    end
  end

private

  def each
    loop do
      loop do
        @i += 2
        if @primes.none? {|p| (@i % p == 0)}
          @primes << @i
          break
        end
      end
      yield @i
    end
  end
end

if __FILE__ == $0
  prime = PrimeSeq.new
  (1..1000).each {|i| p prime[i]}
end

何となく、enumerable にすることで列っぽいものが扱えるだろうと思って、each を実装したけど、結局内部的に take のために使っているだけになった。ただ、列生成を再開すべきポインタを自分で意識的に保持しておかなくても「続きのm個を持って来い」と言えるのは良い、ように思うのだけど、いきなり self に対して take しているとか、しかもその take が暗黙的に private の each を呼んでるのだとか、そういう「Rubyだからね!」というやり方がいいのかどうか、よく分からないな。

そういえば、 MiniTestというのを使ってみた。しかし、RSpecとそんなに違うのかな、ていうか、また別のDSLなのか、ていうか、オレがやりたいのはassertなんだけど! という感じに萎え萎え気分。RSpecより速いらしいし、Ruby1.9標準というのがいいのかな。

require './primeseq.rb'
require 'minitest/autorun'

describe PrimeSeq do
  before do
    @p = PrimeSeq.new
  end

  describe "from 1st to 5th" do
    it "should be" do
      @p[1].must_be :==, 2
      @p[2].must_be :==, 3
      @p[3].must_be :==, 5
      @p[4].must_be :==, 7
      @p[5].must_be :==, 11
    end
  end

  describe "1000th, 2000th" do
    it "should be" do
      @p[1_000].must_be :==, 7919
      @p[2_000].must_be :==, 17389
      @p[1_000].must_be :==, 7919
    end
  end

  describe "fetch from the cache" do
    it "should be" do
      @p[2_000].must_be :==, 17389
      @p[2_000].must_be :==, 17389
      @p[1_000].must_be :==, 7919
    end
  end
end