合并重复的路径节点

时间:2013-05-27 10:52:49

标签: algorithm haskell

考虑以下简单的数据结构:

data Step =
  Match Char |
  Options [Pattern]

type Pattern = [Step]

这与小功能一起使用

match :: Pattern -> String -> Bool
match [] _ = True
match _  "" = False
match (s:ss) (c:cs) =
  case s of
    Match   c0 -> (c == c0) && (match ss cs)
    Options ps -> any (\ p -> match (p ++ ss) (c:cs)) ps

这应该是相当明显的; Pattern根据其中包含的步骤与给定的String匹配或不匹配。每个Step或者匹配单个字符(Match),或者它包含可能的子模式列表。 (注意:子模式必须具有相同的长度!)

假设我们有这样的模式:

[
  Match '*',
  Options
    [
      [Match 'F', Match 'o', Match 'o'],
      [Match 'F', Match 'o', Match 'b']
    ],
  Match '*'
]

此模式匹配两个可能的字符串*Foo**Fob*。显然,我们可以“优化”到

[Match '*', Match 'F', Match 'o', Options [[Match 'o'], [Match 'b']], Match '*']

我的问题:我如何编写这个功能?

更一般地说,给定的Options构造函数可以具有任意数量的子路径,这些子路径具有完全不同的长度,一些具有共同的前缀和后缀,一些没有。甚至可以使用子路径,甚至可以执行类似Options []的操作(当然是无操作)。我正在努力编写一个能够正确减少每个可能输入的函数......

1 个答案:

答案 0 :(得分:4)

粗略检查看起来你已经定义了一个非确定性的有限状态自动机。 NFA首先由Michael O. Rabin和所有peope-Dana Scott定义,他们给我们带来了很多其他的东西!

这是一个自动机,因为它是由步骤构建的,基于接受状态在它们之间进行转换。在每一步,您都有许多可能的过渡。因此,你的自动机是不确定的。现在你想优化它。优化它的一种方法(不是你要求的方式,但相关)是消除回溯。你可以通过将你如何进入状态以及状态本身的每一种组合来做到这一点。这被称为powerset结构:http://en.wikipedia.org/wiki/Powerset_construction

维基百科的文章实际上非常好 - 在像Haskell这样的语言中,我们可以先定义完整的powerset DFA,然后懒洋洋地遍历所有真正的路径,以“剥离”大部分无法到达的残骸。这让我们得到了体面的DFA,但不一定是最小的DFA。

如该文章底部所述,我们可以使用Brzozowski的算法,翻转所有箭头并获得一个描述从最终状态到初始状态的新NFA。现在,如果我们最小化DFA,我们需要再次从那里回到DFA,然后翻转箭头并再次完成所有操作。这不一定是最快的方法,但它很简单,适用于大量案例。还有许多更好的算法可用:http://en.wikipedia.org/wiki/DFA_minimization

为了最大限度地减少NFA,有多种方法,但问题通常是np-hard,所以你必须选择一些毒药: - )

当然,所有这一切都假设您拥有完整的NFA。如果你有相互递归的定义,那么你可以将一个模式“置于”内部,你确定。也就是说,你需要使用聪明的技巧来恢复显式共享结构,以便甚至开始使用这种形式的NFA - 否则你将永远循环。

如果您插入“无共享”规则 - 即您的NFA的有向图不仅是非循环的,而且除非您退出“选项”集,否则分支永远不会“合并”,那么我会想象这种简化是一个更直接的事情,只是'分解'常见的人物。由于这涉及到思考而不仅仅是提供参考,我现在将其留在那里,只是注意到这篇文章可能以某种方式引起关注:http://matt.might.net/articles/parsing-with-derivatives/

P.S。

对“因子分解”解决方案的一种刺激是具有以下类型的函数:

factor :: [Pattern] -> (Maybe Step, [Pattern])
factor = -- pulls out a common element of the pattern head, should one exist. shallow.

factorTail = -- same, but pulling out of the pattern tail

simplify :: [Pattern] -> [Pattern]
simplify = -- remove redundant constructs, such as options composed only of other options, which can be flattened out, options with no elements that are the "only" option, etc. should run "deep" all levels down.

现在您可以从最低级别开始并循环(simplify . factor),直到您没有新因素为止。然后使用(simplify . factorTail)执行此操作。然后去一级,做同样的事情。如果你不能把它“欺骗”成一个非最小的解决方案,我不会感到震惊,但我认为在大多数情况下它会很好用。

更新:此解决方案无法解决的问题是选项[“--DD--”,“++ DD ++”](读取字符串作为匹配列表),因此您在头部和尾部都有不常见的结构,但不是在中间。在这种情况下,更通用的解决方案是在列表中的所有匹配项之间拉出最不常见的子字符串,并将其用作“框架”,并在事物不同的部分中插入选项。