こどもてるびぃ

 | 

2006-05-29

[]#4 解説

year = Regexp.build( 98, 99, 2000..2005 )
"04"   =~ year # => false
"2004" =~ year # => true
"99"   =~ year # => true

これを作るのに、Range をすべて整数に展開する方法がふつう思いつく。Regexp.buid_unfold がそれです。だけどこれだと

regexp = Regexp.build_unfold(0 .. 1000000)

とかやるのは無理なので、もっと賢く整数 Range正規表現に変換する方法を考える。こういう難しそうな問題は最初に簡単なケースに絞って考えると良い、とかそれっぽいことを言ってみる。

とりあえず0以上の範囲だけ考える。firstlast が共に1桁、例えば (2 .. 8) なんかのときは簡単で、正規表現

[2-8]

と書ける。2桁の場合も、10の位が同じ、例えば (42 .. 48) だったら簡単。

4[2-8]

10の位が違う (42 .. 75) なんかのときは、この範囲を (42 .. 49) + (50 .. 69) + (70 .. 75) に分割すると同じ感じに扱える。

4[2-9]|[5-6]\d|7[0-5]

3桁の場合もいまの感じでやると、(139 .. 748) は下のように書ける。R(a .. b) を (a .. b) の正規表現とするよ。

1R(39 .. 99)|[2-6]\d{2}|7R(00 .. 48)

というわけで2桁の R(39 .. 99) と R(00 .. 48) に帰着できる。桁がもっと多くてもこの要領で桁を落として再帰していけばいい。ここまでをコードにしたのが DigitListRange#to_unsigned_regexp のこれ。わかりやすいかと思って Range の要素は 0 から 9 の数字の配列にしています。

def to_unsigned_regexp
  if @order == 1
    /[#{first[0]}-#{last[0]}]/
  elsif first[0] == last[0]
    /#{first[0]}#{self.class.new(first.drop(1), last.drop(1)).to_unsigned_regexp}/
  else
    regs = [
      /#{first[0]}#{self.class.new(first.drop(1), DigitList.max(@order - 1)).to_unsigned_regexp}/,
      /#{last[0]}#{self.class.new(DigitList.zero(@order - 1), last.drop(1)).to_unsigned_regexp}/
    ];
    if first[0] + 1 < last[0]
      regs << /[#{first[0] + 1}-#{last[0] - 1}]\d{#{@order - 1}}/
    end
    Regexp.union(*regs)
  end
end

あとは桁が違う場合とマイナスの場合を考慮すると終わり。

とこういう風に考えて解いた。自然なかんじにできたと思ったんだけど、サンプルのコードより相当長いね。

 |