【问题标题】:Finite automaton in HaskellHaskell 中的有限自动机
【发布时间】:2012-06-16 13:47:49
【问题描述】:

在 Haskell 中表示有限自动机的好方法是什么?它的数据类型如何?

在我们大学里,自动机被定义为一个 5 元组

(Q, X, delta, q_0, F)

其中 Q 是自动机状态的集合,X 是字母表(这部分是否必要),delta 是从 (Q,X) 获取 2 元组并返回 state/-s 的转换函数(非确定性version),F 是接受/结束状态的集合。

最重要的是,我不确定delta 应该有什么类型...

【问题讨论】:

  • 在确定性自动机的情况下,delta 可以是 Q -> X -> Q 类型如果是非确定性自动机,我会选择 Q -> X -> [Q ]
  • Sven Hager 所说的,F 可以实现为isEnd :: Q -> Bool

标签: haskell state-machine representation automaton


【解决方案1】:

有两个基本选项:

  • Sven Hager 建议的显式函数 delta :: Q -> X -> Q(或 [Q],视情况而定)。
  • 地图delta :: Map (Q, X) Q 例如使用Data.Map,或者如果您的州/字母可以通过升序数字Data.ArrayData.Vector 进行索引。

请注意,这两种方法本质上是等价的,一种可以相对容易地从地图版本转换为函数版本(由于lookup 调用中额外的Maybe,这略有不同)

delta_func q x = Data.Map.lookup (q,x) delta_map

(或者您使用的任何映射类型的查找函数的适当柯里化版本。)


如果您在编译时构建自动机(因此知道可能的状态并可以将它们编码为数据类型),那么使用函数版本可以为您提供更好的类型安全性,因为编译器可以验证您是否已涵盖所有情况。

如果您在运行时构建自动机(例如从用户输入),则将 delta 存储为映射(并可能进行上述函数转换)并进行适当的输入验证以保证正确性,以便 @987654332 @ 是安全的(即在映射中总是有一个条目用于任何可能的 (Q,X) 元组,因此查找永远不会失败(永远不会返回 Nothing)。

非确定性自动机与 map 选项配合得很好,因为查找失败与没有状态可去,即空的 [Q] 列表相同,因此不需要 任何除了调用join . maybeToList之外的Maybe的特殊处理(join来自Data.MonadmaybeToList来自Data.Maybe)。


另一方面,字母表是绝对必要的:它是自动机接收输入的方式。

【讨论】:

  • ThanQ 非常感谢伟大而全面的答案 :-) 还有 1 个问题:将正则表达式转换为自动机时,表示 delta 的更好方法是什么?我需要实现自动机的 concatenationdisjunctioniteration 等操作。在我看来,地图是赢家——还是我错了?
  • @mathemage,您可以尝试实现两者,看看您喜欢哪个(我会先尝试地图版本。)另外,如果这个答案对您有帮助,您应该投票,如果它回答您的问题,您可以用复选标记将其表示为已接受:the faq has some more detail
  • @mathemage:如果您使用自动机箭头(请参阅下面的完整答案),那么我认为您可以用组合函数来表达这些操作。例如,析取是一个函数,它将输入传递给它的两个参数状态并返回一个新函数,该函数是两个结果的析取。
【解决方案2】:

查看“箭头”包中的 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')

“delay”箭头表示“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”子句可以实现这一点,因此我们可以建立一个反馈循环。

【讨论】:

    猜你喜欢
    • 2017-01-04
    • 1970-01-01
    • 2011-11-16
    • 1970-01-01
    • 1970-01-01
    • 2012-08-23
    • 2013-11-29
    • 2017-10-11
    • 1970-01-01
    相关资源
    最近更新 更多