在 Haskell 中表示非确定性有限状态机模拟器

Represent Nondeterministic Finite State Machine Simulator in Haskell

我在 Udacity 上关注 "programming languages",并试图在 Haskell 中展示问题集。答案写在Python:

edges = {(1,"a") : [2,3]
        ,(2,"a") : [2]
        ,(3,"b") : [3,4]
        ,(4,"c") : [5]}

accepting = [2,5]

def nfsmSim(string, current, edges, accepting):
    if string == "":
        return current in accepting
    else:
        letter = string[0]
        key = (current, letter)
        if key in edges:
            rest = string[1:]
            states = edges[key]
            for state in states:
                if nfsmSim(rest, state, edges, accepting):
                    return True
         return False

起始状态始终是第一个状态,即 current = 1

接受 "aaa""abc" 等字符串,而 "abb""aabc" 或拒绝。

我尝试使用 Haskell 重写:

nfsmSim [] c _  = [c]
nfsmSim xs c es = [concat $ nfsmSim (tail xs) s es | (k,ss) <- es, s <- ss, x <- xs, k==(c,x)]

我想要 return 一个整数列表,代表输入字符串末尾的最后状态,然后 filter 这些与接受状态相对应,并使用 any 得到最后的 TrueFalse.

我意识到这可能不是 Haskell 的方法,可能有更好的 wholemeal 解决方案。然而,作为一个初学者,我正在为 mondadic 机制而苦苦挣扎,而且很可能是这个问题的递归性质。

请使用 do 表示法而不是列表理解为我指出正确的方向。

让我们先考虑类型。您的 Python 函数或多或少具有以下类型:

type State   = Int
type Map k v = [(k,v)]

nfsmSim :: String -> State -> Map (Int, Char) [State] -> [State] -> Bool
nfsmSim string current edges accepting = …

我们可以对空字符串的情况使用模式匹配:

nfsmSim :: String -> State -> Map (Int, Char) [State] -> [State] -> Bool
nfsmSim "" current _ accepting = current `elem` accepting

对于非空的情况,我们和你的Python代码一样:

nfsmSim (x:xs) current edges accepting =
    let rest   = xs
        states = [s | (k,v) <- edges, k == (current,x), s <- v]
    in or [nfsmSim rest state edges accepting | state <- states]

然而,这并不容易使用。相反,让我们将 nfsmSim 写成高阶函数并改变参数的顺序:

nfsmSim :: (State -> Char -> [State])
        -> (State -> Bool)
        -> String
        -> State
        -> Bool

现在,我们必须提供一个函数,而不是边缘列表,该函数 returns 给定状态和字符的状态列表(可能为空),而不是接受状态列表,我们提供了一个 returns True 的函数。

对于空字符串的情况,没有太大的变化:

nfsmSim advance accept "" current = accept current

我们简单地使用我们的 State -> Bool 来检查我们当前的状态是否可以接受。

但是,现在我们的 StatenfsmSim 的最后一个参数,我们可以使用柯里化来使用您的 any 方法:

nfsmSim advance accept (x:xs) current = 
    any (nfsmSim advance accept xs) (advance current x)

请注意,携带所有参数有点笨拙。你通常会为此写一个工人:

nfsmSim :: (a -> b -> [a]) -> (a -> Bool) -> [b] -> a -> Bool
nfsmSim advance accept string current = go string current
  where
    go []     c = accept c
    go (x:xs) c = any (go xs) (advance c x)

顺便说一下,您仍然可以使用 "edges" 和 "accepting" 最后一个变体,

nfsmSimAccept string current edges accepting =
   let accept  c   = c `elem` accepting
       advance c x = [s | (k,v) <- edges, k == (c,x), s <- v]
   in nfsmSim advance accept string current

说明高阶函数更加灵活

这是我的 Haskell-ish 方法:

我们可以使用 haskell 的 Data.Set 和 Data.Map 库来表示我们的状态机。

import qualified Data.Map as M
import qualified Data.Set as S

让我们为状态机定义一些数据类型:

type State = Int
type Edge = (State, Char)
type Machine = (M.Map Edge (S.Set State), S.Set State)

我们这样定义机器:

myMachine :: Machine
myMachine = (M.fromList
    [ ((1, 'a'), S.fromList [2, 3])
    , ((2, 'a'), S.fromList [2   ])
    , ((3, 'b'), S.fromList [3, 4])
    , ((4, 'c'), S.fromList [5   ])
    ] , S.fromList [2, 5])

我们可以运行这样的机器:

runMachine :: String -> Machine -> State -> Bool
runMachine "" (_, acceptingStates) currentState =
    S.member currentState acceptingStates
runMachine (ch:rest) machine@(edges, _) currentState =
    case M.lookup (currentState, ch) edges of
        Nothing -> False
        Just nextStates ->
            or $ S.map (runMachine rest machine) nextStates

因为函数 returns a Bool,没有很好的理由使用 monad 或 do-notation。然而,如果我们使用类型 Maybe () 代替 Bool,这样的解决方案是可能的,其中 Just () 表示 TrueNothing 表示 False

首先,据我所知,没有"Non Finite State Machine"这样的东西。从你写的来看,我意识到它是关于 "Nondeterministic finite automaton (NFA)".

第一个变体。

nfa :: String -> Int -> [((Int, Char), [Int])] -> [Int] -> Bool
nfa       [] cur     _ acc = cur `elem` acc
nfa (c:rest) cur edges acc
    | Just states <- lookup (cur, c) edges = any (\state -> nfa rest state edges acc) states
    | otherwise                            = False

edges =
    [ ((1, 'a'), [2, 3])
    , ((2, 'a'), [2])
    , ((3, 'b'), [3, 4])
    , ((4, 'c'), [5])
    ]

accepting = [2, 5]

main = do
    print $ nfa "aaa" 1 edges accepting
    print $ nfa "abc" 1 edges accepting
    print $ nfa "abb" 1 edges accepting
    print $ nfa "aabc" 1 edges accepting

输出将是:

True
True
False
False

第二种变体:

import Control.Monad
import Data.Maybe

nfa2 :: String -> Int -> [((Int, Char), [Int])] -> [Int] -> [Int]
nfa2       [] cur     _ acc = guard (cur `elem` acc) >> return cur
nfa2 (c:rest) cur edges acc = do
    state <- fromMaybe mzero $ lookup (cur, c) edges
    nfa2 rest state edges acc

edges =
    [ ((1, 'a'), [2, 3])
    , ((2, 'a'), [2])
    , ((3, 'b'), [3, 4])
    , ((4, 'c'), [5])
    ]

accepting = [2, 5]

main = do
    print $ nfa2 "aaa" 1 edges accepting
    print $ nfa2 "abc" 1 edges accepting
    print $ nfa2 "abb" 1 edges accepting
    print $ nfa2 "aabc" 1 edges accepting

输出将是:

[2]
[5]
[]
[]