NFA -> DFA(3)

いろいろ参考にしながら何とか NFA -> DFA の変換まで書いた。

  • 状態とアルファベットを型変数とできた。また、集合は集合として Data.Set を使った。個人的に満足。
  • 以下のような書き方ができることを知ったのが収穫。これが分からず、MultiParameterTypeClass とか ScopedTypeVariables とか、明後日の方向を調査していた。
class FA fa where
    states :: fa a b -> Set a
  • ループと手続き型言語用に書かれたアルゴリズム再帰に書き直す方法がいまいちうまくできていない。CL だと普通にloopマクロが使えるのだけど。
  • foldl とかzipが使えて幸せ。
  • Haskell にマクロが無いのが残念だ。わたしの腕の問題だけど、ボイラープレートが無くせない。マクロなら、、
  • Haskell の識別子にはせめて「-」ハイフンが使えるべき。キャメルノーテーション、カッコわるい。
  • Test.QuickCheck を使わなくちゃ。DFA -> NFA -> DFA としても受理する文字列が変わらない、とか、rename 前後で受理する文字列が変わらない、とか、常に成り立つ性質を探す必要がある。
  • Functor を使いたい。

参考

  • "Regular Expressions and Automata using Haskell", Simon Thompson @nfunato さんに教えて頂いた。まだちゃんと読めていないけど、これよりも美しく書きたい。@nfunato さんの実装がとても参考になった。まだまだわたしの知らない Haskell の要素がたくさん、、勉強させてもらおう。
    • 今のところわたしのはドラゴンブックそのものの NFA -> DFA 変換になっている。
  • fixpoint ‥? 謎。

コード

そのうち github へ。

{-
  by @quasicrane 2012/05/03
-}

module FA where
import qualified Test.HUnit as Test
import qualified Data.Set as S
import Data.Set (Set,member,notMember,elems,singleton,empty,fromList)
import Data.Char
import Test.QuickCheck

class FA fa where
    states :: fa a b -> Set a
    alphabet :: fa a b -> Set b
    start :: fa a b -> a
    final :: fa a b -> Set a
    size :: fa a b -> Int
    size a = S.size (states a)
    stateMap :: (Ord a1, Ord a2, Ord b) => (a1 -> a2) -> fa a1 b -> fa a2 b
    rename :: (Ord a1, Ord a2, Ord b) => [a2] -> fa a1 b -> fa a2 b
    rename as fa = stateMap conv fa
        where
          conv = \q -> case (lookup q (zip (elems (states fa)) as)) of
                          Just x -> x

-- 狭義の DFA
data DFA a b = DFA
    (Set a) -- 状態の有限集合。
    (Set b) -- アルファベット。文字の集合。
    (a -> b -> a) -- 遷移関数。
    a -- 初期状態。
    (Set a) -- 受理状態の集合。状態の部分集合。

-- NFA
data NFA a b = NFA
    (Set a) -- 状態の有限集合。
    (Set b) -- アルファベット。文字の集合。
    (a -> b -> Set a) -- 遷移関数。
    (a -> Set a) -- ε-遷移関数。
    a -- 初期状態。
    (Set a) -- 受理状態の集合。状態の部分集合。

instance FA DFA where
    states (DFA qset _ _ _ _) = qset
    alphabet (DFA _ cset _ _ _) = cset
    start (DFA _ _ _ q0 _) = q0
    final (DFA _ _ _ _ fset) = fset
    stateMap f (DFA qset cset d q0 fset) = DFA qset' cset d' q0' fset'
        where
          qset' = S.map f qset
          d' = mkdelta [ ((f q, a), f (d q a)) | q <- elems qset, a <- elems cset ]
          q0' = f q0
          fset' = S.map f fset

instance FA NFA where
    states(NFA qset _ _ _ _ _) = qset
    alphabet (NFA _ cset _ _ _ _) = cset
    start (NFA _ _ _ _ q0 _) = q0
    final (NFA _ _ _ _ _ fset) = fset
    stateMap f (NFA qset cset d e q0 fset) = NFA qset' cset d' e' q0' fset'
        where
          qset' = S.map f qset
          d' = mkdelta' [ ((f q, a), elems (S.map f (d q a))) |
                          q <- elems qset, a <- elems cset ]
          e' = mkepsilon [ (f q, elems (S.map f (e q))) | q <- elems qset ]
          q0' = f q0
          fset' = S.map f fset

showDFA (DFA qset cset d q0 fset) =
    "DFA\n " ++ "States: " ++ show (elems qset)
                 ++ "\nq0: " ++ show (q0)
                 ++ "\nfs: " ++ show (elems fset)
                 ++ "\ndelta: \n"
                 ++ concat (map mkstr table)
    where table = [ (q, a, d q a) | q <- elems qset, a <- elems cset ]
          mkstr (q, a, r) = "\t" ++ (show q) ++ " " ++ (show a) ++ " => "
                            ++ (show r) ++ "\n"

instance (Show a, Show b) => Show (DFA a b) where
     show = showDFA

showNFA (NFA qset cset d e q0 fset) =
    "NFA\n" ++ "States:\t" ++ show (elems qset)
                  ++ "\nq0:\t" ++ show (q0)
                  ++ "\nfs:\t" ++ show (elems fset)
                  ++ "\ndelta:\n"
                  ++ concat (map mkstr table)
                  ++ concat (map mkstre etable)
    where table = [ (q, a, d q a) | q <- elems qset, a <- elems cset ]
          mkstr (q, a, r) = "\t" ++ (show q) ++ " " ++ (show a) ++ " => "
                            ++ (show r) ++ "\n"
          etable = [ (q, e q) | q <- elems qset, a <- elems cset ]
          mkstre (q, r) = "\t" ++ (show q) ++ " e => " ++ (show r) ++ "\n"

instance (Show a, Show b) => Show (NFA a b) where
    show = showNFA

-- construct S.Set from list
mkset a = fromList a

mkdelta alist =
    \q c -> case lookup (q, c) alist of
               Just q' -> q'
               Nothing -> error ("Invalid transition")

mkdelta' alist =
    \q c -> case lookup (q, c) alist of
               Just q' -> mkset q'
               Nothing -> empty

mkepsilon alist =
    \q -> case lookup q alist of
             Just q' -> mkset q'
             Nothing -> empty

-- TODO ; validation
makeDFA qs cs alist q0 fs = DFA (mkset qs) (mkset cs) (mkdelta alist) q0 (mkset fs) 
makeNFA qs cs alist e q0 fs =
    NFA (mkset qs) (mkset cs) (mkdelta' alist) (mkepsilon e) q0 (mkset fs)

accept (DFA qset cset d q0 fset) str = accept' q0 str
    where
      accept' q [] = member q fset
      accept' q (s:ss) = accept' (d q s) ss

dfaUnion (DFA qset1 cset1 d1 q1 fset1) (DFA qset2 cset2 d2 q2 fset2)
    | (cset1 == cset2) = DFA qset cset d q0 fset where
    qs1' = elems qset1
    qs2' = elems qset2
    qset = mkset [ (r1, r2) | r1 <- qs1', r2 <- qs2' ]
    cset = cset1 -- same alphabet
    q0 = (q1, q2)
    fset = mkset [ (r1, r2) | r1 <- qs1', r2 <- qs2',
                   (member r1 fset1) || (member r2 fset2)]
    d = mkdelta [ (((r1, r2), a), (d1 r1 a, d2 r2 a)) | r1 <- qs1', r2 <- qs2',
                  a <- elems cset]

-- generate states like ["p0", "p1", ..]
gennames prefix = map (\x -> prefix ++ show x) [0..]
rename' a = rename (gennames "q") a

powerset [] = [[]]
powerset (x:xs) = yss ++ map (x:) yss
    where yss = powerset xs

subset qset = map mkset $ powerset $ elems qset

eclosure e rset = f (elems rset) rset
    where
      f [] res = res
      f (t:ts) res = g (elems (e t)) ts res
      g [] ts res = f ts res
      g (u:us) ts res = if (notMember u res)
                        then
                            g us (u:ts) (S.insert u res)
                        else
                            g us ts res

move d rset c = S.unions [ d r c | r <- elems rset ]

convNFAToDFA (NFA qset cset d e q0 fset) = iter di (unmarked di) []
    where
      ecl = eclosure e
      move' = move d
      extractState = S.map fst
      isMarked (_, b) = b
      mark s set = S.insert (s, True) (S.delete (s, False) set)
      unmarkedSet set = extractState (S.filter (not . isMarked) set)
      unmarked set = elems (unmarkedSet set)
      mayAddState uset dset | member uset (extractState dset) = dset
                            | otherwise = S.insert (uset, False) dset
      q0' = ecl (singleton q0)
      di = singleton (q0', False)
      cs' = elems cset
      iter dset [] alist = DFA qset' cset (mkdelta alist) q0' fset'
          where
            qset' = extractState dset
            fset' = S.filter (\q -> any (\r -> member r fset) (elems q)) qset'
      iter dset (t:ts) alist = iter dset' (unmarked dset') alist'
          where
            dset' = foldl (\s c -> mayAddState (ecl (move' t c)) s) (mark t dset) cs'
            alist' = alist ++ [ ((t, c), ecl (move' t c)) | c <- cs' ]