正则表达式和有限自动机

正则表达式和有限自动机
正则表达式和有限自动机

第二部分正规语言和有限自动机

语言往往是无限集,但描述的方法往往是有限的,一种方法是描述如何通过字符串操作由简单的字符串产生整个语言,或者描述如何通过集合操作由简单语言产生复杂语言。另一种方法是描述识别字符串是否属于某个语言的机制,也就是描述一个算法过程。

本书考察的最简单的语言类是正规语言,正规语言能够通过应用有限次的某个标准操作从一元语言产生。正规语言能够被有限自动机识别,有限自动机是空间严格受限的简单机器。

在第二部分,我们还考察正规语言的另外一些特点:1)导出将一种语言的描述翻译成另一种语言的描述的算法;2)使用形式化方法描述语言;3)正规语言在实际中的应用。

3 正则表达式和有限自动机

3.1 正则语言和正则表达式

注意:regular language和regular expression有时也翻译成正规语言和正规表达式。

正则语言可以从非常简单的表达式得到,初始语言的字符串为空或单字母,仅使用合并、连接和K leene连接运算,因此正则语言可用一个清楚的表达式描述,通常用小括号()代替大括号{},+代替?,称为正则表达式。

下面是一些定义在字母表{0, 1}上的正则表达式,通过这些例子,能够感受到书写正则表达式的一些规律。

语言相应的正则表达式

{Λ} Λ

{0} 0

{001}或{0}{0}{1} 001

{0, 1}或{0}?{1} 0+1

{0, 10}或{0}?{10} 0+10

{1, Λ}{001} (1+Λ)001

{110}*{0, 1} (110)*(0+1)

{1}*{10} 1*10

{10, 111, 11010}* (10+111+11010)*

{0, 10}*({11}*?{001, Λ}) (0+10)*((11)*+001+Λ)

我们认为正则表达式表示的是相应语言的“最典型的字符串”,比如,1*10表示一个字符串,它以10结束,前面可以有任意多个数目的1。

我们在前面将正则语言描述成:在最简单的语言上仅仅使用三种运算合并、连接、Kleene 连接所得到的语言。这种描述预示了正则语言的递归定义(参见2.4节)。下面递归定义不仅定义了语言,而且定义了正则表达式。

定义3.1 字母表∑上正则语言类R,及其相应的正则表达式定义如下:

1.空集φ(即空语言)是正则语言,表达式是φ。

2.{Λ}仅有空串的语言是正则语言,表达式是Λ。

3.每个a∈∑,{a}是正则语言,表达式是a。

4.如果L1和L2是正则语言,表达式是r1和r2,则

(a)L1?L2是正则语言,表达式是r1+r2。

(b)L1L2是正则语言,表达式是r1r2。

(c)L1*是正则语言,表达式是r1*。

只有应用上面4条规则产生的语言才是字母表∑上的正则语言。

对上面的解释做些解释。为了保持一致性和连贯性,空语言被认为是正则语言。后面许多地方将提到这样的说法:对于每个…,都对应一个正则语言。如果空语言不属于正则语言,那么每个这样的说法还需要排除空语言这种特殊情况,带来不简洁的说法。

为了书写简洁,省去大量的括号,我们规定正则表达式中运算符的优先级次序是K leene*、连接、合并。同时我们借用一些代数表达式的符号,如指数幂等。

原表达式简洁表达式

(rr) r2

(a+((b*)c)) a+b*c

((r*)r) r+

同样借助代数学的记号,两个表示不同语言的正则表达式可以使用符号≠,比如:

(a + b)* ≠ a + b*

我们还可以借用符号=来化简正则表达式,如

正则表达式的化简

1*(1+Λ) = 1*

1*1* = 1*

0* + 1* = 1* + 0*

(0*1*)* = (0+1)*

(0+1)*01(0+1)*+1*0*=(0+1)*

其中一些化简用到的规则可以从集合运算规则得到,但另有一些是字符串运算特有的,目前我们还没有发现这种化简的系统的方法(或形式化方法),但上面的例子预示了化简的巨大作用。比如最后一行的例子,两个看似很复杂的语言,它们的并集却很简单。

问题:存不存在化简正则表达式的形式化方法(既是否存在化简正则表达式的通用算法)?是否存在最简洁的正则表达式?

朱洪来信:我的印象中,它是NP-完全的问题。Please look at Garey and Johnson's book: computer and intractibility.

例子3.1 语言L?{0, 1}*,由所有长度为偶数的字符串组成,(由于0是偶数,因此空串Λ属于L),问:L是否是正则语言?如果是,L对应的正则表达式是什么?

解答:任何一个偶数长度的字符串都由多个(或0个)长度为2的字符串连接而成,而字母表{0, 1}上的长度为2的字符串只可能是4个:00、01、10、11,因此L可定义如下:L={00, 01, 10, 11}*

它的正则表达式是:(00+01+10+11)*或((0+1)(0+1))*。

例子3.2 L是定义在字母表{0, 1}上的包含奇数个1的字符串组成的语言,问L的正则表达式是什么?

解答:显然L中的字符串至少有一个1,因此一定有这样的前缀0i10j,其后则有偶数个(或0个)1,因此可分解成多组10m10n的形式。由此得到L的正则表达式:

包含奇数个1的字符串组成的语言

错误的表达式有:(10*10*)*10*

例子3.3 L是字母表{0, 1}上所有长度小于等于6的字符串组成的语言,问L的正则表达式?

解答:由于L中的元素是有限的,最简单的方法是枚举法:

Λ+0+1+00+01+…+111110+111111

而表示长度为n的字符串组成的语言的表达式是:

(0+1)(0+1)…(0+1)=(0+1)n

因此一些简洁的表达式如下:

所有长度小于等于6的字符串组成的语言

Λ+(0+1)+(0+1)(0+1)+…+(0+1)(0+1)(0+1) (0+1)(0+1)(0+1)

Λ+(0+1)+(0+1)2+…+(0+1)6

(Λ+0+1)6

例子3.4 L={x以1结束且不包含子串00 | x∈{0, 1}*},问L的正则表达式?

解答:字符串不包含00,则说明其中的每个0不能后接0,而且0不能是串尾字母,因此每个0后面必定是1,既符合条件的字符串包含大量的01片断,其间是许多1,因此初步得到的表达式是:

(1+01)*

但空串不符合条件,修正得到:

(1+01)*(1+01)=(1+01)+

注意:(1+01)*1不对,漏掉了01情况。

例子3.5 C语言的标志符的组成的语言的正则表达式。

解答:C语言的标志符由3种符号组成:英文字母、数字和下划线。而且第一个字符只能是字母或下划线。因此:

(a+b+…+z+A+B+…+Z+_)( a+b+…+z+A+B+…+Z+0+1+…+9+_)*

我们令l是表示字母的集合,d是表示数字的集合,即:

l = a+b+…+z+A+B+…+Z

d = 0+1+…+9

则上式的简洁表达式是:

(l+_)(l+d+_)*

例子3.6 (暂空)

3.2 识别语言所需要的空间

语言的识别(或字符串的识别,recognize)问题是一个成员资格判定问题,即判定一个任意给定的字符串是否属于某个语言。为了以后讨论问题的方便,给出一些约定。首先限制成从左至右的一次扫描(这简化了整个识别过程中应该记住的信息总量的讨论,也利于根据每步记住的信息量多少对语言分类),其次判别对象是一个特定的字符串。除了扫描完成后,给出最后的判别(是或否),在每一步也给出假设判别,当前的假设判别反映了已扫描的前缀的情况。最后的判别可以看成最后的、最新的假设判别。

先看看人是怎么完成识别任务,然后设计自动机完成这项任务。问题是,为了给出假设判别,我们应该记住多少前缀信息。这里有两种极端的情况:1)记住所有的前缀2)什么也不记忆。在某些情况下,我们确实可以什么也不记,如判别语言φ和∑*,前者我们忽略每步输入,一律回答“否”;后者则一律回答“是”。但多数情况下,我们必须记住一些信息,应该记住的不是字符串本身,而是字符串表达的判别信息。称为有限自动机的原因是所需的空间是有限的。

比如分别输入两个字符串x和y,得到不同的答案。这说明我们一定记住了一些不同的信息当分别输入x和y时,否则我们无法区别这两个字符串,因此通常情况下,为了识别某个语言,我们必须记住一些信息,而记住这些信息则需要一定的空间。

例子3.7 语言L定义在字母表{0, 1}上,由以10结尾的字符串组成。

分析:显然判定(decision)一个字符串是否属于语言L,只需要考察该字符串的最后两个字符,因此在字符串输入识别过程中,只需要记住当前最后的两个字符,而之前的所有字符可以忽略。字母表{0, 1}上的两个字符组成的字符串只有4种,因此本例所需空间为4个单位,当然后面会看到还可进一步减少记住的信息,从而节省空间。

例子3.8语言L定义在字母表{0, 1}上,由包含偶数个0和奇数个1的字符串组成。

分析:显然,整个输入过程中,不需要记住输入的字符串的具体内容,一种方法是记住当前输入的0和1的个数,更简单的方法是记住当前输入的0和1的个数的奇偶性,而这只有4

种可能,因此本例的判定过程所需空间是4个单位。(参见图3-1,77页)

例子3.9 语言L={x以1结束且不包含子串00 | x∈{0, 1}*}(参见例子3.4)

分析:假设在当前输入的字符串中发现了00子串,则我们只需要记住这个事实,不管前面已经读过和后面将输入的字符串是什么,能够肯定该字符串不属于L,不妨将这种情况记为N。

再考虑另两种情况,情况0是最后一个字符是0,情况1是最后一个字符是1。出现情况0时,如果又看到一个0,则转到情况N,而情况0和情况1时看到1都转到情况1,情况1时看到0转到情况0。

这三种情况能够判定所有非空的字符串了,为了判定空字符串,还需要增加一个特殊的情况。显然所有的情况都专注于记住最后一个字符,和是否出现或有可能出现00子串。本例需要的空间是4个单位,参见图3-1。

将例子3.8和3.9的讨论用一个图(图3-1)来总结,它像一个流程图,或展示了我们上面判定过程的算法。图中每个圆表示一种情况,是我们需要记住的关键信息,因此圆的多少表示了记住信息的多少,也表示了判定过程中需要的总的空间的多少。每个图中有一个起始的圆(由一个没有源的箭头指示),输入的字符串从起始圆开始,沿着箭头流动(转移)到下一个圆,每一次流动消耗一个字符,当字符消耗完(即读完所有字符),所停在的圆揭示了输入字符串与图表示的语言的关系,如果圆是双圈,则说明该字符串被接受,或属于这个语言,否则不属于这个语言。

有了这样的图后,任何人或机器不用理解图中每个节点的具体含义,只要按照上面描述的机械的步骤动作,就能完成字符串的判定工作,因此刻画了一种“抽象机”,我们不关心这种机器的实现细节,比如它的驱动动力来自什么,它表示接受的具体信号是什么?我们关心的是它所揭示的一种形式化的过程(或算法),我们称它为自动机。

将要证明能够被这种自动机识别的语言是正则语言,下面的例子显示存在语言无法被这种自动机识别,因此存在非正则语言,而且我们将来的结论是非正则语言大量存在,尽管3.1节显示的正则表达式具有灵活且强大的描述能力。

例子3.10 语言L是例子2.16中描述的回文语言pal。

分析:L的每个字符串相同于它的反写字符串,因此从描述上看,这似乎是一种很简单的语言,但是从判定过程所需要的空间而言,它完全不同于例子3.7到3.9中的语言。对于任意两个不同的字符串x和y,都存在一个字符串z,使得xz属于L,而yz不属于L(证明见后),因此需要区别的字符串是无穷多,自动机需要的空间是无穷多,因此L不是有限自动机能够识别的语言。

分三种情况讨论:

1.x和y长度相同,则令z=x r;

2.x比y短,设y=y1y2,其中y1与x同长,令z=ww r x r,且w≠y2,则xz是回文,而yz

不是回文。

3.类似情况2。

形式化的方法(即从计算机的角度)定义语言的复杂程度,而不是人脑的感觉来判定语言的复杂程度,一些人脑感觉简单的语言,如回文,可能是计算机认为的复杂语言;反过来,一

些计算机容易处理的语言却可能是人脑难以把握的。另外,计算机定义语言是从判定的角度,而不是从语言的产生或大小等等角度进行的。

3.3 有限自动机(finite automata)

从3.2节的例子很容易导出有限自动机的正式定义。

定义3.2:有限自动机(finite automata, FA)是一个5元组(Q, ∑, q0, δ, A),其中

1)Q是一个有限集,其元素称为状态。

2)∑是有限字母表,其元素是输入符号。

3)q0∈Q,是初始状态。

4)A?Q,是接受状态集。

5)δ是转移函数,从Qx∑到Q。

这种定义在其他数学概念定义中很少见,数学家R. P. Boas曾在发表在the American Mathematical Monthly的题为Can We Make Mathematics Intelligible?的文中写到:There is a test for identifying some of the future professional mathematicians at an early age.

There are students who instantly comprehend a sentence beginning “Let X be an ordered quintuple (a, T, π, α, β), where…” They are even more promising if they add, “I never really understood it b efore.”

但这是计算机科学,尤其是形式语言和自动机理论中很喜爱采用的定义形式,很象是描述了一个机器的5个部件,或程序设计语言中对象的定义。且不管它的数学含义,或是否一个5元组如何成为一个机器,它确实提供了一个有效的标记方法。

例子3.11(例子3.7的进一步讨论)画出识别语言L={0, 1}*{10}的有限自动机。见图3-2。

我们可以简化图3-2所示的FA。考虑状态0和状态00,它们都不是接受状态,而且无论下一个字符是0还是1,都进入同一个状态(0进入状态00,1进入状态01),因此状态0和状态00可以合并(不妨称为A),不会改变自动机识别的语言。同理,状态1、状态01和状态11能够合并成一个状态(不妨称为B),更进一步,新状态A可以和状态Λ合并。最后简化后的自动机只有3个状态,见图3-3。

简化后的自动机显然更简洁和深刻地体现了语言的本质特征,图3-3中的状态B含义是发现当前字符为1,满足了10结尾的一半要求,状态10则表示满足了全部要求。

正如正则表达式可以简化,有限自动机也可以简化。寻找简化的形式化方法。删除有限自动机的死状态是简化的一个有效方法。例子3.11的简化方法存在形式化的方法,5.2节会具体介绍。

前面提到了自动机的状态转换,定义了输入一个字符的转移函数,下面形式化定义输入字符串的转移函数,很容易想到递归定义,首先需要知道字符串的递归定义。显然字符串递归定义的起点是空串Λ,长度为n的字符串由长度为n-1的前缀和最后一个字符构成,即x=ya。

定义3.3 M=(Q, ∑, q0, δ, A),函数δ*的递归定义:

1)q∈Q,δ*(q, Λ)=q

2)δ*(q, ya)= δ(δ*(q, y), a)

讨论,也可以认为长度为n的字符串由第一个字符和长度为n-1的后缀构成,即x=ay。则定义3.3的递归部分也可以写成:

2)δ*(q, ay)= δ*(δ(q, a), y)

但通常认为定义3.3使用更方便。

根据图3-4的自动机,计算δ*(q, abc)。

解答1:δ*(q, abc) = δ(δ*(q, ab), c)

= δ(δ(δ*(q, a), b), c)

= δ(δ(δ*(q, Λa), b), c)

= δ(δ(δ(δ*(q, Λ), a), b), c)

= δ(δ(δ(q, a), b), c)

= δ(δ(q1, b), c)

= δ(q2, c)

= q3

解答2:δ*(q, abc) = δ*(q, abc)

= δ*(δ(q, a), bc)

= δ*(q1, bc)

= δ*(δ(q1, b), c)

= δ*(q2, c)

= δ*(δ(q2, c), Λ)

= δ*(q3, c)

= q3

根据定义3.3,有如下的结论:

1.δ*(q, a) = δ(q, a),a是单个字符

2.δ*(q, xy) = δ*(δ*(q, x), y),x、y是字符串,可以用数学归纳法证明,参见练习

3.25

有了字符串转移函数的定义,我们能够形式化定义什么是自动机接受的字符串和自动机接受的语言。

定义3.4 存在自动机M=(Q, ∑, q0, δ, A),字符串x被M接受当且仅当δ*(q0, x)∈A,否则称为x被M拒绝,被M接受或识别的语言是,L(M)={x|x被M接受}。

字母表∑上的语言L,被自动机M接受(accepted)或识别(recognized)的充分必要条件是L=L(M)。

注意:此处没有区分接受(accepted)和识别(recognized)这两种提法。有限自动机接受

的语言是由所有被M接受的字符串组成,而不是部分。有限自动机的能力不体现在它的状态数,不体现在它能接受的字符串的个数,体现在它的鉴别能力,它接受什么样的字符串,拒绝什么样的字符串。如果给定一个语言L,构造接受L的自动机M,它接受所有属于L的字符串,拒绝所有不属于L的字符串,而不仅仅是接受属于L的字符串,否则图3-5所示的自动机接受所有的语言,失去了自动机研究的意义。

定理3.1 一个语言是正则语言当且仅当被一个有限自动机接受。(Kleene定理,后面证明)

定理3.1揭示了,一方面,给定一个有限自动机M,存在一个正则表达式E表示M所接受的语言,即L(M)=L(E);另一方面,给定一个正则表达式E,存在一个有限自动机接受E所表示的语言。第4章Kleene定理的证明将给出构造正则表达式和有限自动机的形式化方法。这里先给出一些仅凭直觉就能解决的简单例子,有助于导出最后的形式化方法。

例子3.12 参见图3-6,构造它接受语言的正则表达式。

分析:逐个分析自动机的每个接受状态,然后分析接受状态的所有到达路线。本例有两个接受状态A和B,状态A接受的语言是(00)*,状态B接受的语言是(00)*11(11)*,则整个自动机接受的语言是:

(00)*+(00)*11(11)* = (00)*(11)*

例子3.13 考虑图3.7所示的自动机。

分析:先找到自动机接受的最短的字符串,baaa,进一步发现所有的状态读入b都会转入状态B,从起始状态A到达接受状态E的路径只有一条,即ABCDE,因此此自动机接受的字符串的特征是以baaa结尾,得到正则表达式如下:

(a+b)*baaa

另一种思考方法是从起始状态开始,考虑到达其他状态所对应的正则表达式,每一次转移预示一个连接操作,每一次分支,预示一个合并操作,每一次循环,预示一个Kleene连接操作。

往往是循环里面有循环,分支里面有分支,循环里面有分支,分支里面有循环,这样产生的表达式将会很复杂。因此目前形式化方法得到的正则表达式并不实用,有赖进一步发现简化的方法。

先给初始状态赋初值,然后扩展到其他状态,然后反馈回来,反复迭代计算,直到最后稳定下来,计算停止,一个关键点是如何比较两个正则表达式。

例子3.14 反过来,给定正则表达式r=(11+110)*0,构造有限自动机接受语言L(r)。

分析:空串不属于语言L,因此初始状态q0不是接受状态,0属于语言L,因此存在从初始状态到接受状态标记为0的转移。另外,1和Λ是可区分字符串(存在字符串110,使得1110被拒绝,而110被接受),因此输入1和Λ,状态q0应该到达不同的状态,称输入1后的状态为r,因此至少存在3个状态,见图3-8a。

如果字符串前缀为0或10,则无论后续字符串是什么,都不属于语言L,引入一个状态s,记录字符串判定中到达失败状态,一旦到达s,则不再离开。

继续分析状态r,由于字符串1和11是可区分的(利用字符串10),而且11与0和Λ都是可区分的,因此需要再加一个状态t,同样发现可区分字符串110,增加状态u,这样得到两个接受状态。我们发现没有新的可区分状态了,因此不用再添加状态了,剩下的任务就是完善各个状态的转移函数。

上面的方法称为hit-or-miss,只要需要,就添加新状态,直到不再添加新状态,构造自动机的过程才停止。定理3.1保证了构造正则表达式表示的语言的自动机的过程能够最终完成,即状态数是有限的。现在方法的最大难点是判断是否需要加入新状态,如果不需要,那么哪个已有状态是合适的?第4章描述的形式化方法将避开这个难点。

3.4 区别字符串

利用有限自动机识别语言的基础是自动机不需要区分所有的字符串,不需要在识别过程中记住前缀的具体内容,正如上节例子显示,自动机状态用来区分那些需要区分的字符串。有限自动机仅仅判别字符串是否属于某个语言,不需要区别不同的字符串,因此不需要记住输入的整个字符串。有限自动机不同状态的数目与不同字符串的数目相关。下面我们形式化这种思想,并揭示状态数与区分字符串数之间的关系。

定义3.5 给定字符串x和y,如果存在字符串z,使得xz和yz只有一个属于语言L,则称z在语言L上区分x和y。如果不存在z,则称x和y在L上是不可区分的。

根据定义3.5,如果x和y是不可区分的,则对任意的字符串z,xz和yz到达同样的状态,同时被接受,或同时被拒绝。例如语言L={x∈{0, 1}* | x以10结尾}(参见例子3.7),字符串01011和100在L上是可区分的,因为存在z=0,区分这两个字符串,而0和100是不可区分的。

引理3.1 M=(Q, ∑, q0, δ, A)识别语言L,如果字符串x和y,δ*(q0, x)=δ*(q0, y),则x和y在L上不可区分。

证明:设z是∑上的任意一个字符串,分别考察xz和yz,根据练习3.25,

δ*(q0, xz)=δ*(δ*(q0, x), z)

δ*(q0, yz)=δ*(δ*(q0, y), z)

根据条件,

δ*(q0, xz)= δ*(q0, yz)

可见xz和yz要么同时被M接受,要么同时被拒绝,因此x和y在M所识别的语言L上是不可区分的。

定理3.2 假设n个字符串在语言L上两两可区分,那么识别L的有限自动机的状态数不少于n。

证明:设这n个在L上两两可区分的字符串是x1, x2, …, x n,存在一个识别L、状态数小于n的自动机M,那么根据鸽笼法则,δ*(q0, x1)、δ*(q0, x2)、…、δ*(q0, x n)中必定存在i和j,使得δ*(q0, x i)= δ*(q0, x j)。根据引理3.1,x i和x j不可区分,与前提矛盾,即假设不成立,状态数少于n的识别L的有限自动机不存在。

定理3.2提供了一个估计有限自动机状态数下限的办法。同时如果能够证明语言L有无穷个可区分的字符串,则说明L不是有限自动机能够接受的语言,也不是正则语言。例如回文语言就不是正则语言。

例子3.15 语言L n={x∈{0, 1}* | |x|>=n,且从右数起的第n个字符为1}

分析:一个直观的方法是,给每个长度小于等于n的字符串构造一个状态(参见例子3.11),这样自动机能够记住当前输入字符串的最后n个字符,当然也记住了倒数第n个字符。长度为n的字符串共有2n个,那么长度小于等于n的字符串共有:

20+21+…+2n=2n+1-1

这就是总的状态数,图3-9显示了识别n=3的语言的自动机,它的转移函数很简单:

δ(abc, d)=bcd

也许其他方法能够发现更简单(状态数大大少于2n+1-1)的自动机,但根据定理3.2我们能够证明接受语言L n的自动机至少需要2n个状态,因为长度为n的字符串在L n上两两可区分,即至少存在2n个可区分的字符串。

设x和y是两个长度为n的不同的字符串,它们在从右数第i个字符不同(1=

定理3.2保证了识别某个语言L的任何自动机的状态数不少于某个常数,如果对任何一个常数n,都能证明任何识别L的自动机的状态数都不少于n,则说明在L上存在无限多的可区分字符串,没有有限自动机能够识别它,这种语言也不是正则语言。反过来,如果能够证明L 上有无限多个可区分字符串,就能证明不存在接受L的有限自动机,L不是正则语言。

引申例子3.10,我们得到定理3.3,这也是我们显示的第一个非正则语言。

定理3.3 字母表{0, 1}上的回文语言pal(palindromes)是非正则的。

证明:根据例子3.10,字母表上的任何两个字符串都是可区分的,因此存在无限多的可区分字符串,根据定理3.2,pal不是正则语言。

在第5章,我们考察其他一些非正则语言,并提出其他方法去判定语言的正则性,定义3.5将再次出现,可以用来很好地描述“最小状态”自动机的概念。

3.5 并集、交集和补集

设L1和L2都是字母表∑上的正则语言,则存在有限自动机M1和M2分别接受L1和L2(根据定理3.1),同时另外三种语言L1?L2、L1L2和L1*也是正则语言(根据定义3.1),因此也存在接受这三种新语言的有限自动机,一个问题很自然提出来:接受新语言的有限自动机能否根据M1和M2构造出来?

有关连接和Kleene连接运算的问题我们到第4章后再讨论,此处讨论合并运算,容易看到,合并运算的处理方法稍作修改就能处理交集和差集运算。

设M1=(Q1, ∑, q1, δ1, A1)且M2=(Q2, ∑, q2, δ2, A2),现在要构造FA M接受语言L1?L2,显然如果M在识别字符串x的每一步都能记住足够多的信息,并最终知道x与L1和L2的关系,那么M就能判定x与语言L1?L2的关系。我们可以将M的状态设计成一个二元组(p, q),用它来同时跟踪M1和M2的状态转移,其中p∈Q1,q∈Q2,(p, q)∈Q1?Q2,M的初始状态是(q1, q2),分别来自M1和M2的初始状态,M的转移函数定义如下:

δ((p, q), a)=(δ1(p, a), δ2(q, a))

现在我们来确定接受状态集。x被M接受,即它或者被M1接受,或者被M2接受,因此状态(p, q)是接受状态,当且仅当p、q中至少有一个分别在M1和M2中是接受状态。对于交集语言L1?L2,则要求p和q同时为接受状态,差集语言可做类似处理。

定理3.4 已知接受语言L1和L2的有限自动机分别是M1和M2,则构造识别两种语言的并集、交集和差集的方法如下:

设M1=(Q1, ∑, q1, δ1, A1),M2=(Q2, ∑, q2, δ2, A2),得到

M=(Q, ∑, q0, δ, A)

Q=Q1xQ2

q0=(q1, q2)

δ((p, q), a)= (δ1(p, a), δ2(q, a))

1)如果A={(p, q) | p∈A1 或q∈A2},则M接受语言L1?L2。

2)如果A={(p, q) | p∈A1 且q∈A2},则M接受语言L1?L2。

3)如果A={(p, q) | p∈A1 且q?A2},则M接受语言L1-L2。

证明:只要证明对任意字符串x和M的状态(p, q),都成立:

δ*((p, q), x)= (δ1*(p, x), δ2*(q, x))

这可以用数学归纳法证明。有了这个等式就能够很容易证明上面三个结论,下面以1)为例说明。

字符串x被M接受,当且仅当δ*((q1, q2), x)∈A,当且仅当(δ1*(q1, x), δ2*(q2, x))∈A,根据1)的条件,当且仅当δ1*(q1, x)∈A1或δ2*(q2, x)∈A2,即x被M1或M2接受。因此x∈L1?L2。

2)和3)可类似证明。

现在差集运算的一个特殊情况,设语言L1是全集∑*,那么差集L1-L2就是L2的补集L2’,根据图3-5,我们知道存在接受∑*的有限自动机,因此接受L2’的有限自动机能够根据定理3.4

中的差集方式构造出来。更简单的方法是交换M2中的接受状态集和非接受状态集,即M2’=(Q2, ∑, q2, δ2, Q2-A2)

定理3.4尽管提供了形式化方法,但构造的自动机往往比较繁琐(这是许多形式化方法的通病),举例如下。

例子3.16 设有下面两个定义在字母表{0, 1}上语言:

L1={x | x不含00子串}

L2={x | x以01结尾}

接受它们的自动机见图3-10a,为了构造接受L1?L2的自动机,首先构造初始状态(A, P),然后从初始状态出发,计算转移函数的各种结果,如

δ((A, P), 0)= (δ1(A, 0), δ2(P, 0))=(B, Q)

δ((A, P), 1)= (δ1(A, 1), δ2(P, 1))=(A, P)

产生一个新状态(B, Q),再计算新状态的转移函数结果,整个过程直到没有新状态加入才停止,最后确定接受状态,本例的接手状态是(A, P)和(B, Q),参见图3-10b。根据定理3.4的方法生成的状态集显然太大了,有些状态无法从初始状态到达,可以省略掉,参见图3-10c。本例还可以进一步简化,状态(C, P)、(C, Q)、(C, R)都不是接受状态,而且一旦进入这三个状态,就不能出去,可以合并成一个失败状态,简化后的结果参见图3-10d。

正则表达式教程

正则表达式 学习要点: 1.什么是正则表达式 2.创建正则表达式 3.获取控制 4.常用的正则 假设用户需要在HTML 表单中填写姓名、地址、出生日期等。那么在将表单提交到服 务器进一步处理前,JavaScript 程序会检查表单以确认用户确实输入了信息并且这些信息是 符合要求的。 一.什么是正则表达式 正则表达式(regular expression)是一个描述字符模式的对象。ECMAScript 的RegExp 类 表示正则表达式,而String 和RegExp 都定义了使用正则表达式进行强大的模式匹配和文本 检索与替换的函数。 正则表达式主要用来验证客户端的输入数据。用户填写完表单单击按钮之后,表单就会 被发送到服务器,在服务器端通常会用PHP、https://www.360docs.net/doc/9611620129.html, 等服务器脚本对其进行进一步处理。 因为客户端验证,可以节约大量的服务器端的系统资源,并且提供更

好的用户体验。 二.创建正则表达式 创建正则表达式和创建字符串类似,创建正则表达式提供了两种方法,一种是采用new 运算符,另一个是采用字面量方式。 1.两种创建方式 var box = new RegExp('box'); //第一个参数字符串 var box = new RegExp('box', 'ig'); //第二个参数可选模式修饰符 模式修饰符的可选参数 参数含义 i 忽略大小写 g 全局匹配 m 多行匹配 var box = /box/; //直接用两个反斜杠 var box = /box/ig; //在第二个斜杠后面加上模式修饰符 2.测试正则表达式 RegExp 对象包含两个方法:test()和exec(),功能基本相似,用于测试字符串匹配。test() 方法在字符串中查找是否存在指定的正则表达式并返回布尔值,如果存在则返回true,不存 在则返回false。exec()方法也用于在字符串中查找指定正则表达式,如果exec()方法执行成

VC正则表达式的使用

VC正则表达式的使用 2010年9月11日星期六邵盛松 正则表达式是一种对字符进行模糊匹配的一个公式。在数据有效性验证,查找,替换文本中都可以使用正则表达式。 本篇文章主要描述的是使用ATL中两个模板类CAtlRegExp和CAtlREMatchContext。 在使用CAtlRegExp类之前需要添加#include 这个头文件。 RegExp是Regular Expression的缩写 以匹配邮件地址字符串为例说明两个类的使用 该示例更改自https://www.360docs.net/doc/9611620129.html,/en-us/library/k3zs4axe(VS.80).aspx CString strRegex=L"({[0-9_]+@[a-zA-Z0-9]+[.][a-zA-Z0-9]+[.]?[a-zA-Z0-9]+})"; CString strInput; strInput=L"admin@https://www.360docs.net/doc/9611620129.html,"; CAtlRegExp reRule; wchar_t *wt = (wchar_t *)(LPCTSTR)strRegex; REParseError status = reRule.Parse((const ATL::CAtlRegExp::RECHAR *)wt); if (REPARSE_ERROR_OK != status) { return 0; } CAtlREMatchContext mcRule; wt = (wchar_t *)(LPCTSTR)strInput; if (!reRule.Match((const ATL::CAtlRegExp::RECHAR *)wt,&mcRule)) { AfxMessageBox(L"您输入的邮件地址不合法!"); } else { for (UINT nGroupIndex = 0; nGroupIndex < mcRule.m_uNumGroups; ++nGroupIndex) { const CAtlREMatchContext<>::RECHAR* szStart = 0;

编译原理课程设计

<PL0编译器-PCompiler> 软件需求说明书 作者:刁诗云、麻汉华、潘彦荃、周津、李程完成日期:2009年6月7日 签收人: 签收日期: 修改情况记录:

目录 软件需求说明书 (1) 1 引言 (1) 1.1 编写目的 (1) 1.2 项目背景 (1) 2 项目概述 (2) 2.1 产品描述 (2) 2.2 产品功能 (2) 2.3 用户特点 (2) 3 具体需求 (3) 3.1 EBNF定义的PL/0文法 (3) 3.2 语法图 (4) 3.3 功能需求 (6) 3.4 系统概要设计 (15)

1 引言 1.1 编写目的 为了清楚表达客户提出的需求,便于用户理解和确认项目所包含的具体功能需求、性能需求以及非公能性需求,因此以文件化的形式,把系统整体及其部分的业务流程、系统功能进行了详细的说明。同时,此文也对开发人员起到引导的作用,请认真阅读。 1.2 项目背景 PL/0是由世界著名计算机科学家、PASCAL语言的创始人N.Wirth教授选择提供的。在选择PL/0语言的过程中,Wirth很费了一番脑筋。一方面他希望借助这个语言,能尽可能把程序设计语言和编译技术一些最重要的内容都讲到;但另一方面又不希望内容太多,太杂,而希望尽可能简单一些,以便与有限的课时和课程范围相适应。于是他精心选择提供了这个PL/0语言。事实证明,它非常适合于编译技术的教学,目前已被国内越来越多的编译教材所采用。 PL/0语言的语句类型比较丰富,能适应各种可能的程序结构。最进本的是赋值语句。组合结构语句有语句串、条件语句和循环语句。还有重要的子程序概念,是通过过程说明和过程调用两部分实现的。至于数据类型和数据结构,PL/0则特别简单,只有整数类型一种,没有数据结构,因此只允许有整常数和整数变量的说明以及相应的算术运算表达式。PL/0允许在一个过程范围内说明常数、变量和过程。这些常数、变量和过程只在它们被说明的过程范围内有效。PL/0语言也允许递归调用,既可以间接递归,也可以直接递归。

正则表达式语法完整版

正则表达式基础知识 一个正则表达式就是由普通字符(例如字符a 到z)以及特殊字符(称为元字符)组成的文字模式。该模式描述在查找文字主体时待匹配的一个或多个字符串。正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。如:

下面看几个例子: "^The":表示所有以"The"开始的字符串("There","The cat"等); "of despair$":表示所以以"of despair"结尾的字符串; "^abc$":表示开始和结尾都是"abc"的字符串——呵呵,只有"abc"自己了;"notice":表示任何包含"notice"的字符串。 '*','+'和'?'这三个符号,表示一个或一序列字符重复出现的次数。它们分别表示“没有或更多”,“一次或更多”还有“没有或一次”。下面是几个例子: "ab*":表示一个字符串有一个a后面跟着零个或若干个b。("a", "ab", "abbb",……);"ab+":表示一个字符串有一个a后面跟着至少一个b或者更多; "ab?":表示一个字符串有一个a后面跟着零个或者一个b; "a?b+$":表示在字符串的末尾有零个或一个a跟着一个或几个b。 也可以使用范围,用大括号括起,用以表示重复次数的范围。 "ab{2}":表示一个字符串有一个a跟着2个b("abb"); "ab{2,}":表示一个字符串有一个a跟着至少2个b; "ab{3,5}":表示一个字符串有一个a跟着3到5个b。

请注意,你必须指定范围的下限(如:"{0,2}"而不是"{,2}")。 还有,你可能注意到了,'*','+'和'?'相当于"{0,}","{1,}"和"{0,1}"。 还有一个'|',表示“或”操作: "hi|hello":表示一个字符串里有"hi"或者"hello"; "(b|cd)ef":表示"bef"或"cdef"; "(a|b)*c":表示一串"a""b"混合的字符串后面跟一个"c"; '.'可以替代任何字符: "a.[0-9]":表示一个字符串有一个"a"后面跟着一个任意字符和一个数字; "^.{3}$":表示有任意三个字符的字符串(长度为3个字符); 方括号表示某些字符允许在一个字符串中的某一特定位置出现: "[ab]":表示一个字符串有一个"a"或"b"(相当于"a|b"); "[a-d]":表示一个字符串包含小写的'a'到'd'中的一个(相当于"a|b|c|d"或者"[abcd]");"^[a-zA-Z]":表示一个以字母开头的字符串; "[0-9]%":表示一个百分号前有一位的数字; "[0-9]+":表示一个以上的数字; ",[a-zA-Z0-9]$":表示一个字符串以一个逗号后面跟着一个字母或数字结束。 你也可以在方括号里用'^'表示不希望出现的字符,'^'应在方括号里的第一位。(如:"%[^a-zA-Z]%"表 示两个百分号中不应该出现字母)。 为了逐字表达,必须在"^.$()|*+?{\"这些字符前加上转移字符'\'。 请注意在方括号中,不需要转义字符。

常用正则表达式

1. 平时做网站经常要用正则表达式,下面是一些讲解和例子,仅供大家参考和修改使用: 2. "^\d+$"//非负整数(正整数+ 0) 3. "^[0-9]*[1-9][0-9]*$"//正整数 4. "^((-\d+)|(0+))$"//非正整数(负整数+ 0) 5. "^-[0-9]*[1-9][0-9]*$"//负整数 6. "^-?\d+$"//整数 7. "^\d+(\.\d+)?$"//非负浮点数(正浮点数+ 0) 8. "^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$"//正浮点数 9. "^((-\d+(\.\d+)?)|(0+(\.0+)?))$"//非正浮点数(负浮点数+ 0) 10. "^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$"//负浮点数 11. "^(-?\d+)(\.\d+)?$"//浮点数 12. "^[A-Za-z]+$"//由26个英文字母组成的字符串 13. "^[A-Z]+$"//由26个英文字母的大写组成的字符串 14. "^[a-z]+$"//由26个英文字母的小写组成的字符串 15. "^[A-Za-z0-9]+$"//由数字和26个英文字母组成的字符串 16. "^\w+$"//由数字、26个英文字母或者下划线组成的字符串 17. "^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$"//email地址 18. "^[a-zA-z]+://(\w+(-\w+)*)(\.(\w+(-\w+)*))*(\?\S*)?$"//url 19. /^(d{2}|d{4})-((0([1-9]{1}))|(1[1|2]))-(([0-2]([1-9]{1}))|(3[0|1]))$/ // 年-月-日 20. /^((0([1-9]{1}))|(1[1|2]))/(([0-2]([1-9]{1}))|(3[0|1]))/(d{2}|d{4})$/ // 月/日/年 21. "^([w-.]+)@(([[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.)|(([w-]+.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(]?)$" //Emil 22. /^((\+?[0-9]{2,4}\-[0-9]{3,4}\-)|([0-9]{3,4}\-))?([0-9]{7,8})(\-[0-9]+)?$/ //电话号码 23. "^(d{1,2}|1dd|2[0-4]d|25[0-5]).(d{1,2}|1dd|2[0-4]d|25[0-5]).(d{1,2}|1dd|2[0-4]d|25[0-5]).(d{1,2}| 1dd|2[0-4]d|25[0-5])$" //IP地址 24. 25. 匹配中文字符的正则表达式:[\u4e00-\u9fa5] 26. 匹配双字节字符(包括汉字在内):[^\x00-\xff] 27. 匹配空行的正则表达式:\n[\s| ]*\r 28. 匹配HTML标记的正则表达式:/<(.*)>.*<\/\1>|<(.*) \/>/ 29. 匹配首尾空格的正则表达式:(^\s*)|(\s*$) 30. 匹配Email地址的正则表达式:\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)* 31. 匹配网址URL的正则表达式:^[a-zA-z]+://(\\w+(-\\w+)*)(\\.(\\w+(-\\w+)*))*(\\?\\S*)?$ 32. 匹配帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$ 33. 匹配国内电话号码:(\d{3}-|\d{4}-)?(\d{8}|\d{7})? 34. 匹配腾讯QQ号:^[1-9]*[1-9][0-9]*$ 35. 36. 37. 元字符及其在正则表达式上下文中的行为:

编译原理作业集第七章

第七章语义分析和中间代码产生 本章要点 1. 中间语言,各种常见中间语言形式; 2. 说明语句、赋值语句、布尔表达式、控制语句等的翻译; 3. 过程调用的处理; 4. 类型检查; 本章目标 掌握和理解中间语言,各种常见中间语言形式;各种语句到中间语言的翻译;以及类型检查等内容。 本章重点 1.中间代码的几种形式,它们之间的相互转换:四元式、三元式、逆波兰表示; 3.赋值语句、算术表达式、布尔表达式的翻译及其中间代码格式; 4.各种控制流语句的翻译及其中间代码格式; 5.过程调用的中间代码格式; 6.类型检查; 本章难点 1. 各种语句的翻译; 2. 类型系统和类型检查; 作业题 一、单项选择题: 1. 布尔表达式计算时可以采用某种优化措施,比如A and B用if-then-else可解释为_______。 a. if A then true else B; b. if A then B else false; c. if A then false else true; d. if A then true else false; 2. 为了便于优化处理,三地址代码可以表示成________。 a. 三元式 b. 四元式 c. 后缀式 d. 间接三元式 3. 使用三元式是为了________:

a. 便于代码优化处理 b. 避免把临时变量填入符号表 c. 节省存储代码的空间 d. 提高访问代码的速度 4. 表达式-a+b*(-c+d)的逆波兰式是________。 a. ab+-cd+-*; b. a-b+c-d+*; c. a-b+c-d+*; d. a-bc-d+*+; 5. 赋值语句x:=-(a+b)/(c-d)-(a+b*c)的逆波兰式表示是_______。 a. xab+cd-/-bc*a+-:=;a. xab+/cd-bc*a+--:=;a. xab+-cd-/abc*+-:=;a. xab+cd-/abc*+--:=; 6. 在一棵语法树中结点的继承属性和综合属性之间的相互依赖关系可以由________来描述。 a. 抽象语法树; b. 语法规则; c. 依赖图; d. 三地址代码; 7. 按照教材中的约定,三地址语句if x relop y then L表示成四元式为。 a. (relop,x,y,L); b. (relop,L,x,y); c. (relop,x,L,y); d. (L,x,y,relop); 8. 在编译程序中,不是常见的中间语言形式。 a.波兰式; b. 三元式; c. 四元式; d. 抽象语法树; 9. 在编译程序中安排中间代码生成的目的是________。 a. 便于提高编译效率; b. 便于提高分析的正确性; c. 便于代码优化和目标程序的移植; d.便于提高编译速度; 10. 按照教材中的约定,下面不是类型表达式: a. boolean; b. type-error; c. real; d. DAG; 11. 一个Pascal函数 function f ( a, b:char ) :↑integer; …… 其作用域类型是: a. char×integer; b. char×char; c. char×pointer(integer); d. integer×integer; 12. 因为标识符可用于多种情况,比如常量标识符、变量标识符、过程标识符等等。因此,在符号表中为了给出各个符号的标志,常给标识符引入一个属性kind,然后在相应产生式的语义动作中添加给kind属性赋值的语句。比如,在在产生式D id:T的语义动作中添加赋值语句id.kind=。 a. V AR; b. CONSTANT; c. PROC; d. FUNC; 13. 下面情况下,编译器需要创建一张新的符号表。 a. 过程调用语句; b. 标号说明语句; c. 数组说明语句; d.记录说明语句; 14. 函数function f(a,b:char):↑integer;… 所以f函数的类型表达式为: a. char×char→pointer(integer); b. char×char→pointer; c. char×char→integer; d. char×char→integer (pointer) 15. 如果一个语言的编译器能保证编译通过的程序,在运行时不会出现类型错误,则称该语言是。 a. 静态的; b. 强类型的; c. 动态的; d. 良类型的; 一.答案:1. b;2. d;3. b;4. d;5. c;6. c.;7. a;8. a;9. c;10. d;11. b;12. a;13. d; 14. a;15. b;

正则表达式

正则表达式定义 正则表达式(regular expression)描述了一种字符串匹配的模式,可以用来检查一个串是否含有某种子串、将匹配的子串做替换或者从某个串中取出符合某个条件的子串等。 列目录时,dir *.txt或ls *.txt中的*.txt就不是一个正则表达式,因为这里*与正则式的*的含义是不同的。 正则表达式是由普通字符(例如字符a 到z)以及特殊字符(称为元字符)组成的文字模式。正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。 普通字符 由所有那些未显式指定为元字符的打印和非打印字符组成。这包括所有的大写和小写字母字符,所有数字,所有标点符号以及一些符号。 非打印字符 字符含义 \cx 匹配由x指明的控制字符。例如,\cM 匹配一个Control-M 或回车符。x 的值必须为A-Z 或a-z 之一。否则,将c 视为一个原义的'c' 字符。 \f 匹配一个换页符。等价于\x0c 和\cL。 \n 匹配一个换行符。等价于\x0a 和\cJ。 \r 匹配一个回车符。等价于\x0d 和\cM。 \s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]。 \S 匹配任何非空白字符。等价于[^ \f\n\r\t\v]。 \t 匹配一个制表符。等价于\x09 和\cI。 \v 匹配一个垂直制表符。等价于\x0b 和\cK。 特殊字符 所谓特殊字符,就是一些有特殊含义的字符,如上面说的"*.txt"中的*,简单的说就是表示任何字符串的意思。 如果要查找文件名中有*的文件,则需要对*进行转义,即在其前加一个\。ls \*.txt。正则表达式有以下特殊字符。 $ 匹配输入字符串的结尾位置。如果设置了RegExp 对象的Multiline 属性,则$ 也匹配'\n' 或'\r'。要匹配$ 字符本身,请使用\$。 ( ) 标记一个子表达式的开始和结束位置。子表达式可以获取供以后使用。要匹配这些字符,请使用\( 和\)。 * 匹配前面的子表达式零次或多次。要匹配* 字符,请使用\*。 = + 匹配前面的子表达式一次或多次。要匹配+ 字符,请使用\+。 . 匹配除换行符\n之外的任何单字符。要匹配 .,请使用\。 [ 标记一个中括号表达式的开始。要匹配[,请使用\[。 ? 匹配前面的子表达式零次或一次,或指明一个非贪婪限定符。要匹配? 字符,请使用\?。 \ 将下一个字符标记为或特殊字符、或原义字符、或向后引用、或八进制转义符。例如,'n' 匹配字符'n'。'\n' 匹配换行符。序列'\\' 匹配"\",而'\(' 则匹配"("。 ^ 匹配输入字符串的开始位置,除非在方括号表达式中使用,此时它表示不接受该字符集合。要匹配^ 字符本身,请使用\^。 { 标记限定符表达式的开始。要匹配{,请使用\{。| 指明两项之间的一个选择。要匹配|,请使用\|。

【IT专家】在正则表达式中使用OR运算符

本文由我司收集整编,推荐下载,如有疑问,请与我司联系 在正则表达式中使用OR 运算符 How can I use OR in a Java regex? I tried the following, but it’s returning null instead of the text. 如何在Java 正则表达式中使用OR?我尝试了以下,但它返回null 而不是文本。 Pattern reg = Patternpile(“\\*+|#+ (.+?)”);Matcher matcher = reg.matcher(“*kdkdk”); \\ “#aksdasd”matcher.find();System.out.println(matcher.group(1)); 3 The regex syntax for searching for X or Y is (X|Y). The parentheses are required if you have anything else in the pattern. You were searching for one of these patterns: 用于搜索X 或Y 的正则表达式语法是(X | Y)。如果模式中还有其他任何内容, 则必须使用括号。您正在搜索以下模式之一: a literal * repeated one or more times 文字*重复一次或多次 OR 要么 a literal # repeated one or more times, followed by a space, followed by one or more of any character, matching a minimum number of times 文字#重复一次或多次,后跟一个空格,后跟一个或多个任何字符,匹配最少次 数 This pattern matches * using the first part of the OR, but since that subpattern defines no capture groups, matcher.group(1) will be null. If you printed matcher.group(0), you would get * as the output. 此模式使用OR 的第一部分匹配*,但由于该子模式不定义捕获组,因此 matcher.group(1)将为null。如果你打印matcher.group(0),你会得到*作为输 出。 If you want to capture the first character to the right of a space on a line that starts with either “*”or “#”repeated some number of times, followed by a space and at least one

PHP 常用正则表达式 正则

PHP 常用正则表达式正则 平时做网站经常要用正则表达式,下面是一些讲解和例子,仅供大家参考和修改使用:"^\d+$"//非负整数(正整数+ 0) "^[0-9]*[1-9][0-9]*$"//正整数 "^((-\d+)|(0+))$"//非正整数(负整数+ 0) "^-[0-9]*[1-9][0-9]*$"//负整数 "^-?\d+$"//整数 "^\d+(\.\d+)?$"//非负浮点数(正浮点数+ 0) "^(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*))$"//正浮点数"^((-\d+(\.\d+)?)|(0+(\.0+)?))$"//非正浮点数(负浮点数+ 0) "^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$"//负浮点数 "^(-?\d+)(\.\d+)?$"//浮点数 "^[A-Za-z]+$"//由26个英文字母组成的字符串 "^[A-Z]+$"//由26个英文字母的大写组成的字符串 "^[a-z]+$"//由26个英文字母的小写组成的字符串 "^[A-Za-z0-9]+$"//由数字和26个英文字母组成的字符串 "^\w+$"//由数字、26个英文字母或者下划线组成的字符串 "^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$"//email地址 "^[a-zA-z]+://(\w+(-\w+)*)(\.(\w+(-\w+)*))*(\?\S*)?$"//url /^(d{2}|d{4})-((0([1-9]{1}))|(1[1|2]))-(([0-2]([1-9]{1}))|(3[0|1]))$/ // 年-月-日 /^((0([1-9]{1}))|(1[1|2]))/(([0-2]([1-9]{1}))|(3[0|1]))/(d{2}|d{4})$/ // 月/日/年 "^([w-.]+)@(([[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.)|(([w-]+.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(]?)$" //Emil /^((\+?[0-9]{2,4}\-[0-9]{3,4}\-)|([0-9]{3,4}\-))?([0-9]{7,8})(\-[0-9]+)?$/ //电话号码 "^(d{1,2}|1dd|2[0-4]d|25[0-5]).(d{1,2}|1dd|2[0-4]d|25[0-5]).(d{1,2}|1dd|2[0-4]d|25[0-5]).(d{1,2}| 1dd|2[0-4]d|25[0-5])$" //IP地址 匹配中文字符的正则表达式:[\u4e00-\u9fa5] 匹配双字节字符(包括汉字在内):[^\x00-\xff] 匹配空行的正则表达式:\n[\s| ]*\r 匹配HTML标记的正则表达式:/<(.*)>.*<\/\1>|<(.*) \/>/ 匹配首尾空格的正则表达式:(^\s*)|(\s*$) 匹配Email地址的正则表达式:\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)* 匹配网址URL的正则表达式:^[a-zA-z]+://(\\w+(-\\w+)*)(\\.(\\w+(-\\w+)*))*(\\?\\S*)?$ 匹配帐号是否合法(字母开头,允许5-16字节,允许字母数字下划线):^[a-zA-Z][a-zA-Z0-9_]{4,15}$ 匹配国内电话号码:(\d{3}-|\d{4}-)?(\d{8}|\d{7})? 匹配腾讯QQ号:^[1-9]*[1-9][0-9]*$ 元字符及其在正则表达式上下文中的行为: \ 将下一个字符标记为一个特殊字符、或一个原义字符、或一个后向引用、或一个八进制转

java中正则表达式的使用

Java代码 1.Java中在某个字符串中查询某个字符或者某个子字串Java代码

Java代码 3.对字符串的分割 Java代码 如果用正则表达式分割就如上所示,一般我们都会使用下面更简单的方法:Java代码 4.字符串的替换/删除 Java代码 如果要把字符串中的@都给删除,只用要空字符串替换就可以了: Java代码

注:对Pattern类的说明: 1.public final class java.util.regex.Pattern是正则表达式编译后的表达法。 下面的语句将创建一个Pattern对象并赋值给句柄pat:Pattern pat = https://www.360docs.net/doc/9611620129.html,pile(regEx); 有趣的是,Pattern类是final类,而且它的构造器是private。也许有人告诉你一些设计模式的东西,或者你自己查有关资料。这里的结论是:Pattern类不能被继承,我们不能通过new创建Pattern类的对象。 因此在Pattern类中,提供了2个重载的静态方法,其返回值是Pattern对象(的引用)。如:Java代码 1.public static Pattern compile(String regex) { 2.return new Pattern(regex, 0); 3.} 当然,我们可以声明Pattern类的句柄,如Pattern pat = null; 2.pat.matcher(str)表示以用Pattern去生成一个字符串str的匹配器,它的返回值是一个Matcher类的引用。 我们可以简单的使用如下方法:boolean rs = https://www.360docs.net/doc/9611620129.html,pile(regEx).matcher(str).find();

编译原理大作业

《编译原理》实验报告 课程编译原理 实验名称编译原理综合实验 专业 班级 姓名 学号 完成日期2013/6/5

目录 实验一 (2) 实验目的和内容 (2) PL/0语言描述 (2) 内部码对照表 (3) 实验过程及方法 (4) 实验结果 (4) 总结 (5) 实验二 (5) 实验目的 (5) 实验内容及要求 (6) 实验算法 (7) 实验结果 (7) 总结 (8) 实验三 (8) 实验目的 (8) 实验内容 (9) 实现算法 (9) 实验结果 (9) 总结 (12) 实验一 实验目的和内容 1.实验目的:通过完成词法分析程序,了解词法分析的过程。 2.实验内容:用C/C++实现对Pascal的子集程序设计语言的词法识别程序。 3.实验要求:将该语言的源程序,也就是相应字符流转换成内码,并根据需要是否对于标识符填写相应的符号表供编译程序的以后各阶段使用。 PL/0语言描述 PL/0程序设计语言是一个较简单的语言,它以赋值语句为基础,包括顺序、条件和循环三种控制结构。PL/0有子程序(即函数)概念。PL/0中唯一的数据类型是整型,可以用来说明该类型的常量和变量。当然PL/0也具有通常的算术运算和关系运算。

具体的PL/0语法描述如下(采用扩充的BNF表示)。 <程序>→<程序首部> <分程序> {<分程序>}. <程序首部>→PROGRAM标识符; <分程序>→<过程首部> [<常量说明部分>] [<变量说明部分>] <复合语句> <常量说明部分>→CONST <常量定义> {,<常量定义> } ; <常量定义>→标识符= 无符号整数 <变量说明部分>→V AR <变量定义> {;<变量定义>}; <变量定义>→标识符{,标识符}:<类型> <类型>→INTEGER <过程首部>→PROCEDURE标识符;| PROCEDURE标识符(标识符:<类型>); <复合语句>→BEGIN<语句>{;<语句>}END <语句>→<赋值语句>|<条件语句>|<当型循环语句>|<过程调用语句> |<读语句>|<写语句>|<复合语句>|ε <赋值语句>→标识符:=<表达式> <条件语句>→IF<条件>THEN<语句> <条件语句> → if<布尔表达式> then <语句>|if<布尔表达式> then <语句> else <语句> <布尔表达式> → <条件> | !<布尔表达式>| <布尔表达式> && <布尔表达式> <当型循环语句>→WHILE<条件>DO<语句> <过程调用语句>→CALL 标识符| CALL 标识符(<表达式>) <读语句>→READ(标识符{,标识符} ) <写语句>→WRITE(<表达式>{,<表达式>}) <条件>→<表达式><关系运算符><表达式> | ODD<表达式> <表达式>→<项>{<加型运算符><项>} <项>→<因子>{<乘型运算符><因子>} <因子>→标识符| 无符号整数| (<表达式>) <加型运算符>→+|- <乘型运算符>→* | / <关系运算符>→=|<>|<|<=|>|>= 内部码对照表 表1-1 内部码对照表 内码单词内码单词内码单词内码单词 1 PROGRAM 2 CONST 3 V AR 4 INTEGER 5 Call 6 PROCEDURE 7 IF 8 THEN

C语言使用正则表达式(常规方法)

C语言使用正则表达式 据说一个好的程序员是会使用DB和Regular Expression的程序员,可见两者是多么重要。正则表达式是能极大地提高工作效率的工具,使用过Linux下各种具备RE特性的工具的人一定对此深有感触。很多语言都支持RE,用的最多的当然是脚本,其中以perl最盛。不过,用C语言来用RE不是很多见,但是有时候也很有用,我最近也是看到别人说道这个,所以搜了一些资料加上自己的体会来说一说RE在C语言里的应用。C语言本身不具备RE特性,但是有很多库,在Linux下你可以很方便的使用regex.h提供的库。我先贴一段代码展示一下RE在C语言里是怎么用的 1 #include 2 #include 3 #include 4 #include 5 #include 6 7 int main(){ 8 9 char*bematch ="hhhericchd@https://www.360docs.net/doc/9611620129.html,"; 10 char*pattern ="h{3,10}(.*)@.{5}.(.*)"; 11 char errbuf[1024]; 12 char match[100]; 13 regex_t reg; 14 int err,nm =10; 15 regmatch_t pmatch[nm]; 16 17 if((err=regcomp(®,pattern,REG_EXTENDED))<0){ 18 regerror(err,®,errbuf,sizeof(errbuf)); 19 printf("err:%s\n",errbuf); 20 }

编译原理作业集-第五章-修订(精选.)

第五章语法分析—自下而上分析 本章要点 1. 自下而上语法分析法的基本概念: 2. 算符优先分析法; 3. LR分析法分析过程; 4. 语法分析器自动产生工具Y ACC; 5. LR分析过程中的出错处理。 本章目标 掌握和理解自下而上分析的基本问题、算符优先分析、LR分析法及语法分析器的自动产生工具YACC等内容。 本章重点 1.自下而上语法分析的基本概念:归约、句柄、最左素短语; 2.算符优先分析方法:FirstVT, LastVT集的计算,算符优先表的构造,工作原理;3.LR分析器: (1)LR(0)项目集族,LR(1)项目集簇; (2)LR(0)、SLR、LR(1)和LALR(1)分析表的构造; (3)LR分析的基本原理,分析过程; 4.LR方法如何用于二义文法; 本章难点 1. 句柄的概念; 2. 算符优先分析法; 3. LR分析器基本; 作业题 一、单项选择题: 1. LR语法分析栈中存放的状态是识别________的DFA状态。 a. 前缀; b. 可归前缀; c. 项目; d. 句柄; 2. 算符优先分析法每次都是对________进行归约: (a)句柄(b)最左素短语(c)素短语(d)简单短语

3. 有文法G=({S},{a},{S→SaS,S→ε},S),该文法是________。 a. LL(1)文法; b.二义性文法; c.算符优先文法; d.SLR(1)文法; 4. 在编译程序中,语法分析分为自顶向下分析和自底向上分析两类,和LL(1)分析法属于自顶向下分析; a. 深度分析法 b. 宽度优先分析法 c. 算符优先分析法 d. 递归下降子程序分析法 5. 自底向上语法分析采用分析法,常用的是自底向上语法分析有算符优先分析法和LR分析法。 a. 递归 b. 回溯 c. 枚举 d. 移进-归约 6. 一个LR(k)文法,无论k取多大,。 a. 都是无二义性的; b. 都是二义性的; c. 一部分是二义性的; d. 无法判定二义性; 7. 在编译程序中,语法分析分为自顶向下分析和自底向上分析两类,和LR分析法属于自底向上分析。 a. 深度分析法 b. 宽度优先分析法 c. 算符优先分析法 d. 递归下降子程序分析法 8. 在编译程序中,语法分析分为自顶向下分析和自底向上分析两类,自顶向下分析试图为输入符号串构造一个; a. 语法树 b. 有向无环图 c. 最左推导 d. 最右推导 9. 在编译程序中,语法分析分为自顶向下分析和自底向上分析两类,自底向上分析试图为输入符号串构造一个。 a. 语法树 b. 有向无环图 c. 最左推导 d. 最右推导 10. 采用自顶向下分析方法时,要求文法中不含有。 a. 右递归 b. 左递归 c. 直接右递归 d. 直接左递归 11. LR分析是寻找右句型的;而算符优先分析是寻找右句型的。 a. 短语; b. 素短语; c. 最左素短语; d. 句柄 12. LR分析法中分析能力最强的是;分析能力最弱的是。 a. SLR(1); b. LR(0); c. LR(1); d. LALR(1) 13. 设有文法G: T->T*F | F F->F↑P | P P->(T) | a 该文法句型T*P↑(T*F)的最左直接短语是下列符号串________。 a. (T*F), b. T*F, c. P, d. P↑(T*F) 14. 在通常的语法分析方法中,()特别适用于表达式的分析。 a.算符优先分析法b.LR分析法c.递归下降分析法d.LL(1)分析法 15. .运算符的优先数之间有几种关系。 a.3种 b. 2种 c. 4种 d. 1种 16. 算符优先法属于() a.自上而下分析法 b.LR分析法 c.SLR分析法 d.自下而上分析法 17. 在LR分析法中,分析栈中存放的状态是识别规范句型的DFA状态。 a.句柄 b. 前缀 c. 活前缀 d. LR(0)项目 一.答案: 1. b; 2. b; 3. b; 4. d; 5. d; 6. a; 7. c; 8. c; 9. d;10. b;11. d,c;12. c,b;13. a;14. a 15. a;16. d;17. c;

正则表达式

要想真正的用好正则表达式,正确的理解元字符是最重要的事情。下表列出了所有的元字符和对它们的一个简短的描述。 字符描述 \ 将下一个字符标记为一个特殊字符、或一个原义字符、或一个向后引用、或一个八进制转义符。例如,“\n”匹配字符“n”。“\\n”匹配一个换行符。序列“\\”匹配“\”而“\(”则匹配“(”。 ^ 匹配输入字符串的开始位置。如果设置了RegExp对象的Multiline属性,^也匹配“\n”或“\r”之后的位置。 $ 匹配输入字符串的结束位置。如果设置了RegExp对象的Multiline属性,$也匹配“\n”或“\r”之前的位置。 * 匹配前面的子表达式零次或多次。例如,zo*能匹配“z”以及“zoo”。*等价于{0,}。 + 匹配前面的子表达式一次或多次。例如,“z o+”能匹配“zo”以及“zoo”,但不能匹配“z”。+等价于{1,}。 ? 匹配前面的子表达式零次或一次。例如,“do(es)?”可以匹配“does”或“does”中的“d o”。?等价于{0,1}。 {n} n是一个非负整数。匹配确定的n次。例如,“o{2}”不能匹配“Bob”中的“o”,但是能匹配“food”中的两个o。 {n,} n是一个非负整数。至少匹配n次。例如,“o{2,}”不能匹配“Bob”中的“o”,但能匹配“fo o o ood”中的所有o。“o{1,}”等价于“o+”。“o{0,}”则等价于“o*”。 {n,m} m和n均为非负整数,其中n<=m。最少匹配n次且最多匹配m次。例如,“o{1,3}”将匹配“fooooood”中的前三个o。“o{0,1}”等价于“o?”。请注意在逗号和两个数之间不能有空格。 ? 当该字符紧跟在任何一个其他限制符(*,+,?,{n},{n,},{n,m})后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串“oooo”,“o?”将匹配单个“o”,而“o+”将匹配所有“o”。 点匹配除“\n”之外的任何单个字符。要匹配包括“\n”在内的任何字符,请使用像“[\s\S]”的模式。

利用正则表达式进行查找

利用正则表达式进行查找 Js中利用正则表达式进行查找 一、String对象支持四种利用正则表达式的方法,分别为search(),replace(),match(),split() 1、search()方法以正则表达式作为参数,返回第一个与之匹配的子串开始的位置,如果没有任何与之匹配的子串,它返回-1。 2、replace()方法执行检索和替换操作,它的第一个参数是正则表达式,第二个参数是要进行替换的字符串或者闭包。 3、match()方法的唯一一个参数是正则表达式,它的行为取决于这个正则表达式的标志,如果正则表达式包含了标志g,它的返回值就是包含了出现在字符串中匹配的数组。如果该正则表达式不包含标志g,它也返回一个数组,它的第一个元素是匹配的字符串,余下的元素则是正则表达式中的各个分组。 4、split()方法是能够支持模式匹配的。 二、RegExp对象定义了两个用于模式匹配的方法,它们是exec()和test() 1、RegExp的exec()方法和String的match()方法很类似,它对一个指定的字符串执行一个正则表达式匹配,如果没有找到任何一个匹配,它将返回null,否则返回一个数组,这个数组的第一个元素包含的是与正则表达式相匹配的字符串,余下的所有元素包含的是匹配的各个分组。而且,正则表达式对象的index属性还包含了匹配发生的字符串的位置,属性input 引用的则是被检索的字符串。 如果正则表达式具有g标志,它将把该对象的lastIndex属性设置到紧接着匹配字符串的位置开始检索,如果exec()没有发现任何匹配,它将把lastIndex属性重置为0,这一特殊的行为可以使你可以反复调用exec()遍历一个字符串中所有的正则表达式匹配。 2、RegExp对象的test()参数为一个字符串,如果这个字符串包含正则表达式的一个匹配,它就返回true,否则返回false 当一个具有g标志的正则表达式调用test()方法时,它的行为和exec()相同,既它从lastIndex 处开始检索特定的字符串,如果它发现匹配,就将lastIndex设置为紧接在那个匹配之后的字符的位置,这样我们就可以使用方法test()来遍历字符串了。 PS:以上内容摘自月影的《JavsScript王者归来》,一本不错的书。

相关文档
最新文档