【问题标题】:Short example of regular expression converted to a state machine?正则表达式转换为状态机的简短示例?
【发布时间】:2010-10-06 05:12:08
【问题描述】:

在 Stack Overflow 播客 #36 (https://blog.stackoverflow.com/2009/01/podcast-36/) 中,表达了以下观点: 一旦您了解了设置状态机是多么容易,您就再也不会尝试不恰当地使用正则表达式了。

我已经做了很多搜索。我找到了一些学术论文和其他复杂的例子,但我想找到一个简单的例子来帮助我理解这个过程。我使用了很多正则表达式,并且我想确保我永远不会再“不恰当地”使用一个。

【问题讨论】:

  • 您是在问如何实现有限状态机? Joel 的观点是,与非常复杂的正则表达式相比,实现 FSM 可以更简单、更容易理解/维护。有时这是真的。此外,您可以获得可读性和性能。 :)

标签: regex state-machine fsm


【解决方案1】:

我相信有人有更好的例子,但你可以check this post by Phil Haack,它有一个正则表达式的例子和一个状态机做同样的事情(之前的一篇文章也有更多的正则表达式例子我认为..)

检查该页面上的“HenriFormatter”。

【讨论】:

    【解决方案2】:

    当然可以,尽管您需要更复杂的示例才能真正了解 RE 的工作原理。考虑以下 RE:

    ^[A-Za-z][A-Za-z0-9_]*$
    

    这是一个典型的标识符(必须以字母开头,并且后面可以有任意数量的字母数字和取消划线字符,包括没有)。以下伪代码显示了如何使用有限状态机完成此操作:

    state = FIRSTCHAR
    for char in all_chars_in(string):
        if state == FIRSTCHAR:
                if char is not in the set "A-Z" or "a-z":
                    error "Invalid first character"
                state = SUBSEQUENTCHARS
                next char
        if state == SUBSEQUENTCHARS:
                if char is not in the set "A-Z" or "a-z" or "0-9" or "_":
                    error "Invalid subsequent character"
                state = SUBSEQUENTCHARS
                next char
    

    现在,正如我所说,这是一个非常简单的例子。它没有展示如何进行贪婪/非贪婪匹配、回溯、一行内(而不是整行)匹配以及其他更深奥的状态机功能,这些功能很容易被 RE 语法处理。

    这就是 RE 如此强大的原因。执行单行 RE 可以执行的操作所需的实际有限状态机代码通常非常长且复杂。

    您可以做的最好的事情是获取特定简单语言的一些 lex/yacc(或等效)代码的副本,然后查看它生成的代码。它不漂亮(不一定是因为它不应该被人类阅读,他们应该查看 lex/yacc 代码)但它可能会让您更好地了解它们是如何工作的。

    【讨论】:

      【解决方案3】:

      我不知道您已经阅读了哪些学术论文,但理解如何实现有限状态机确实并不难。有一些有趣的数学,但想法实际上很容易理解。理解 FSM 最简单的方法是通过输入和输出(实际上,这包含了大部分正式定义,我不会在这里描述)。 “状态”本质上只是描述了一组已经发生并且可能从某个点发生的输入和输出。

      通过图表最容易理解有限状态机。例如:

      alt text http://img6.imageshack.us/img6/7571/mathfinitestatemachinedco3.gif

      这就是说,如果您从某个状态 q0(旁边带有 Start 符号的状态)开始,您可以转到其他状态。每个状态都是一个圆圈。每个箭头代表一个输入或输出(取决于您如何看待它)。考虑有限状态机的另一种方法是根据“有效”或“可接受”输入。某些输出字符串不可能是某些有限状态机;这将允许您“匹配”表达式。

      现在假设您从 q0 开始。现在,如果您输入 0,您将进入状态 q1。但是,如果您输入 1,您将进入状态 q2。您可以通过输入/输出箭头上方的符号看到这一点。

      假设您从 q0 开始并获取此输入

      0、1、0、1、1、1

      这意味着你已经经历了状态(q0 没有输入,你只是从那里开始):

      q0 -> q1 -> q0 -> q1 -> q0 -> q2 -> q3 -> q3

      如果没有意义,请用手指跟踪图片。请注意,对于输入 0 和 1,q3 会返回到自身。

      另一种说法是“如果您处于状态 q0 并且您看到 0 转到 q1,但如果您看到 1 转到 q2。”如果您为每个状态都设置了这些条件,那么您几乎完成了对状态机的定义。您所要做的就是拥有一个状态变量,然后是一种输入输入的方法,基本上就是这样。

      好的,那么为什么这对 Joel 的声明很重要?好吧,建立“一个真正的正则表达式来统治他们所有人”可能非常困难,也很难维护修改,甚至很难让其他人回来理解。此外,在某些情况下它更有效。

      当然,状态机还有许多其他用途。希望这在一些小方面有所帮助。请注意,我没有费心深入理论,但有一些关于 FSM 的有趣证明。

      【讨论】:

      • 图片链接好像坏了。我看到的只是一个紫色的盒子,里面有“gallery.hd.org”。
      • @joel.neely,嗯,我不知道该图像是否适合我。所以需要做图像托管。 @Pax,不确定您的意思。 :)
      • 这是运动鞋电影中的一句话。
      • 图片链接又坏了。请上传到 Imgur。
      【解决方案4】:

      即时制作自己的!

      http://osteele.com/tools/reanimator/???

      这是一个非常好的组合工具,可以将正则表达式可视化为 FSM。它不支持您在现实世界的正则表达式引擎中找到的某些语法,但肯定足以准确理解发生了什么。

      【讨论】:

      • 那个网站没那么有用 - 输入 [A-Za-z][A-Za-z0-9]{0,4} 看看会产生什么 :-)跨度>
      • Pax,哎哟:/太糟糕了,这么好的网站,但无法编译那个简单的正则表达式
      • 是的,很遗憾无法处理重复限定符...但是您可以在语义上与 [A-Za-z]\w{0,4} 进行等效的拍手!为了简单起见,他可能将它们排除在外。再加上重复限定符真的会毁掉你的 FSM - UI 很快就会变得无用。
      • “这个特定的实现不知道锚、断言、非贪婪和有界限定符、排序规则元素或反向引用。” - 这是一份不可多得的清单。
      • 很遗憾,该工具似乎已损坏。
      【解决方案5】:

      问题是“如何选择状态和转换条件?”,还​​是“如何在 Foo 中实现我的抽象状态机?”

      如何选择状态和转移条件?

      我通常将 FSM 用于相当简单的问题并直观地选择它们。在 my answer to another question about regular expressions 中,我只是将解析问题视为 Insideoutside 标记对之一,并从那里写出转换(具有开始和结束状态以保持实现干净)。

      如何在 Foo 中实现我的抽象状态机?

      如果您的实现语言支持像cswitch 语句这样的结构,那么您打开当前状态并处理输入以查看接下来执行哪个操作和/或转换。

      如果没有类似switch 的结构,或者如果它们在某些方面存在缺陷,您可以使用if 样式分支。 呃。

      全部写在c 中的一个地方,我链接的示例如下所示:

      token_t token;
      state_t state=BEGIN_STATE;
      do {
         switch ( state.getValue() ) {
         case BEGIN_STATE;
            state=OUT_STATE;
            break;
         case OUT_STATE:
            switch ( token.getValue() ) {
               case CODE_TOKEN:
                  state = IN_STATE;
                  output(token.string());
                  break;
               case NEWLINE_TOKEN;
                  output("<break>");
                  output(token.string());
                  break;
               ...
            }
            break;
         ...
         }
      } while (state != END_STATE);
      

      这很混乱,所以我通常将 state 案例拆分为单独的函数。

      【讨论】:

        【解决方案6】:

        在任何模式上使用 python 鲜为人知的 re.DEBUG 标志的一种相当方便的方法来帮助查看这个:

        >>> re.compile(r'<([A-Z][A-Z0-9]*)\b[^>]*>(.*?)</\1>', re.DEBUG)
        literal 60
        subpattern 1
          in
            range (65, 90)
          max_repeat 0 65535
            in
              range (65, 90)
              range (48, 57)
        at at_boundary
        max_repeat 0 65535
          not_literal 62
        literal 62
        subpattern 2
          min_repeat 0 65535
            any None
        literal 60
        literal 47
        groupref 1
        literal 62
        

        “literal”和“range”后面的数字是指它们应该匹配的 ascii 字符的整数值。

        【讨论】:

        • +1,正在等待这个小宝石浮出水面。有谁知道它的输出记录在哪里?我从来没有找到任何东西..
        猜你喜欢
        • 2016-08-19
        • 1970-01-01
        • 2017-04-23
        • 1970-01-01
        • 1970-01-01
        • 2011-10-02
        • 2016-04-02
        • 1970-01-01
        • 2019-05-11
        相关资源
        最近更新 更多