在Haskell中表示有限自动机的好方法是什么?它的数据类型如何?
在我们学院,自动机被定义为5元组
(Q, X, delta, q_0, F)
其中Q是自动机状态的集合,X是字母表(这部分甚至是必要的),delta是从(Q,X)获取2元组并返回状态/ -s的转换函数(在非确定性中)版本)和F是接受/结束状态的集合。
最重要的是,我不确定delta
应该具有哪种类型......
答案 0 :(得分:17)
有两个基本选项:
delta :: Q -> X -> Q
(或[Q]
)。delta :: Map (Q, X) Q
,例如使用Data.Map
,或者您的州/字母可以使用升序号Data.Array
或Data.Vector
编制索引。请注意,这两种方法基本上是等效的,可以从地图版本转换为函数版本(由于来自Maybe
调用的额外lookup
,这稍微不同)
delta_func q x = Data.Map.lookup (q,x) delta_map
(或者您正在使用的任何映射类型的查找函数的适当curry版本。)
如果您在编译时构建自动机(因此知道可能的状态并可以将它们编码为数据类型),那么使用函数版本可以提供更好的类型安全性,因为编译器可以验证您已覆盖所有情况。
如果您在运行时构建自动机(例如从用户输入),然后将delta
存储为映射(并且可能执行上述功能转换)并进行适当的输入验证以确保正确性,以便fromJust
是安全的(即地图中始终存在任何可能的(Q,X)
元组的条目,因此查找永远不会失败(永远不会返回Nothing
))。
非确定性自动机与map选项配合良好,因为查找失败与没有状态相同,即空[Q]
列表,因此不需要 Maybe
之后{{1>}的所有特殊处理join . maybeToList
join
来自Data.Monad
而maybeToList
来自{{1} }})。
另一方面,字母表绝对是必要的:它是自动机接收输入的方式。
答案 1 :(得分:13)
查看“arrows”包中的Control.Arrow.Transformer.Automaton模块。类型看起来像这样
newtype Automaton a b c = Automaton (a b (c, Automaton a b c))
这有点令人困惑,因为它是一个箭头变换器。在最简单的情况下,您可以写
type Auto = Automaton (->)
使用函数作为底层箭头。在Automaton定义中用( - >)替换“a”并使用中缀表示法,您可以看到这大致相当于:
newtype Auto b c = Automaton (b -> (c, Auto b c))
换句话说,自动机是一个接受输入并返回结果和新自动机的函数。
您可以通过为接受参数的每个状态编写函数并返回结果和下一个函数来直接使用它。例如,这里是一个状态机,用于识别正则表达式“a + b”(即,一系列至少一个'a'后跟'b')。 (注意:未经测试的代码)
state1, state2 :: Auto Char Bool
state1 c = if c == 'a' then (False, state2) else (False, state1)
state2 c = case c of
'a' -> (False, state2)
'b' -> (True, state1)
otherwise -> (False, state1)
就原始问题而言,Q = {state1,state2},X = Char,delta是函数应用程序,F是状态转换返回True(而不是具有“接受状态”我使用了输出具有接受价值的过渡)。
或者,您可以使用箭头表示法。 Automaton是所有有趣箭头类的实例,包括Loop和Circuit,因此您可以使用延迟访问以前的值。 (注意:再次,未经测试的代码)
recognise :: Auto Char Bool
recognise = proc c -> do
prev <- delay 'x' -< c -- Doesn't matter what 'x' is, as long as its not 'a'.
returnA -< (prev == 'a' && c == 'b')
“延迟”箭头表示“prev”等于“c”的先前值而不是当前值。您还可以使用“rec”访问以前的输出。例如,这是一个箭头,随着时间的推移会给你一个衰减的总数。 (在这种情况下实际测试过)
-- | Inputs are accumulated, but decay over time. Input is a (time, value) pair.
-- Output is a pair consisting
-- of the previous output decayed, and the current output.
decay :: (ArrowCircuit a) => NominalDiffTime -> a (UTCTime, Double) (Double, Double)
decay tau = proc (t2,v2) -> do
rec
(t1, v1) <- delay (t0, 0) -< (t2, v)
let
dt = fromRational $ toRational $ diffUTCTime t2 t1
v1a = v1 * exp (negate dt / tau1)
v = v1a + v2
returnA -< (v1a, v)
where
t0 = UTCTime (ModifiedJulianDay 0) (secondsToDiffTime 0)
tau1 = fromRational $ toRational tau
注意“延迟”的输入如何包括“v”,这是从其输出中获得的值。 “rec”子句启用了这个,因此我们可以建立一个反馈循环。