<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>我爱正则表达式 &#187; DFA</title>
	<atom:link href="http://iregex.org/blog/tag/dfa/feed" rel="self" type="application/rss+xml" />
	<link>http://iregex.org</link>
	<description>原创、翻译、转载关于正则表达式的文章</description>
	<lastBuildDate>Fri, 30 Mar 2012 12:50:01 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
<atom:link rel="hub" href="http://pubsubhubbub.appspot.com"/><atom:link rel="hub" href="http://superfeedr.com/hubbub"/><atom:link rel="hub" href="http://www.feedsky.com/api/RPC2"/><atom:link rel="hub" href="http://blogsearch.google.com/ping/RPC2"/><atom:link rel="hub" href="http://blog.yodao.com/ping/RPC2"/><atom:link rel="hub" href="http://www.feedsky.com/api/RPC2"/><atom:link rel="hub" href="http://www.xianguo.com/xmlrpc/ping.php"/><atom:link rel="hub" href="http://www.zhuaxia.com/rpc/server.php"/><atom:link rel="hub" href="http://rpc.technorati.com/rpc/ping"/><atom:link rel="hub" href="http://rpc.pingomatic.com/"/>		<item>
		<title>正则学习杂感：磨刀不误砍柴功</title>
		<link>http://iregex.org/blog/20090923-learning-regex.html</link>
		<comments>http://iregex.org/blog/20090923-learning-regex.html#comments</comments>
		<pubDate>Wed, 23 Sep 2009 06:05:39 +0000</pubDate>
		<dc:creator>rex</dc:creator>
				<category><![CDATA[杂项]]></category>
		<category><![CDATA[DFA]]></category>
		<category><![CDATA[NFA]]></category>
		<category><![CDATA[正则式]]></category>
		<category><![CDATA[龙书]]></category>

		<guid isPermaLink="false">http://iregex.org/?p=67</guid>
		<description><![CDATA[反复读紫龙书第三章，以及网文Write Your Own Regular Expression Parser（链接在这篇文章中找），连同该网文附带的源码，终于破关。一点感悟，写在这里。 使用正则表达式搜索字串，比逐位进行strcmp... ]]></description>
			<content:encoded><![CDATA[<p>反复读<a href="http://www.douban.com/subject/1134994/" target="_blank">紫龙书</a>第三章，以及网文Write Your Own Regular Expression Parser（链接在<a href="http://iregex.org/blog/20090918-learning-regex-with-some-urls.html" target="_blank">这篇文章</a>中找），连同该网文附带的源码，终于破关。一点感悟，写在这里。</p>
<p>使用正则表达式搜索字串，比逐位进行strcmp这样的方法要复杂N倍，如果说strcmp是只支持＋－×÷的计算器的话，那么正则表达式就是一台现代化的电脑。正则式不用逐位比较，它有自己的一整套方法。</p>
<p>要想在源字串找某个正则式模式，需要先详尽地分析该正则式，其过程是将该正则式解剖成不可再分的字符（称为状态），然后从任意一个状态开始，通过不同的路径（字符输入）可以到达不同的状态。第一个字符之前，叫做初始状态；最后一个字符之后，叫做接收状态。这种是NFA（不确定的有穷自动机），因为一个状态上有可能存在不止一条的转出状态。</p>
<p>一旦做好了这样的状态集合，就可以进行匹配了。每输入一个字符，从开始状态上就会产生不同的状态集合；依次输入完毕，如果所得到状态集合中包含了一个接受状态，就证明所输入的串是匹配的。但是NFA的效率低下。因为从每个状态上转出的路径不止一条，因此就有回溯。为提升效率，可以将NFA转成等效的DFA，即确定的有穷自动机。</p>
<p>DFA的原理是，对于每个状态集合D和给定的输入字符a，最多只有一条通路可走。与NFA的区别是，对于每个单个的状态f和给定的字符a，就有N条路可走。但是DFA也不是生来就万能的，比方说，它有死状态需要消除，同构的状态需要合并，还存在对于复杂正则表达式的DFA的转换效率低、实现复杂度高的情况，等等。虽如此，NFA转换为DFA还是值得的，有道是磨刀不误砍柴功。</p>
<p>对于正则表达式做好了DFA，就可以进行匹配了。每输入一个字符，自动机就转至下一个状态集合，直至输入结尾。如果所达到的状态集合中包含了接受状态，那么匹配就是成功的。</p>
<p>以上就是NFA、DFA的原理。当然，细节是简化过的。否则的话，上面任何一个步骤都有N多的细节，需要使用各种奇怪的数据结构来存储，复杂的成员函数来操作。</p>
<p>说到这里，想起python, pcre，以及其它一些正则表达式库，都有compile一个选项。先编译再执行，一次编译多次执行，文档一再这样强调。这比一上来就execute效率要高，如果重复执行多次的话。以前总是图方便直接就execute了，现在想想，实现一个正则表达式多不容易呀，对于多次重复的匹配，还是老老实实地compile一次才有礼貌。正则引擎在幕后默默无闻地做了那么多工作，不应该老是麻烦它老人家，呵呵。</p>
]]></content:encoded>
			<wfw:commentRss>http://iregex.org/blog/20090923-learning-regex.html/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>正则学习杂感，附几枚链接</title>
		<link>http://iregex.org/blog/20090918-learning-regex-with-some-urls.html</link>
		<comments>http://iregex.org/blog/20090918-learning-regex-with-some-urls.html#comments</comments>
		<pubDate>Fri, 18 Sep 2009 02:31:37 +0000</pubDate>
		<dc:creator>rex</dc:creator>
				<category><![CDATA[杂项]]></category>
		<category><![CDATA[DFA]]></category>
		<category><![CDATA[NFA]]></category>
		<category><![CDATA[正则式]]></category>
		<category><![CDATA[龙书]]></category>

		<guid isPermaLink="false">http://iregex.org/?p=65</guid>
		<description><![CDATA[最近在学习编译原理，尤其是正则表达式的实现这一节。从网上搜索到有用的链接，张贴在这里。一个感受是，数学知识是非常有用的，尤其是创造性的编程。如果数学知识足够，完全可以写... ]]></description>
			<content:encoded><![CDATA[<p>最近在学习编译原理，尤其是正则表达式的实现这一节。从网上搜索到有用的链接，张贴在这里。一个感受是，数学知识是非常有用的，尤其是创造性的编程。如果数学知识足够，完全可以写出更加高效的算法；如果数学知识不够，最多只是重复别人已经写好的算法而已。因此在初步学习时，可以不必另造车轮，老老实实地学习已经很成熟的算法，才是正途。</p>
<p>至于正则表达式的实现，其实也只是先有数学思想，然后将其使用编程语言实现而已。准备步骤是先归纳语法树，自顶至下，逐级细分；而实现步骤是从现有的正则表达式自叶至顶，先将正则式转换为NFA图，再优化，转为DFA，这时才可与目标字串进行比较。</p>
<p>在看紫龙书时，感觉到其中从思路到算法，从伪代码到实际代码之间，其转换需要太多的细节来补充。而这些知识，不单来自于对具体语言语法的掌握，还需要合适的数据支持，才能化繁为简，事半功倍。顺便读了一些C++ STL的内容，觉得这些标准库真是相当有用，在构建正则表达式时这些库正好可以物尽其用。STL的作者善莫大焉。</p>
<p>从理论上说，即使没有STL支持的纯C语言，对于实现正则表达式也是没有任何问题的，只不过是自己捎带着再写一遍自己需要的数据结构而已。别说C，就是宏汇编，也可以实现的。如果感兴趣，可以自行到Aogo的www.aogosoft.com上自行搜索他的实现。归结到一句话：思路有了，语言的实现只是翻译。我等初学者是无力想出更高级的实现的，只是在这种翻译过程中，作为一种深化理解和课后练习罢。言归正传，下面是我要推荐的几则链接。</p>
<p>1. 《正则表达式识别》 <a href="http://blog.csdn.net/slavik/archive/2006/09/18/1240386.aspx" target="_blank">http://blog.csdn.net/slavik/archive/2006&#8230;40386.aspx</a> <br />
有图有真相。本文的配图还是挺丰富的。思路也清晰。极具指导意义。<br />
可惜没有源码。该博客的其它相关文章，也值得阅读。</p>
<p>2. 正则表达式 TO NFA表格 <br />
<a href="http://hi.baidu.com/rabbit5455/blog/item/2b2b46f030737bc27931aac8.html" target="_blank">http://hi.baidu.com/rabbit5455/blog/item&#8230;1aac8.html</a><br />
这篇文章主要是提供了源码，源码中附注释。也不错。</p>
<p>3. 《Write Your Own Regular Expression Parser》 <a href="http://www.codeguru.com/cpp/cpp/cpp_mfc/parsing/article.php/c4093" target="_blank">http://www.codeguru.com/cpp/cpp/cpp_mfc/&#8230;.php/c4093</a></p>
<p>本文是英文文章，有图示，有源码，很不错。我正在边阅读，边学习，边翻译，一边自行实现。建议直接阅读英文资料，不必等我的翻译。本文的代码只提供了*,|，以及连接操作的实现。不过也对其它操作的实现给出了指导。我已经在源代码基础上添加了问号和加号的支持。</p>
]]></content:encoded>
			<wfw:commentRss>http://iregex.org/blog/20090918-learning-regex-with-some-urls.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[转]理解正则表达式</title>
		<link>http://iregex.org/blog/to-understand-regular-expressions.html</link>
		<comments>http://iregex.org/blog/to-understand-regular-expressions.html#comments</comments>
		<pubDate>Sun, 30 Aug 2009 13:34:18 +0000</pubDate>
		<dc:creator>rex</dc:creator>
				<category><![CDATA[教程]]></category>
		<category><![CDATA[DFA]]></category>
		<category><![CDATA[NFA]]></category>
		<category><![CDATA[编译原理]]></category>

		<guid isPermaLink="false">http://iregex.org/?p=64</guid>
		<description><![CDATA[rex注：本文原作者孟岩，原文转自孟岩CSDN博客。本文为《程序员》07年3月号《七种武器》专题所做。rex以前只是知道如何使用正则表达式而已，在读MRE时，读到NFA、DFA其实都是一头雾水，不知... ]]></description>
			<content:encoded><![CDATA[<blockquote style="border-left:2px solid #DDDDDD; margin:15px 30px 0 10px; padding-left:20px;"><p>rex注：本文原作者孟岩，原文转自<a href="http://blog.csdn.net/myan/archive/2007/03/03/1520033.aspx" target="_blank">孟岩CSDN博客</a>。本文为《程序员》07年3月号《七种武器》专题所做。rex以前只是知道如何使用正则表达式而已，在读MRE时，读到NFA、DFA其实都是一头雾水，不知所云。现在抓紧时间恶补基础知识，学习了些编译原理，这才有些明白。如今再看从源头讲正则表达式的文章，就心有戚戚了，呵呵。搜到好文章一篇，与大家分享。
</p></blockquote>
<p>在程序员日常工作中，数据处理占据了相当的比重。而在所有的数据之中，文本又占据了相当的比重。文本能够被人理解，具有良好的透明性，利于系统的开发、测试和维护。然而，易于被人理解的文本数据，机器处理起来就不一定都那么容易。文本数据复杂多变，特定性强，甚至是千奇百怪。因此，文本处理程序可谓生存环境恶劣。一般来说，文本处理程序都是特定于应用的，一个项目有一个项目的要求，彼此之间很难抽出共同点，代码很难复用，往往是&#8220;一次编码，一次运行，到处补丁&#8221;。其程序结构散乱丑陋，谈不上有什么&#8220;艺术性&#8221;，基本上与&#8220;模式&#8221;、&#8220;架构&#8221;什么的无缘。在这里，从容雅致、温文尔雅派不上用场，要想生存就必须以暴制暴。<br />事实上，几十年的实践证明，除了正则表达式和更高级的parser技术，在这样一场街头斗殴中别无利器。而其中，尤以正则表达式最为常用。所以，对于今天的程序员来说，熟练使用正则表达式着实应该是一种必不可少的基本功。然而现实情况却是，知道的人很多，善于应用的人却很少，而能够洞悉其原理，理智而高效地应用它的人则少之又少。大多数开发者被它的外表吓倒，不敢也不耐烦深入了解其原理。事实上，正则表达式背后的原理并不复杂，只要耐心学习，积极实践，理解正则表达式并不困难。下面列举的一些条款，来自我本人学习和时间经验的不完全总结。由于水平和篇幅所限，只能浮光掠影，不足和谬误之处，希望得到有识之士的指教。</p>
<p>1.&nbsp;了解正则表达式的历史<br />正则表达式萌芽于1940年代的神经生理学研究，由著名数学家Stephen Kleene第一个正式描述。具体地说，Kleene归纳了前述的神经生理学研究，在一篇题为《正则集代数》的论文中定义了&#8220;正则集&#8221;，并在其上定义了一个代数系统，并且引入了一种记号系统来描述正则集，这种记号系统被他称为&#8220;正则表达式&#8221;。在理论数学的圈子里被研究了几十年之后，1968年，后来发明了UNIX系统的Ken Thompson第一个把正则表达式用于计算机领域，开发了qed和grep两个实用文本处理工具，取得了巨大成功。在此后十几年里，一大批一流计算机科学家和黑客对正则表达式进行了密集的研究和实践。在1980年代早期，UNIX运动的两个中心贝尔实验室和加州大学伯克利分校分别围绕grep工具对正则表达式引擎进行了研究和实现。与之同时，编译器&#8220;龙书&#8221;的作者Alfred Aho开发了Egrep工具，大大扩展和增强了正则表达式的功能。此后，他又与《C程序设计语言》的作者Brian Kernighan等三人一起发明了流行的awk文本编辑语言。到了1986年，正则表达式迎来了一次飞跃。先是C语言顶级黑客Henry Spencer以源代码形式发布了一个用C语言写成的正则表达式程序库（当时还不叫open source），从而把正则表达式的奥妙带入寻常百姓家，然后是技术怪杰Larry Wall横空出世，发布了Perl语言的第一个版本。自那以后，Perl一直是正则表达式的旗手，可以说，今天正则表达式的标准和地位是由Perl塑造的。Perl 5.x发布以后，正则表达式进入了稳定成熟期，其强大能力已经征服了几乎所有主流语言平台，成为每个专业开发者都必须掌握的基本工具。</p>
<p>2.&nbsp;掌握一门正则表达式语言<br />使用正则表达式有两种方法，一种是通过程序库，另一种是通过内置了正则表达式引擎的语言本身。前者的代表是Java、.NET、C/C++、Python，后者的代表则是Perl、Ruby、JavaScript和一些新兴语言，如Groovy等。如果学习正则表达式的目标仅仅是应付日常应用，则通过程序库使用就可以。但只有掌握一门正则表达式语言，才能够将正则表达式变成编程的直觉本能，达到较高的水准。不但如此，正则表达式语言也能够在实践中提供更高的开发和执行效率。因此，有心者应当掌握一门正则表达式语言。</p>
<p>3.&nbsp;理解DFA和NFA<br />正则表达式引擎分成两类，一类称为DFA（确定性有穷自动机），另一类称为NFA（非确定性有穷自动机）。两类引擎要顺利工作，都必须有一个正则式和一个文本串，一个捏在手里，一个吃下去。DFA捏着文本串去比较正则式，看到一个子正则式，就把可能的匹配串全标注出来，然后再看正则式的下一个部分，根据新的匹配结果更新标注。而NFA是捏着正则式去比文本，吃掉一个字符，就把它跟正则式比较，匹配就记下来：&#8220;某年某月某日在某处匹配上了！&#8221;，然后接着往下干。一旦不匹配，就把刚吃的这个字符吐出来，一个个的吐，直到回到上一次匹配的地方。<br />DFA与NFA机制上的不同带来5个影响：<br />1.&nbsp;DFA对于文本串里的每一个字符只需扫描一次，比较快，但特性较少；NFA要翻来覆去吃字符、吐字符，速度慢，但是特性丰富，所以反而应用广泛，当今主要的正则表达式引擎，如Perl、Ruby、Python的re模块、Java和.NET的regex库，都是NFA的。<br />2.&nbsp;只有NFA才支持lazy和backreference等特性；<br />3.&nbsp;NFA急于邀功请赏，所以最左子正则式优先匹配成功，因此偶尔会错过最佳匹配结果；DFA则是&#8220;最长的左子正则式优先匹配成功&#8221;。<br />4.&nbsp;NFA缺省采用greedy量词（见item 4）；<br />5.&nbsp;NFA可能会陷入递归调用的陷阱而表现得性能极差。</p>
<p>我这里举一个例子来说明第3个影响。</p>
<p>例如用正则式/perl|perlman/来匹配文本 &#8216;perlman book&#8217;。如果是NFA，则以正则式为导向，手里捏着正则式，眼睛看着文本，一个字符一个字符的吃，吃完 &#8216;perl&#8217; 以后，跟第一个子正则式/perl/已经匹配上了，于是记录在案，往下再看，吃进一个 &#8216;m&#8217;，这下糟了，跟子式/perl/不匹配了，于是把m吐出来，向上汇报说成功匹配 &#8216;perl&#8217;，不再关心其他，也不尝试后面那个子正则式/perlman/，自然也就看不到那个更好的答案了。</p>
<p>如果是DFA，它是以文本为导向，手里捏着文本，眼睛看着正则式，一口一口的吃。吃到/p/，就在手里的 &#8216;p&#8217; 上打一个钩，记上一笔，说这个字符已经匹配上了，然后往下吃。当看到 /perl/ 之后，DFA不会停，会尝试再吃一口。这时候，第一个子正则式已经山穷水尽了，没得吃了，于是就甩掉它，去吃第二个子正则式的/m/。这一吃好了，因为又匹配上了，于是接着往下吃。直到把正则式吃完，心满意足往上报告说成功匹配了 &#8216;perlman&#8217;。</p>
<p>由此可知，要让NFA正确工作，应该使用 /perlman|perl/ 模式。</p>
<p>通过以上例子，可以理解为什么NFA是最左子式匹配，而DFA是最长左子式匹配。实际上，如果仔细分析，关于NFA和DFA的不同之处，都可以找出道理。而明白这些道理，对于有效应用正则表达式是非常有意义的。<br />4.&nbsp;理解greedy和lazy量词<br />由于日常遇到的正则表达式引擎全都是NFA，所以缺省都采用greedy量词。Greedy量词的意思不难理解，就是对于/.*/、/\w+/这样的&#8220;重复n&#8221;次的模式，以贪婪方式进行，尽可能匹配更多字符，直到不得以罢手为止。</p>
<p>举一个例子，以 /&lt;.*&gt;/ 模式匹配 &#8216;&lt;book&gt; &lt;title&gt; Perl Hacks &lt;/title&gt; &lt;/book&gt;\t&#8217;文本，匹配结果不是 &#8216;&lt;book&gt;&#8217;，而是 &#8216;&lt;book&gt; &lt;title&gt; Perl Hacks &lt;/title&gt; &lt;/book&gt;&#8217;。原因就在于NFA引擎以贪婪方式执行&#8220;重复n次&#8221;的命令。让我们来仔细分析一下这个过程。</p>
<p>条款3指出，NFA的模型是以正则式为导向，拿着正则式吃文本。在上面的例子里，当它拿着/.*/这个正则式去吃文本的时候，缺省情况下它就这么一路吃下去，即使碰到 &#8216;&gt;&#8217;字符也不罢手——既然 /./ 是匹配任意字符， &#8216;&gt;&#8217; 当然也可以匹配！所以就尽管吃下去，直到吃完遇到结尾（包括\t字符）也不觉得有什么不对。这个时候它突然发现，在正则表达式最后还有一个 /&gt;/，于是慌了神，知道吃多了，于是就开始一个字符一个字符的往回吐，直到吐出倒数第二个字符 &#8216;&gt;&#8217;，完成了与正则式的匹配，才长舒一口气，向上汇报，匹配字符串从第一个字符 &#8216;&lt;&#8217; 开始，到倒数第二个字符 &#8216;&gt;&#8217;结束，即&#8217;&lt;book&gt; &lt;title&gt; Perl Hacks &lt;/title&gt; &lt;/book&gt;&#8217;。</p>
<p>Greedy量词的行为有时确实是用户所需要的，有时则不是。比如在这个例子里，用户可能实际上想得到的是 &#8216;book&#8217; 串。怎么办呢？这时候lazy量词就派上用场了。把模式改为/&lt;.*?&gt;/就可以得到 &#8216;book&#8217;。这个加在 &#8216;*&#8217;号后面的 &#8216;?&#8217; 把greedy量词行为变成lazy量词行为，从而由尽量多吃变为尽量少吃，只要吃到一个 &#8216;&gt;&#8217;立刻停止。</p>
<p>问号在正则表达式里用途最广泛，这里是很重要的一个用途。</p>
<p>5.&nbsp;理解backtracking<br />在条款4的基础上解释backtracking就很容易了。当NFA发现自己吃多了，一个一个往回吐，边吐边找匹配，这个过程叫做backtracking。由于存在这个过程，在NFA匹配过程中，特别是在编写不合理的正则式匹配过程中，文本被反复扫描，效率损失是不小的。明白这个道理，对于写出高效的正则表达式很有帮助。<br />&nbsp;</p>
]]></content:encoded>
			<wfw:commentRss>http://iregex.org/blog/to-understand-regular-expressions.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

