找回密码
 加入
搜索
查看: 19136|回复: 19

[AU3基础] 求一正则:字符串里夹杂数字,把其中的数字按从后到前每三个数字间用逗号分开

 火.. [复制链接]
发表于 2013-1-27 08:32:16 | 显示全部楼层 |阅读模式
老师给的作业题,说是主要学零宽断言

也就是要把连续的数字,变成金融里记数:给数字加上',',以便好识别和读出数字,如:
2367903  ==>  2,267,903

如给定数字和其它符号字母等夹杂的字符串
$sStr = "first1234567890back987654321end"
如何通过:
$sRes = StringRegExpReplace($sStr, __________,  ________)
得到结果:
$sRes = "first1,234,567,890back987,654,321end"


============================================================
零宽断言,一直没有学明白,虽然看表面意思好象懂了。
希望写出上面正则表达式的达人,能详细地把匹配过程解释一下。因为很多时候,我发现之所以不能写出想要的正则表达式,就是对匹配过程一知半解。或者说,正则引擎对这个正则表达式是如何工作的。

请授之以鱼,更要授之以渔!谢谢
发表于 2013-1-27 09:06:40 | 显示全部楼层
等待A版出手
发表于 2013-1-27 09:22:26 | 显示全部楼层
网上正则资料浩如烟海…
 楼主| 发表于 2013-1-27 09:51:33 | 显示全部楼层
网上正则资料浩如烟海…
annybaby 发表于 2013-1-27 09:22



    虽然可以说是‘浩如烟海’,但基本上都是要吗简单的罗列,就如StringRegExp函数的帮助文档一样;要吗就是来几个简单的例子,走马观花样的说明一下,学者试着模仿,可以粗通。但遇到稍微复杂的新问题,就只有象我一样抓虾求帮。也就是绝大多数都是知其然而不知其所以然的Howto快餐式的文档。如能讲能正则引擎匹配原理/过程的资料就象比‘浩如烟海’里的一滴水一样少,特别对一个复杂的正则表达式详细讲解正则引擎是如何工作的。
发表于 2013-1-27 10:16:10 | 显示全部楼层
回复 2# haijie1223


    一次正则我是肯定搞不定的。以前弄这个都是正则了多次的:http://www.autoitx.com/thread-14377-1-1.html
 楼主| 发表于 2013-1-27 10:16:49 | 显示全部楼层
例如:
$sStr = "as12345a123bee123"
$aResA = StringRegExp($sStr, "(?<!as|a)123", 3)
$aResB = StringRegExp($sStr, "(?<!as?)123", 3)
为什么$aResA有匹配,而$aResB没有匹配。
昨天听老师讲了,才明白为什么,不然叫我想破脑子,也想不出这是为什么原因。
发表于 2013-1-27 10:29:46 | 显示全部楼层
第一次见到那么复杂的正则,学习
发表于 2013-1-27 11:02:49 | 显示全部楼层

标题

回复 4# runsnake


    每个爷爷都是从孙子过来的,估计没几个人天生就会的吧??
原理性的资料真的很多,并且,如何从一大堆资料里找出自己需要的东西,这也是一种能力,在网络日益发达的今天,也显得特别重要!别人嚼碎了再喂到你嘴里,未必比你自己嚼味道好些…

个人感觉而已,不爱听的无视即可……
发表于 2013-1-27 13:31:20 | 显示全部楼层
本帖最后由 happytc 于 2013-1-27 13:49 编辑

回复 1# runsnake

我来说上几句,发现论坛问正则问题的频率非常高。平时我一般不回答此类问题,因为要大动动筋,活着已经够累了。
首先,我对那个王八蛋翻译成的‘零宽断言’表示十分鄙视,这个比翻译‘鲁棒性’更加不堪,但现在已经成自然了,下面我还是要用这个词。


在解决问题之前,我们需要了解一些关于‘零宽断言’特性
        ⑴ 断言(锚点也一样)和一般的正则表达式符号不同,它不匹配实际的任何字符,而是寻找文本的中的位置,是0长度。
                他们匹配的是字符之前或之后的位置。如$并不是匹配换行符,而是匹配目标字符串的末尾或整个字符串末尾的换行符之前的位置
                (?=pattern), (?<=pattern), (?!Pattern), (?<!pattern) ^, $, \b, \A, \Z 都是这样的只找‘位置’的元字符
       
        ⑵ 对于断言来说,明白两个重要概念:‘当前位置’和‘不消耗字符’,非常重要两条,如(?<=Auto)(?=It)和(?=It)(?<=Auto)是等价的,你能解释吗?
                $sResA = StringRegExpReplace("AutoIt", "(?<=Auto)(?=It)", "'")
                $sResB = StringRegExpReplace("AutoIt", "(?=It)(?<=Auto)", "'")
                你打印下会发现,$sResA和$sResB的结果是一样。若你能解释为什么一样的,说明你理解所谓的‘断言’已经有一定深度了。
                (?<=Auto)表示当前位置的左边是‘Auto’,而(?=It)表示当前位置右边是‘It',也就是只要是断言,匹配了断言的子表达式之后的‘当前位置’
                跟匹配之前的‘当前位置’是同一个位置,这也就是当引擎匹配完断言的子表达式之后,不会消耗掉如上面‘It’两个字符的,
                下一次匹配还是这Auto的o后面开始。
                这也是为什么象表达式:foo(?=bar)bee 无论匹配什么字串,它的结果永远为空的原因,因为无论在断言子表达式匹配前还是匹配后,
                引擎的当前查找位置都在foo后面,这时已经匹配了bar了,而该表达式又想在foo后面匹配bee,那就不可能了。此时到可以这样匹配
                foo(?=bar)bar,虽然可以匹配,但不过是画蛇添足而已


好了,理解了上面基础的两点,我们就可以来做这道题了

① 第一步,我们先来个稍简单的,让:
   $sStr = "1234567890"    也就是字符串只有一个纯多位数字
   这个问题核心就是从数字的最后开始三位三位地数,然后添加逗号,也就是逆序(从右到左),最容易想到的当然是逆序断言了(方向一至嘛)
   于是就有两个思路,一则就是纯只找符合要求的位置,二则就是找符合要求的数字后面,先看第一个思路:
   那么从末尾向右数3位3位地数,用正则很容易想到: ((\d{3})+$), 用$来定位串末尾的位置;再考虑不能在第一位数字前给加上逗号了,于是我们用
   逆序肯定断言 (?<=\d) 来保证(这个表达式的意思就是要找的‘位置’前面不能是数字),这个也容易。因为这里是用的纯找位置的方法,而表达式
   ((\d{3})+$) 在匹配时是要消耗字符的,跟思路不合,于是限定一下,马上想到用顺序肯定断言来限制,((\d{3})+$)于是就变成了 (?=((\d{3})+$))
   现在把二者联系起来,不就可以了
   

$sStr = "1234567890"
$sRes = StringRegExpReplace($sStr, "(?<=\d)(?=(\d{3})+$)", ",")
MsgBox(0, "Result", $sRes)

   
   上面代码已经达成简化过的目标了,我们再深入一下,去掉那个$,表达式成为 (?<=\d)(?=(\d{3})+),结果却成为了 $sStr = "1,2,3,4,5,6,7,890"
        试想一下为什么?这就想从你上面帖子所说的正则引擎如何工作的入手了,不然很难想象为什么会如此结果。我们来解析一下引擎的工作原理:
        第一次匹配:当前搜索位置在1的前面,也就是元字符'^'代表的位置,引擎解析(?<=\d)后发现1前面没有数字,于是这次匹配失败。于是引擎把‘当前位置’移一个字符到达1和2之间
        第二次匹配:引擎从‘当前位置’解析 (?<=\d),发现前面是1,符合“这个位置前面必须是数字”的要求,于是再解析 (?=(\d{3})+)
                        三位三位往后搜有 234 567 890,符合此表达式,于是在1和2之间加上‘,’,匹配完这次后,当前搜索位置再向右移动一个字符,也就是到了2和3之间
        第三次匹配:引擎从‘当前位置’解析 (?<=\d),发现前面是2,符合“这个位置前面必须是数字”的要求,于是再解析 (?=(\d{3})+)
                        三位三位往后搜有 345 678,也符合此表达式(这里虽然90不合要求,但前面已经搜出两组,满足'+'元字符要求的),所以在2和3之间加上逗号
        第四次匹配:以及后的匹配,就跟第三次一样类推了。
        所以表达式里的$保证了字符串以3的倍数位数数字结尾,这很重要!
       
        再来考虑下别的,若把里面的'+'换成 '*'会怎么样,结果是:$sRes = "1,234,567,890,",也就是0后面多了个逗号,怎么来的呢?
        我们自然想到'+'跟'*'的区别,因为后者也可以是零次重复,当引擎的当前位置是0之后时,还会匹配最后一次,此时当然这个‘当前位置’前面是数字0,满足
        (?<=\d),而后面是空位,当'*'取零次时也满足 (?<=\d)(?=(\d{3})*$),于是0后面逗号也就加上了。

② 第二步,来看看复杂点情况,也就是你给的:$sStr = "first1234567890back987654321end"
        这时,我们不能再用'$'来确定最后数字的边界了,仔细想想跟上面的有什么不同呢?不同就在于最后数字0后面不再是串结尾位置,而是任意字符了。
        当然这个‘任意’不能是数字,也就是如何找到:后面字符不是数字的位置,这不简单嘛,用顺序否定断言嘛,于是用(?!\d)替换'$',就可以得到答案了:
        (考虑下,我们这里为什么不用'\D'来替换'$',它也表示非数字呀)

$sStr = "first1234567890back987654321end"
$sRes = StringRegExpReplace($sStr, "(?=\d)(?=(\d{3})+(?!\d))", ",")
MsgBox(0, "Result", $sRes)

        看上面的代码,已经基本满足要求了,但仔细一看,居然在k和9之间也插入了一个逗号。若你把这多余的逗号倒底是怎么来的考虑明白了,
        基本上你对什么是零宽断言和引擎工作原理已经登堂入室了。留给你自己考虑,希望能发帖回复这个是啥原因?对比下面更精确的表达式

$sStr = "first1234567890back987654321end"
$sRes = StringRegExpReplace($sStr, "(?=\d)(\d)(?=(\d\d\d)+(?!\d))", "$1,")
MsgBox(0, "Result", $sRes)


=======================================================================================
上面是思路上对得到第一位是数字而非别的字符上,其实我们还可以微微转换一下思路,就可以得到下面的表达式,同样可以达到目的

$sStr = "first1234567890back987654321end"
$sRes = StringRegExpReplace($sStr, "(\d{1,3})(?=(?:\d{3})+(?!\d))", "$1,")
MsgBox(0, "Result", $sRes)

评分

参与人数 8威望 +5 金钱 +305 贡献 +33 收起 理由
runsnake + 20 非常精彩的文章
3mile + 35 + 5 神一般的科普
zldfsz + 50 + 5 此贴不顶,更待何贴
afan + 5 + 50 + 5 精品文章
lpxx + 5

查看全部评分

发表于 2013-1-27 14:15:36 | 显示全部楼层
$sStr = "first1234567890back987654321end"
$sRes=StringRegExpReplace($sStr,"(\d)(?=(\d{3})+(\D|$))","$1,")
MsgBox(0,"",$sRes)
这个也可以试下
发表于 2013-1-27 14:23:08 | 显示全部楼层
从想表达式到写这些文字,花费了我整整一个小时的周末休息时间呀。这也是我在本版回帖最长的帖子了。
呵,今天心情好,昨天才发了年终奖,哈哈……!
======================================================================
其实我是赞同楼主在#4楼的说法的。中文的正则资料看到很多,很多都是旧瓶换酒而已。
au3的正则引擎是PCRE(Philip Hazel在1997年开发了PCRE,它的引擎质量很高),所以若懂C/C++,可以到官网看看你感兴趣部分源码是怎么写的。
而PCRE和Perl的man page都写得非常详细,了解了基本的正则概念后,完全可以去查看它们:
http://www.pcre.org/pcre.txt
http://perldoc.perl.org/perlre.html#Modifiers

想了解正则引擎是如何工作的,你可以到下面的网站,说得非常详细,深入浅出!
当然PCRE用的是‘正则导向的引擎(regex-directed engines)’,也叫NFA,绝大多数语言和工具都是用的这个流派;相对应的还有少数语言和工具用的是‘文本导向的引擎(text-directed engines),也叫DFA’
当然au3是正则导向的
http://www.regular-expressions.info/tutorial.html#engine
发表于 2013-1-27 17:14:02 | 显示全部楼层
回复 11# happytc


    想了一个下午,看了好多篇有关正则的、特别是零宽断言的文章,依然无法,最后用上了递归才算...呵,你9楼的回复简直就是一篇好文章,循序渐进,深入浅出,使人豁然开朗,多谢了。
发表于 2013-1-27 19:17:56 | 显示全部楼层
本帖最后由 shqf 于 2013-1-27 19:19 编辑

回复 9# happytc


    弱弱地问一下:对为啥不用\D,k与9之间为啥也有逗号,已理解,但对(?=\d)(?=(\d{3})+(?!\d))中的第1个括号有啥用难以理解,不用也可以啊,比如如下:
$sStr = "first1234567890back987654321end"
$sRes = StringRegExpReplace($sStr, "(?<=\d)(?=(\d{3})+(?!\d))", ",")
MsgBox(0, "Result", $sRes)
发表于 2013-1-27 22:21:39 | 显示全部楼层
回复 13# shqf

用(?=\d)(?=(\d{3})+(?!\d))中的(?=\d),纯是为了跟(?<=\d)(?=(\d{3})+(?!\d))对比,更好地理解断言中的‘顺序’和‘逆序’细微的区别,因为很多人可能不了解正则引擎原理,很容易‘想当然’地就想到:要保证后面是数字,于是就写出了(?=\d)

上面的:
第一种思路是纯找位置:(?<=\d)(?=(\d{3})+(?!\d))
第二种思路是‘半找位置’:(\d{1,3})(?=(?:\d{3})+(?!\d))

其实还有一点没有说出来,看看第一种的后半部分:(?=(\d{3})+(?!\d))和第二种的后半部分:(?=(?:\d{3})+(?!\d)),就会发现第二种我特意多写了个‘非捕获型组’,其实不多写这个答案也不会变。
这样做的好处在于,见到这个正则表达式的人不会担心与捕获型括号关联的$1是否会被用到;而且它的效率更高,因为引擎不需要缓存捕获的文本。当然反之,表达式每多一个括号其可读性就会大大降低,更不用说(?:pattern)了,这就需要在两者权衡了,前者表达式更清晰简洁,后者效率更高。
发表于 2013-1-27 22:57:20 | 显示全部楼层
例如:
$sStr = "as12345a123bee123"
$aResA = StringRegExp($sStr, "(?<!as|a)123", 3)
$aResB = StringRegExp($sStr, "(?<!as?)123", 3)
为什么$aResA有匹配,而$aResB没有匹配。
昨天听老师讲了,才明白为什么,不然叫我想破脑子,也想不出这是为什么原因。
runsnake 发表于 2013-1-27 10:16


这种有什么搞不明白的,au3所用的正则流派(Flavor)PCRE不支持逆向断言时其逆向查找的子表达式有不确定性嘛(你给的例子里有'?'),之所以这样规定,因为逆向查找太多的字符串会占用大量资源,并且效率以指数降低。而顺序查找,就不会有这个限制。当然有别的正则流派的确支持逆向查找时,跟顺序查找时一样,支持无限多字符。你要是经常用egrep,ed,awk等工具,就会发现其正则有好多限制的。
您需要登录后才可以回帖 登录 | 加入

本版积分规则

QQ|手机版|小黑屋|AUTOIT CN ( 鲁ICP备19019924号-1 )谷歌 百度

GMT+8, 2024-12-26 00:24 , Processed in 0.079629 second(s), 24 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表