Lookaround是否影响哪些语言可以通过正则expression式匹配?

现代正则expression式引擎中有一些function可以让你匹配没有这个function的语言。 例如,使用反向引用的以下正则expression式匹配包含重复自身的单词的所有string的语言: (.+)\1 。 这种语言是不规则的,不能用一个不使用反向引用的正则expression式匹配。

lookaround是否也影响哪些语言可以通过正则expression式匹配? 即是否有任何语言可以使用lookaround匹配,否则无法匹配? 如果是这样,对于所有types的视angular(负面或正面的向前看或向后看),还是仅仅对于其中的一些来说,都是如此?

正如其他答案所声称的,周转不会给正则expression式增加任何额外的权力。

我想我们可以用下面的方法来展示这个:

一个卵石2-NFA (见引用它的论文的引言部分)。

1-pebble 2NFA不处理嵌套的lookahead,但是,我们可以使用多卵石2NFA的变体(参见下面的部分)。

介绍

2-NFA是一个非确定性有限自动机,它能够在其input上向左或向右移动。

一台卵石机就是机器可以在input磁带上放置卵石的地方(即用卵石标记一个特定的input符号),根据在当前input位置是否有卵石做不同的转换。

众所周知,一粒卵石2-NFA具有与常规DFA相同的能力。

非嵌套的Lookaheads

基本思路如下:

2NFA允许我们通过在input磁带中向前或向后移动来回溯(或“前方轨道”)。 因此,为了向前看,我们可以匹配先行expression式,然后回溯我们已经消耗的内容,以匹配先行expression式。 为了确切地知道何时停止回溯,我们使用卵石! 我们在进入dfa之前放下卵石来标记追踪需要停止的地方。

因此,在通过卵石2NFA运行我们的string结束时,我们知道我们是否匹配了先行expression式,并且剩余的input(即,剩下的要消耗的)恰恰是匹配剩余的。

所以对于formsu(?= v)w的前瞻

我们有u,v和w的DFA。

从u的接受状态(是的,我们可以假设只有一个)DFA开始,我们做一个电子转换到v的开始状态,用卵石标记input。

从v的接受状态,我们转换到一个状态,它继续移动input,直到find一个卵石,然后转换到w的开始状态。

从v的拒绝状态,我们转换到一个继续向左移动的状态,直到find卵石,并转换到u的接受状态(即我们离开的位置)。

用于常规NFA的certificate显示r1 | r2或r *等,inheritance这些卵石2nfas。 请参阅http://www.coli.uni-saarland.de/projects/milca/courses/coal/html/node41.html#regularlanguages.sec.regexptofsa了解更多有关组件机器如何放在一起以提供更大的机器r *expression式等

r *等工作的上述certificate的原因是回溯确保input指针始终在正确的位置,当我们进入组件nfas重复。 而且,如果使用卵石,那么它正在被一个先行部件机器处理。 由于没有完全回溯和取回卵石,没有从前视机器到前视机器的转换,所以只需要一台卵石机器。

例如考虑([^ a] | a(?= … b))*

和stringabbb。

我们有abbb,它通过peb2nfa(?= … b),结束时我们处于(bbb,匹配)状态(即在inputbbb中是剩余的,它匹配了'a'之后是“..b”)。 现在由于*,我们回到开头(参见上面链接中的构造),并为[^ a]inputdfa。 匹配b,回到开始,再次input[^ a]两次,然后接受。

处理嵌套的Lookaheads

为了处理嵌套的lookahead,我们可以使用这里定义的k-pebble 2NFA的受限版本: 双向和多卵石自动机及其逻辑的复杂性结果 (参见定义4.1和定理4.2)。

一般情况下,2个卵石自动机可以接受非正则集,但有以下限制,可以certificatek-卵石自动机是正则的(上述论文中的定理4.2)。

如果鹅卵石是P_1,P_2,…,P_K

  • 除非P_i已经在录像带上,否则P_ {i + 1}可能不会被放置,除非P_ {i + 1}不在录像带上,否则P_ {i}可能不会被拾取。 基本上鹅卵石需要以后进式的方式使用。

  • 在放置P_ {i + 1}的时间和P_ {i}被拾取或者P_ {i + 2}被放置的时间之间,自动机可以仅遍历位于当前位置P_ {i}并且input词的结尾位于P_ {i + 1}的方向上。 而且,在这个子词中,自动机只能作为Pebble P_ {i + 1}的一个卵石自动机。 特别是不允许举起,放置甚至感觉到另一块卵石的存在。

所以如果v是深度k的嵌套先行expression式,则(?= v)是深度k + 1的嵌套先行expression式。 当我们进入一台前视机器时,我们确切地知道到目前为止已经放置了多less个鹅卵石,因此可以准确地确定放置哪个鹅卵石,以及当我们退出机器时,我们知道哪个鹅卵石要抬起。 所有在深度t的机器都是通过放置卵石t并且退出(即,我们返回到深度t-1机器的处理)通过去除卵石t来input的。 整个机器的任何运行看起来都像一棵树的recursiondfs调用,并且可以迎合以上两个多卵石机器的限制。

现在当你组合expression式时,对于rr1,由于你是concat,所以r1的卵石数必须增加r的深度。 对于r *和r | r1,卵石编号保持不变。

因此,任何带前视的expression式都可以转换成等效的多卵石机器,在卵石放置方面有上述限制,因此是规则的。

结论

这基本上解决了弗朗西斯原始证据的缺点:能够阻止先行expression式消耗未来匹配所需的任何东西。

由于Lookbehinds只是有限的string(不是真正的正则expression式),我们可以先处理它们,然后再处理lookahead。

对不完整的文字,但一个完整的证据将涉及大量的数字。

它看起来是正确的,但我会很高兴知道任何错误(我似乎喜欢:-))。

对于你所问的问题的答案是否定的,是否可以通过正视expression式来增强查看,而不是正常语言的更大类别。

certificate是相对简单的,但是将包含lookarounds的正则expression式转换成一个没有的algorithm是混乱的。

首先:请注意,您可以始终否定正则expression式(超过有限字母表)。 给定一个识别expression式产生的语言的有限状态自动机,你可以简单地将所有的接受状态交换为非接受状态,以得到一个FSA,该FSA准确地识别那个语言的否定,为此有一组等价的正则expression式。

第二:由于正则语言(因此是正则expression式)在否定的情况下被closures,所以它们也在交集之下被封闭,因为根据摩根定律,相交B = neg(neg(A)union neg(B))。 换句话说,给出两个正则expression式,你可以find另一个匹配两个正则expression式。

这使您可以模拟环视expression式。 例如,u(?= v)w只匹配匹配uv和uw的expression式。

对于负向预测,您需要等价于集合理论A \ B的正则expression式,它只是一个相交(负B)或等价负(负(A)B)。 因此,对于任何正则expression式r和s,您可以find一个正则expression式rs,它与匹配r的expression式不匹配s。 在负面的前瞻性术语中:u(?!v)w只匹配与uw – uv匹配的expression式。

查找是有用的有两个原因。

首先,因为否定正则expression式会导致更less的整理。 例如q(?!u)=q($|[^u])

其次,正则expression式不仅仅是匹配expression式,还会消耗string中的字符,或者至less这是我们想要的。 例如,在Python中,我关心的.start()和.end(),当然:

 >>> re.search('q($|[^u])', 'Iraq!').end() 5 >>> re.search('q(?!u)', 'Iraq!').end() 4 

第三,我认为这是一个非常重要的原因,否定正则expression式并不能很好地提升连接。 neg(a)neg(b)与neg(ab)不是同一个东西,这意味着你不能从你find的上下文中翻译出一个lookaround – 你必须处理整个string。 我认为这让人们不愉快的工作,打破了人们对正则expression式的直觉。

我希望我已经回答了你的理论问题(深夜,所以如果我不清楚,请原谅我)。 我同意一个评论员说,这确实有实际的应用。 在试图抓取一些非常复杂的网页时,我遇到了同样的问题。

编辑

我对不清楚的道歉表示:我不相信你可以通过结构化归纳来certificate正则expression式的正则性+expression式,我的例子只是一个简单的例子在那。 结构性归纳不起作用的原因是因为结果expression式是非构成性的 – 我试图对上面的否定做点说明。 我怀疑任何直接的formscertificate会有很多杂乱的细节。 我试图想到一个简单的方法来展示它,但不能拿出一个我的头顶。

为了说明使用Josh的第一个例子^([^a]|(?=..b))*$这相当于一个所有状态都接受的状态DFSA:

 A - (a) -> B - (a) -> C --- (a) --------> D Λ | \ | | (not a) \ (b) | | \ | | v \ v (b) E - (a) -> F \-(not(a)--> G | <- (b) - / | | | | | (not a) | | | | | v | \--------- H <-------------------(b)-----/ 

单独的状态A的正则expression式如下所示:

 ^(a([^a](ab)*[^a]|a(ab|[^a])*b)b)*$ 

换句话说,任何通过消除查表而获得的正则expression式通常会更长,更混乱。

为了回应乔希的评论 – 是的,我确实认为certificate等同性的最直接的方法是通过金融服务pipe理局。 是什么让这个混乱的是,通常的方式来构build一个FSA是通过一个非确定性的机器 – 它更容易expressionu | v简单的机器构造的机器u和v与ε过渡到他们两个。 当然,这相当于一个确定性的机器,但是处于指数状态的风险。 而通过确定性机器否定要容易得多。

一般的certificate将涉及到两台机器的笛卡尔乘积,并select你希望保留在每个点你想要插入一个lookaround的状态。 上面的例子在一定程度上说明了我的意思。

我不支持build设的道歉。

进一步的编辑:我发现了一篇博客文章 ,描述了一个从正则expression式中产生DFA的algorithm。 它的整洁是因为作者以一种明显的方式扩展了NFA-e的“标记epsilon转换”的概念,然后解释了如何将这种自动机转换为DFA。

我认为这样做是一种方法,但是我很高兴有人写下来。 拿出一些如此整齐的东西是我无法想象的。

我同意其他文章的观点是规则的(意思是它没有增加任何正则expression式的基本能力),但我有一个争论,那就是比其他我所看到的更简单的IMO。

我将通过提供一个DFA构造来展示查看是经常的。 一种语言是有规则的,当且仅当它具有识别它的DFA时。 请注意,Perl实际上并没有在内部使用DFA(请参阅本文中的详细信息: http : //swtch.com/~rsc/regexp/regexp1.html ),但是为了certificate的目的,我们构build了一个DFA。

构造正则expression式DFA的传统方法是首先使用汤普森algorithm构buildNFA。 给定两个正则expression式片段r1r2 ,Thompsonalgorithm提供了正则expression式的级联( r1r2 ),交替( r1|r2 )和重复( r1* )的构造。 这允许您构build一个可以识别原始正则expression式的NFA。 有关更多详细信息,请参阅上文。

为了表明积极和消极的前瞻是规则的,我将提供一个正则expression式或负面前瞻式(?=v)(?!v)正则expression式连接的结构。 只有级联需要特殊处理; 通常的交替和重复build设工作正常。

u(?= v)和u(?!v)的构造是:

http://imgur.com/ClQpz.png

换句话说,将u的现有NFA的每个最终状态连接到一个接受状态一个u的NFA,但是修改如下。 函数f(v)被定义为:

  • aa(v)成为NFA v上的一个函数,它将每个接受状态变为“反接受状态”。 如果通过NFA的任何path在给定strings状态下结束,即使通过v的不同path以s结束于接受状态,反接受状态也被定义为导致匹配失败的状态。
  • loop(v)成为NFA v上的一个函数,它在任何接受状态上添加一个自我转换。 换句话说,一旦一条path通向一个接受状态,那么无论接下来有什么input,该path都可以永远处于接受状态。
  • 对于负向预测, f(v) = aa(loop(v))
  • 对于正向预测, f(v) = aa(neg(v))

为了提供一个直观的例子,我将使用正则expression式(b|a(?:.b))+ ,这是我在弗朗西斯certificate的评论中提出的正则expression式的一个稍微简化的版本。 如果我们使用我的build筑和传统的汤普森build筑一样,我们会得到:

替代文字

e是epsilon转换(可以不消耗任何input的转换),反接受状态标记为X 在图的左半部分,您可以看到(a|b)+的表示forms:任何ab将图表置于接受状态,但也允许转换回开始状态,以便我们可以再次执行。 但是请注意,每当我们匹配一个a我们也进入图的右半部分,我们处于反接受状态,直到匹配“any”后跟b

这不是传统的NFA,因为传统的NFA没有反接受状态。 但是我们可以使用传统的NFA-> DFAalgorithm将其转换为传统的DFA。 algorithm的工作方式和往常一样,我们通过使DFA状态与我们可能处于的NFA状态的子集相对应来模拟NFA的多次运行。唯一的一点是我们略微增加了决定DFA状态是否为接受(最终)状态与否。 在传统algorithm中,如果任何 NFA状态是接受状态,则DFA状态是接受状态。 我们修改这个以说明DFA状态是一个接受状态当且仅当:

  • = 1 NFA状态是一个接受状态,

  • 0 NFA国家是反接受国家。

这个algorithm会给我们一个DFA,用正向来识别正则expression式。 埃尔戈,前瞻是正常的。 请注意,lookbehind需要一个单独的certificate。

我有一种感觉,这里提出了两个截然不同的问题:

  • Regex引擎是否构成“lookaround”比正则引擎更强大?
  • “lookaround”是否能赋予Regex引擎能够parsing比从Chomsky Type 3 – Regular grammar生成的语言更复杂的语言 ?

第一个问题在实际意义上的答案是肯定的。 Lookaround会给出一个正则expression式引擎,使用这个function比不支持的function更基本。 这是因为它为匹配过程提供了更丰富的“锚点”。 Lookaround允许您将整个Regex定义为可能的锚点(零宽度断言)。 你可以在这里得到这个function的很好的概述。

周而复始,虽然function强大,但并不能将正则expression式引擎提升到超过3型语法的理论极限。 例如,您将永远无法使用装备了环视的正则expression式引擎可靠地parsing基于Context Free-Type 2语法的语言。 正则expression式引擎仅限于有限状态自动化的能力 ,这从根本上限制了他们可以parsing的任何语言的expression能力,以3级语法的水平。 不pipe有多less“技巧”添加到您的Regex引擎,通过上下文无关语法生成的语言将永远超越其function。 parsing上下文无关 – types2语法需要下推自动化来“记住”它在recursion语言结构中的位置。 任何需要recursion评估语法规则的东西都不能使用正则expression式引擎进行分析。

总结一下:Lookaround为正则expression式引擎提供了一些实际的好处,但并没有在理论层面上“改变游戏”。

编辑

types3(普通)和types2(上下文无关)之间是否有一些复杂的语法?

我相信答案是否定的。 原因是因为在描述正则语言所需的NFA / DFA的规模上没有理论上的限制。 它可能变得任意大,因此使用(或指定)是不切实际的。 这是“环视”等闪光灯有用的地方。 它们提供了一种简短的机制来指定会导致非常大/复杂的NFA / DFA规范的内容。 它们不会增加正则语言的performance力,只是使其更具实用性。 一旦你明白了这一点,很明显的是有很多“特征”可以添加到正则expression式引擎中,使它们在实际意义上更加有用 – 但是没有任何东西能够超越正则语言的限制。

常规语言与上下文无关语言的基本区别在于,常规语言不包含recursion元素。 为了评估一个recursion语言,你需要一个下推自动化来“记住”你在recursion中的位置。 NFA / DFA不会堆叠状态信息,因此无法处理recursion。 所以给定一个非recursion的语言定义,将会有一些NFA / DFA(但不一定是实际的正则expression式)来描述它。