Rubyで有限オートマトン

Rubyによる情報科学入門」(久野靖)に、与えられた文字列がある規則に当てはまるかどうかを判定するという課題を例にした有限オートマトンの使い方というのがある。正規表現ぽい。

演習をやってみた。ちょっと元のコードが好みと違ったので、クラスを使って書き直してみた。

class Automata

  def initialize(atm)
    @atm = atm
  end

  def accept?(str)
    cur = 0
    str.length.times do |i|
      n = @atm[cur][str[i]]
      if n
        cur = n
      else
        return false
      end
    end
    return @atm[cur][:final] == true
  end

end

if __FILE__ == $0
  a = Automata.new([{'a' => 1}, {'b' => 0, :final => true}])
  
  test_case = ["ab", "aba", "abab", "ababa", "ababab", "abababa",
               "b", "ba", "bab", 
               "abaab", "abaaba"]
  
  test_case.each do |pat|
    print pat, "\t: "
    print a.accept?(pat)
    print "\n"
  end
end

「aとbがさまざまに混ざって現れるが、aの連続は最大2個まで」とか「aとbがさまざまに混ざって現れるが、aの個数は偶数個」という文字列を受理するオートマトンを紙に書けと演習にあって、「えっ、そんなことが可能なの?」と一瞬、驚いた。そんなことが可能と思ってなかったけど、なるほど、オートマトンってそういうことかと思って紙に書き出して、実際にハッシュに入れてみたら、確かにちゃんと動いた。面白い。

「aとbがさまざまに混ざって現れるが、aの連続は最大2個まで」「aとbがさまざまに混ざって現れるが、aの個数は偶数個」というのは、次のような状態遷移図で書ける。

で、これらをコードにすると、

a = Automata.new([{'a' => 1, 'b' => 0, :final => true}, 
                  {'a' => 2, 'b' => 0, :final => true},
                  {'b' => 0, :final => true}])
a = Automata.new([{'a' => 1, 'b' => 0, :final => true}, 
                  {'a' => 0, 'b' => 1}])

出力はこんな感じ。

% ruby 6c.rb
ab      : false
aba     : true
abab    : true
ababa   : false
ababab  : false
abababa : true
b       : true
ba      : false
bab     : false
abaab   : false
abaaba  : true
aaaaab  : false
aab     : true
abababbababbabba        : false
abbbbbbabbbbaabbb       : true
abaabbaaabb     : true
%

Wikipediaを見ると、どうもDFAというやつか。NFAという状態遷移が非決定的なモデルもあるけど、NFAはDFAに変換可能で、実装ではそうするもんなんだとか。NFAがDFAに変換可能というのは、直感に反する気がする。