正则表达式-趣现象一则

  昨天帮朋友解决了一个正则表达式问题,很有意思的,和大家分享一下。

  需求很简单,Web页面上一个输入月份的文本框,只能输入1~12,要求用一个RegularExpressionValidator控件进行验证。如何配置正则表达式?

  朋友的想法很直接:([1-9])|([1][0-2])。貌似没有问题,1~9的一位数字或首位为1个位为0~2的两位数,正好表示1~12。然而,这个表达式只能匹配1~9,如果输入了10~12,就会出现问题。经过N次调整也无法解决,朋友濒临崩溃……

  我的解决方法更直接:([1][0-2])|([1-9])。看到么,只是顺序变了一下,问题解决。

  个中原理就涉及到编译原理中的一些理论了,我能体会,但无法详解。仅将问题罗列在此,一是希望其他朋友遇到类似问题时能尽快解决;二是希望有高手降临,释清其中原理。

posted @ 2006-04-22 10:54 Anders Liu 阅读(2741) 评论(24)  编辑 收藏

  回复  引用  查看    
#1楼 2006-04-22 11:12 | GoKu'S Blog      
正则表达式能用得熟感觉就不错了,再了解原理...
([1-9])|([1][0-2]) 这种好像会把12分开匹配
难道| 这个也有优先级问题
  回复  引用    
#2楼 2006-04-22 11:29 | ang [未注册用户]
是这样的, 正则匹配引擎对"|"连接的或匹配。优先匹配前面的组。

10, 12 前的数字1已经符合了前一个组, 所以认为匹配([1-9])这个组.
  回复  引用  查看    
#3楼 [楼主]2006-04-22 11:48 | Anders Liu (lover_P)      
@ang
  那可是反过来,1已经在[1][0~2]这个组里面,应该认为匹配这个组,但1后面没有其他字符了,是不符合[1][0~2]的啊,为什么还可以匹配呢?
  回复  引用    
#4楼 2006-04-22 11:57 | 琪 [未注册用户]
javascript:openScript(news/dismemo.asp?id=138",500,400)

请问这是什么错误??

谢谢了!
  回复  引用    
#5楼 2006-04-22 12:10 | 小d [未注册用户]
因为编译原理里正则表达式是这样解释的

[1][0-2]表示串接,结果是(1)(0|1|2)={10,11,12}

后面再 | 上[1-9]={1,2,3,4,5,6,7,8,9}

所以总的集合就是(1,2,3,4,5,6,7,8,9,10,11,12}符合题目要求
  回复  引用    
#6楼 2006-04-22 12:16 | 小d [未注册用户]
补充一句,编译器都是从左到右扫描的

如果先匹配1~9则匹配成功后就不做 | 后面的 10 ~12匹配部分了

只有调整顺序 先匹配10~12

此时如果只输入一个1 是匹配不上前面部分的,因为前面的集合是10~12 ,这个1会自动去匹配后面的 1~9
  回复  引用    
#7楼 2006-04-22 13:35 | dsvdvfS [未注册用户]
我能体会,但无法详解
  回复  引用  查看    
#8楼 2006-04-22 14:27 | eboy.yang      
第一次回复,留个链接  RegularExpressionValidator浅析 

  回复  引用  查看    
#9楼 2006-04-22 15:01 | Aldebaran's Home      
有没试过
([1-9]{1})|([1][0-2])
  回复  引用  查看    
#10楼 2006-04-22 19:21 | Laser.NET      
^(([1-9])|([1][0-2]))$
改成这个就可以了(加了约束,必须从开始到结束匹配整个字符串)。

对于|运算,正则表达式在处理逻辑运算的时候确实是有优先级(事实上几乎所有的运算处理的时候总有个先后顺序),并且返回最早匹配的运算结果。对于10-12,([1-9])|([1][0-2])匹配的时候都首先匹配了[1-9],因此匹配的结果都是十位数1。如果次序调换一下,就会首先匹配[1][0-2],那么匹配的结果就是两位数了。

其实正则表达式在执行的时候并没有什么错误,也并不是没有找到匹配结果,主要是RegularExpressionValidator控件要求匹配出的结果一定要和用户输入的字符串一样才算通过。比如输入12,([1-9])|([1][0-2])匹配出的结果是1,而"1" != "12",所以没有通过。
  回复  引用    
#11楼 2006-04-22 23:25 | ang [未注册用户]
@Anders Liu (lover_P)
>@ang
>  那可是反过来,1已经在[1][0~2]这个组里面,应该认为匹配这个组,但1后面没
>有其他字符了,是不符合[1][0~2]的啊,为什么还可以匹配呢?

因为你写的那个表达式是贪婪匹配, 不符合时, 会回溯. 于是匹配([1-9]), 匹配成功
  回复  引用  查看    
#12楼 2006-04-23 09:59 | THIN      
呵呵,你写错了吧
<script language="javascript" type="text/javascript">
var match = /^[0-9]|[1][0-2]$/.test('12');
window.alert(match);
</script>
结果为True
  回复  引用  查看    
#13楼 2006-04-23 13:04 | 装配脑袋      
正则表达式都会被翻译成(最小)DFA来执行的,所以正则表达式中的许多运算先后顺序,在消除状态的时候都可能面目全非了。
  回复  引用  查看    
#14楼 [楼主]2006-04-23 21:42 | Anders Liu (lover_P)      
@装配脑袋
  嗯,这个解释没问题,可为啥呢?
  回复  引用    
#15楼 2006-04-24 00:13 | vender [未注册用户]
目前流行的正则式解析引擎大致分两种,一种用dfa实现(有awk (most versions), egrep (most versions), flex, lex, MySQL, Procmail等等),另一种用nfa实现(GNU Emacs, Java, grep (most versions), less, more, .NET languages, PCRE library, Perl, PHP (pcre routines), Python, Ruby, sed (most versions), vi等等),其中nfa的又分几种,行为上大体类似

具体细节三言两语说不完,只好忽略一些,用例子说明用"[1-9]|1[0-2]"匹配"12"时的行为区别:

使用dfa的引擎:首先,将"[1-9]|1[0-2]"转化成一个确定有限自动机
字符集:ansi字符集或unicode字符集
态0:起始态,接受到'1'内部状态转到态1,接受到'2'-'9'时转到态s,否则到态f
态1:接受到'0'-'2'时到态s,否则转到态s
态s:结束态,成功
态f:结束态,失败
匹配12,很显然,状态转换为0->1->s,其中,1->s时接受'0'-'2'比接受其它字符或不接受字符优先,因此会匹配到整个"12"。广义的说,这也是"dfa引擎"的一个特点,尽可能多的匹配目标,也能看到,dfa的正则式引擎是不需要回滚匹配过程的

使用nfa的引擎:自动机理论告诉我们,nfa和dfa是等价的,即nfa总可以转换为dfa,然而这里转化到dfa则会失去引擎的一些功能(比如look-ahead、look-behind、back-reference等等),因此支持这些功能的引擎是不可能采用类似dfa方式实现的,即不可能通过表达式产生完全的状态转换图(虽然局部可以,但为了行为统一一般也不会这么做),匹配的过程大体是:
1、输入"12",其中'1'符合[1-9]的条件
2、匹配完成
可见当输入能够满足|的第一个子表达式"[0-9]"时,引擎就不会再去尝试第二个子表达式"1[0-2]"了,由此可反映出nfa引擎的一个特点,写在前面的alternation优先级更高

从此例中正则式引擎的行为看,它属于nfa引擎

——上述内容是结合《Mastering Regular Expressions》(by Jeffrey E. F. Friedl)所述知识后的个人理解

  回复  引用  查看    
#16楼 2006-04-24 23:19 | equinox-dAVId      
现在正好在学编译原理

对于NFA ,DFA这些很熟

不过对于某种语言里的正则表达式写法不是很了解

第一种写法读入1后就直接进入结束状态了

第二种写法读入1后还会尝试读下一个字符,如果是0-2则进入结束状态,如果没有字符也进入结束状态作为1
  回复  引用  查看    
#17楼 2006-04-25 08:15 | 装配脑袋      
DFA/NFA概念上是完整串运行到接受状态中止,而实际的正则引擎常常是匹配到不能匹配的时候中止,所以造成了NFA和DFA实现的不同。
NFA的猜测和回溯能力是很弱的,只不过是靠同时处于多个状态来完成。这里就是问题所在。同时处于多个状态的NFA在实现的时候到底哪个状态先尝试下一个输入符号?在NFA概念上这些同时发生,所以NFA才和DFA一样,但是在实现代码的时候,很可能是两种情况,一种是某个分支尝试直到匹配失败或成功,第二种是每个分支都尝试1步,然后继续。没有写过正则引擎所以不知道常常是哪一种实现,但如果是某一个分支先尝试到底的话,很可能并运算写在|前面的那个分支(因为常见的文法分析器中二元运算运算是从左到右解析的),所以这样可能就造成了|前边部分先匹配的现象。
  回复  引用  查看    
#18楼 2006-04-25 09:05 | 装配脑袋      
我把NFA和相应的DFA都画出来了,上面是带空转移的NFA,下面是DFA。从图上看,即使DFA也可以提前匹配1,而不继续搜索[0-2]。那就只有引擎实现的方法不同可以决定这里匹配的不同了。



  回复  引用  查看    
#19楼 2006-04-25 09:49 | Laser.NET      
哈哈,终于见识了什么才叫肯钻研的技术牛人啊!^_^,赞一个!
  回复  引用  查看    
#20楼 2006-04-25 10:08 | 装配脑袋      
这只是子集构造出来的DFA,如果转化为最小DFA就更难判断了
最小DFA中p1和p3可以合并,也就是说只有三个状态p0, p1, p2(和一个代表死状态的f)
一下是最小DFA的转移:
p0 -> p1 [1-9]
p1 -> p2 [0-2]
p0 -> f [^1-9]
p1 -> f [^0-2]
p2 -> f .
f -> f.

这样一来,到达p1之后是继续匹配p2,还是接受(终止)就是一个大问题了。不知道DFA型引擎一般是设计为最长匹配还是贪心匹配。
  回复  引用  查看    
#21楼 2006-05-13 13:31 | 维生素C.NET      
您好,很想知道知道对于这个问题的逻辑,使用正则表达式能否有更为清晰的思路的或更高的效率,谢谢您.
文章地址:http://www.cnblogs.com/lovewangshu/archive/2006/05/13/398894.html
  回复  引用    
#22楼 2006-08-22 08:36 | Hatter Jiang [未注册用户]
呵呵,试试下面这个吧
^(([1-9])|([1][0-2]))$
  回复  引用  查看    
#23楼 2006-12-17 18:06 | Wisdom-zh      
这个问题跟正则引擎有关, 有些正则引擎实现的较差, 所以才会这样.
不知楼主在什么正则引擎下使用该正则.

  回复  引用  查看    
#24楼 2006-12-17 18:09 | Wisdom-zh      
Nuva 语言在以下两种情况下都是正确的:

<.
?? '10'.RegexIsMatch('([1-9])|([1][0-2])')
?? '10'.RegexIsMatch('([1][0-2])|([1-9])')
.>

执行结果为:
True
True



标题  
姓名  
主页
Email (只有博主才能看到) 
验证码 *  看不清,换一张 [登录][注册]
内容(请不要发表任何与政治相关的内容)  
  登录  使用高级评论  新用户注册  返回页首  恢复上次提交