二、入门实例扩展
大家还记得第一篇里提到过的查找重复单词的例子吗?完整解决这个问题只需要perl之类的语言写几行代码。
比如我们想确定每个文件中“ResetSize”出现的次数与“SetSize”出现的次数是否一样多。使用perl命令如下:
% perl -0ne ‘print “$ARGV\n” if s/RestSize//ig != s/SetSize//ig’ *
现在可以不明白这条命令,但要注意到这条命令有多简洁。
perl关于文本处理和正则表达式的许多概念来自两种专业化的语言
awk和sed,非常不同于传统的语言,如C。
2.1 使用正则表达式匹配文本
perl可以以多种方式使用正则表达式,最简单的就是检查变量中的文本能否由某个正则表达式匹配。下面的代码检查$reply中所含的字符串,报告这个字符串是否全部由数字组成:
if ($reply = ~ m/^[0-9]+$/)
{
print “only digits\n”;
}
else
{
print “not only digits\n”;
}
第一行代码也许有些奇怪:正则表达式是“^[0-9]+$”,两边的m/…/告诉perl该对正则表达式进行什么操作。m代表尝试进行“正则表达式匹配 (regular expression match)”。斜线用来标记接线。之前的”=~”用来连接m/…/和欲搜索的字符串,即本例中的$reply。可以把”=~”读作匹配。
上面这个正则不能很好的
匹配浮点数。扩展这个表达式,允许有
负数和
可能的小数部分。
(1)容许负数我们就可以添加“
[+-]?”来处理开头的符号。
(2)容许可能出现的小数我们可以添加”(\.[0-9]*)?”。综合起来就是”^[+-]?[0-9]+(\.[0-9]*)?$”。它能够匹配32,-3.23,+98.3这样的文字。
2.2 成功匹配的副作用
第一篇我们看到某些版本的egrep支持作为元字符的”\1”,”\2”用来保存前面的括号内的子表达式实际匹配的文字。perl和其他许多支持正则表达式的语言都支持这些功能,而且匹配成功之后,在正则表达式之外的代码仍然能引用这些匹配的文本。
但是perl是通过变量$1,$2等来指向第一组,第二组括号内的子表达式实际匹配的文本。
现在我们比较”^[+-]?[0-9]+[CF]$”和”^([+-]?[0-9]+)([CF])$”。添加的括号改变了正则表达式的意义了吗?我们前面知道括号的使用场景,这里既没有改变星号或其他量词的作用对象,也没改变多选分支的范围,也就是这个表达式仍然能够匹配相同的文本。不过,他们确实围住了我们期望匹配字符串中有价值的子表达式。在perl里,第一个括号里的内容就会自动保存到$1里,第二个括号匹配的内容会自动保存到$2里,我们可以在程序里通过$1和$2两个变量来引用匹配到的内容。这个括号也称为
捕获型括号,灰常有用。也就是第一篇说到的括号的三个用途的第三种。
2.3 错综复杂的正则表达式
在perl之类的高级语言中,正则表达式的使用与其程序的逻辑是混合在一起的。针对上面的例子我们再增加一个匹配小数的功能,添加一个”(\.[0-9]*)?”就可以,最终为:
“([+-]?[0-9]+(\.[0-9]*)?)([CF])$”。请注意,增加的匹配小数部分是放在原来的第一个括号内部的,这样$2保存的就是新增的小数部分。
这些都是捕获型括号,会按照从左到右的顺序保存到变量里。但是上例中新增的小数部分可能永远不会单独被引用,这样的话能不被保存到变量里就好了。也就是说需要一个
只能用于分组,而不会影响文本的捕获和变量的保存的非捕获型括号。
perl以及近期出现的其他正则表达式流派提供了这个功能。
“(…)”用来分组和捕获,而“(?:...)”只分组不捕获。(联想下[…]和[^…])。这样“^([+-]?[0-9]+(?:\.[0-9]*)?)([CF])$”中的小数部分就不会被捕获,也不会被保存到变量里了。
我们已经见过不同的字符组之间的冲突。在egrep中,我们把正则表达式包含在单引号中。整个egrep命令行写在command-shell提示符后,shell能认出它自己的元字符。例如,对shell来说,空格符就是一个元字符,它用来分隔命令和参数,或者参数与参数。在许多shell中,单引号是元字符,单引号内的字符串中的字符不需要被当做元字符处理(DOS使用双引号)。在shell中使用引号容许我们在正则表达式中使用空格,否则shell会把空格认作参数之间的分隔符,而不是把整个正则表达式传递给egrep。
在perl的正则表达式中,
“\b”通常是匹配一个单词分界符的,
但在字符组中,它匹配一个退格符。前一篇我们提过,字符组可以看做一个独立的“子语言”,它里面的规范不同于正则表达式主体,这条规则也适用于perl(包括任何其他流派的正则表达式)。
补充:
\b:表示匹配一个位置,并不占用任何字符,这个位置的一侧是单词字符,一侧为非单词字符,或字符串的开始或结束位置。
还可以参考:
http://wenku.baidu.com/link?url=iWQ-tay23mSRdlOOLR9u-FLxGnFd0xEIoLNFBKL9gBGj2E5ek4HaEAgGyVibLO5n5av9IhwVtoSqBXhWonpVkZkonl7RssMTw2qC2UF5ERK
2.3.1 使用\s匹配所有空白
讨论空白的问题时,我们可以使用“[ \t]*”,这样做没问题,但许多流派的正则表达式提供了一种方便的办法:”\s”。”\s”看起来类似”\t”, “\t”代表制表符,而”\s”则能表示所有表示”空白字符(whitespace character)”的字符组,其中包括
空格符,制表符,换行符和回车符。
2.3.2 小结
尽管上面大部分例子是关于perl的,但其思想也适用其他语言。
1. 许多工具都有自己的正则表达式流派。perl和egrep可能属于同一个流派,但perl的正则表达式中的元字符更多。许多其他的语言,如java,python,.net等,他们的流派都类似于perl。
2. perl用$variable =~ m/regex/ 来判断一个正则表达式是否能匹配某个字符串。m表示“匹配”(match),而斜线用来标注正则表达式的边界(他们本身不属于正则表达式)。整个测试语句作为一个单元,返回true或者false。
3. 元字符(具有特殊意义的字符)的定义在正则表达式中并不是统一的。
4. perl和其他流派的正则表达式提供了许多用用的简记法(shorthands):
\t 制表符
\n 换行符
\r 回车符
\s 任何“空白”字符(例如空格符,制表符等)
\S 任何除“\s”之外的字符
\w 表示[a-zA-Z0-9],(\w+很有用,可以用来匹配一个单词)
\W 除\w之外的任何字符,也就是[^a-zA-Z0-9]。
\d [0-9],即数字
\D 除\d之外的任何字符,即[^0-9]。
5. /i修饰符表示此测试不区分大小写。
6. (?:…)可以用来分组文本,但并不捕获。
7. 匹配成功之后,perl可以用$1,$2,$3之类的变量来保存相对应捕获型(…)括号内的子表达式匹配的文本。
2.4 使用正则表达式修改文本
到现在,我们遇到的例子都只是从字符串中提取信息。现在我们来看perl和其他许多语言提供的一个正则表达式特性:
替换(也可以叫做‘查找和替换’)。
我们已经看到,”$var =~ m/regex/”尝试用正则表达式来匹配保存在变量中的文本,并返回表示能否匹配的布尔值.与之类似的结构”
$var =~ s/regex/replacement/”则更进一步:如果正则表达式能够匹配$var中的某段文本,则将这段匹配的文本替换为replacement。其中regex与之前m/…/的用法一样,而replacement(位于第二个和第三个斜线之间,不包括斜线)则作为双引号内的字符串。也就是说,在其中可以使用变量,例如$1和$2来引用之前匹配的具体文本。
如$var的值为”Jeff Friedl”, 那么运行: $var =~ s/Jeff/Jeffrey/;
$var的值就变成”Jeffery Friedl”。如果再运行一次,就得到”Jeffreyrey Friedl”。要避免这种情况,我们就可以添加单词分界符的元字符。perl中的单词分界符是”\b”,:
$var =~ s/\bJeff\b/Jeffery/;
2.4.1 举例:修正股票价格
有时候我们得到的股票价格类似”9.05000037272”,我们希望展示的是”9.05”。抽象一下要求就是:
通常保留小数点后两位数,如果第三位不是零,也需要保留,去掉其他的数字。也就是”12.37500392”或”12.375”会被修正为12.375,而37.500被修正为37.50。
假设$price包含了需要修正的字符串,我么可以用:
$price =~ s/(\.\d\d[1-9]?)\d*/$1/;
解释下:最开始的”\.”匹配小数点。接下来的”\d\d”匹配开头的两位小数.”[1-9]?”匹配可能跟在后面的非零数字。到这里,任何匹配的文本都是我们希望保留的,所以我们用括号把它们保存到$1中,然后将$1放入replacement字符串中。正则表达式的末尾”\d*”用来匹配其他数字,但并不是我们需要保留的,所以在括号外,但必须要有。
补充:这里介绍一个类似匹配时的”/i”的选项”/g”,在替换的时候很有用,/g
全局替换符,如果需要检查的字符串包含多行需要替换的文本,每条替换规则都对所有的行生效,我们就必须使用/g。默认不使用时只替换第一个匹配的文本。这里的例子虽然不涉及这个选项,后面会遇到的。类似 “$var =~ s/regex/replacement/g”
2.5 用环视功能为数值添加逗号
大的数值,如果在其间加入逗号,会更容易理解,如”The US population is 298444215”后面的数值可能写成”298,444,215”更自然。如果用正则表达式来修正句子里的数值该如何做呢?
我们应该从这个数的右边开始,每次数3个数字,如果左边还有数字的话就加入一个逗号。但是正则表达式一般都是从左到右工作的。不过梳理一下思路会发现,逗号应该加在“
左边有数字,右边数字的个数正好事3的倍数的位置”。这样,使用一组相对较新的正则表达式特性:“
环视(look around)”能轻松解决这个问题。
环视结构不匹配任何字符,只匹配文本中的特定位置。这一点与单词分界符”\b”,锚点”^”和”$”相似,但是环视更加通用。
2.5.1 顺序环视 (Look ahead)和逆序环视
一种类型的环视叫“顺序环视”,作为表达式的一部分,顺序环视从左到右查看文本,尝试匹配子表达式,如果能够匹配,就返回匹配成功信息。顺序环视又分肯定型和否定性顺序环视两种,比如肯定性顺序环视(positive look ahead)用特殊的序列”(?=…)”来表示。例如”(?=\d)”表示如果当前位置右边的字符是数字则匹配成功。
另一种环视被称为逆序环视,它从右向左查看文本。他用特殊的序列(?<=…)表示,例如”(?<=\d)”表示如果当前位置的左边是一位数字则匹配成功(也就是说,紧跟在数字后面的位置)。
环视不会占用字符。
在理解顺序环视和其他环视功能时需要特别注意一点,即在检查子表达式能否匹配的过程中,他们本身不会占用任何文本。举例说明,普通正则表达式”Jeffrey”匹配文本”… by Jeffrey Friedl.”则匹配到的是 ,
如果用肯定型顺序环视“(?=Jeffery)”匹配到的位置如下:
顺序环视会检查子表达式能否匹配,但它只寻找能够匹配的位置,而不会占用这些字符,通过与普通正则表达式结合可以得到更精确的匹配,如“(?=Jeffery)Jeff”表示只能匹配”Jeffery”这个单词中的”Jeff”。我们还会发现”Jeff(?=rey)”与它是等价的。
下面我们看下”m/(?<=\bJeff)(?=s\b)/’/g”什么意思?
这个表达式实际上并没有匹配任何字符,只是匹配了一个希望插入撇号的位置。
回到2.5节开始提到的在数字中插入逗号的问题。我们希望给一个很长的数字插入逗号,插入逗号的位置必须满足“左边是数字,右边数字的个数正好是3的倍数”。第二个要求用逆序环视很容易解决,”(?=(\d\d\d)+$)”左边只要有一位数字就能满足“左边有数字”的要求,可以用”(?<=\d)”。结合在一起就是” (?<=\d) (?=(\d\d\d)+$)”。
我们还可以使用非捕获型括号”(?:…)”来限制,得到” (?<=\d) (?=(?:\d\d\d)+$)”,这样做的好处一是不会担心与捕获型括号关联的$1是否会被用到,二是效率更高,因为引擎不需要记忆捕获的文本。
2.5.2 单词分界符和否定环视
现在再来讨论给数字插入逗号的问题,如果一个数字在一个句子中间,例如:
$text = “the population of 298444215 is growing.”;
如果用$text =~ s/(?<=\d)(?=(\d\d\d)++$)/,/g;来插入逗号是不会有效的,因为最后的$要求最后是3的倍数位数字结尾。但是我们也不能直接去掉$,因为去掉后就会把数字变成 … of 2,9,8,4,4,4,215 is growing.(仔细想想)
可能初看起来有些棘手,但我们可以用单词分界符”\b”来替换”$”.尽管我们处理的只是数字,perl的单词概念也能解决这个问题。就像”\w”一样,perl和其他语言都把数字,字母和下划线当做单词的一部分。结果,单词分界符的意思就是,在此位置的一侧是单词(例如数字),另一侧不是(比如行的末尾或空格)。
这个“
一侧如此这般,另一侧如此那般”好像很眼熟,就像前面的顺序环视或逆序环视,这里的区别之一在于,有一侧必须使用否定的匹配。这样看来,迄今为止我们用到的顺序环视和逆序环视都应该被称为
肯定顺序环视和肯定逆序环视。因为他们成功的条件是子表达式在这些位置能够匹配。正则表达式还提供了相对应的否定顺序环视和否定逆序环视。
所以单词分界符的意思是,一侧是”\w”,而另一侧不是”\w”,就可以”(?<!\w)(?=\w)”来表示单词起始分界符,用”(?=\w)(?<!\w)”表示单词结束分界符。两者用或结合起来” (?<!\w)(?=\w)| (?=\w)(?<!\w)”就等价于”\b”(从这里我们也看到,如果不使用括号来限制范围,或’|’的范围无限延伸。比如aa|bb匹配“aa或bb”,(aa)(bb)|(cc)(dd)匹配“(aa)(bb)或(cc)(dd),但是(aa)(bb|cc)(dd)中或的范围就只是”bb或cc””)。如果语言本身不支持”\b”就可以用前者代替,但效率不如直接支持的”\b”。
对于前面的插入逗号的为题,我们真正需要的是”(?!\d)”来标记3位数字的起始计数位置,用它来取代”\b”或”$”得到:
$text =~ s/(?<=\d)(?=(\d\d\d)+(?!\d))/,/g;
2.5.3 不通过逆序环视添加逗号
逆序环视和顺序环视一样,所获的支持十分有限,使用也不广泛。尽管perl现在两者都支持,但许多其他语言不是这样。想一想如果不用逆序环视来解决加逗号的问题可能更有意义:
$text =~ s/(\d)(?=(\d\d\d)+(?!\d))/$1,/g;
与之前的区别在于,开头的”\d”所处的肯定逆序环视编程了捕获型括号,replacement字符串则在逗号之前加入了相应的$1。
如果连顺序环视也不用呢?写成:
$text =~ s/(\d)((\d\d\d)+\b)/$1,$2/g;
可以吗?
结果并非期望,会得到类似”281,421906”的字符串,因为”(\d\d\d)+”匹配的数字属于最终匹配文本,是占位的,不能作为”未匹配”部分供下次匹配。但可以借助语言的循环迭代来处理,一次匹配一个位置。
- 大小: 8.1 KB
- 大小: 12.4 KB
- 大小: 56.6 KB
分享到:
相关推荐
链表是一种基础且重要的数据结构,它在计算机科学中被广泛应用,特别是在算法和数据结构设计中。本题目的核心是“链表逆序”,这是一个常见的编程面试和笔试问题,主要考察程序员对链表操作的理解和实现能力。接下来...
使用 XStream 不用任何映射就能实现多数 Java 对象的...不需要修改类,使用 XStream 就能直接序列化/逆序列化任何第三方类。 该下载资源包括示例源码、相关的 xpp3_min-1.1.4c.jar xstream-1.3.jar 包及XStream API
`<filter>`元素定义过滤器类,`<filter-name>`是过滤器的唯一标识,`<filter-class>`指定实现类。`<filter-mapping>`元素则将过滤器与特定的Servlet或URL模式关联。 例如: ```xml <filter> <filter-name>MyFilter...
请求将按配置顺序通过每个过滤器,而响应则逆序返回。如果一个过滤器决定不传递请求(比如因为验证失败),那么后续的过滤器都不会被执行。 ### 使用示例 以下是一个简单的登录检查过滤器的示例: ```java import...
请求会按声明顺序逐个通过过滤器,响应则逆序返回。 5. **配置过滤器**: 在web应用的配置文件web.xml中,通过`<filter>`和`<filter-mapping>`元素定义过滤器及其映射关系。例如: ```xml <filter> <filter-...
<filter-name>LoginFilter</filter-name> <filter-class>com.example.webapp.LoginFilter</filter-class> </filter> <filter-mapping> <filter-name>LoginFilter</filter-name> <url-pattern>/protected/*</url...
给n个数a1,a2…an,如果存在存在ai>aj,且i<j,则称这样的元素对<ai,aj>为一个逆序对 统计这n个数中逆序对的总数 比如说,n=5,a1到a5分别为5,3,1,4,3 则逆序对有 <5,3>,<5,1>,<5,4>,<5,3>,<3,1>,<4,3>共6对
cout << " B" << " " << " " << " ->" << table[2][2] << " ->" << table[2][3] << endl; } 输出分析栈可以使用以下函数实现: void show_stack() { cout << " "; for (int i = 0; i <= s->top; i++) { cout ...
<filter-name>MyFilter</filter-name> <filter-class>com.example.MyFilterClass</filter-class> </filter> <filter-mapping> <filter-name>MyFilter</filter-name> <url-pattern>/somePattern/*</url-pattern>...
<filter-name>LoginCheckFilter</filter-name> <url-pattern>/protected/*</url-pattern> </filter-mapping> ``` 以上配置表示,所有以`/protected/`开头的URL都将通过LoginCheckFilter进行过滤。 **工具应用** ...
#include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #define MAX_HOST_LEN 32 /* 主机名最大长度 */ #define MAX...
4. 此时,栈2中的元素顺序即为原队列的逆序。从栈2顶部开始逐个弹出元素,即可得到逆序输出的队列。 在C语言中,我们可以自定义栈和队列的数据结构。例如,`stack.h`可能定义了栈的结构体和相关操作,如初始化、...
### C++ 使用递归来顺序和逆序输出链表的全部元素 #### 概述 在计算机科学中,链表是一种常见的数据结构,用于存储一系列的数据元素。每个元素包含实际存储的数据和一个指向列表中下一个元素的引用(或指针)。在...
当一个请求到达时,Struts2会按照配置的拦截器栈顺序逐个调用这些拦截器,然后执行Action,最后再按逆序返回。拦截器可以进行如权限验证、日志记录、性能监控等多种操作。 2. **创建自定义拦截器** 创建自定义拦截...
有一实数序列a1,a2,....an,若i<j且ai>aj,则(ai,aj)形成了一个逆序对,请使用分治算法求整个序列中逆序对个数,并分析算法时间复杂度。
本文将通过一个具体的实例——六台电动机顺序启动-逆序停止的PLC编程,来深入探讨这一控制系统的实现方法,并对相关知识点进行总结。 首先,我们来设计一个控制方案。在本实例中,我们假定使用的是三菱PLC或西门子...
<br>内容及步骤:<br>1、 设有一个线性表(e0,e1,e2,e3,…,en-2,en-1)存放在一个一维数组A[arraySize]中的前n个数组元素位置。请编写一个函数将这个线性表原地逆置,即将数组的前n个原地址内容置换为(en-1,en-2,…,e3,...
while (p->next && p->next->data <= s->data) p = p->next; s->next = p->next; p->next = s; } } ``` - **选择排序**:遍历链表,每次找到未排序部分中的最小值,并将其移动到已排序部分的末尾。 ```cpp ...
电机顺序启动 逆序停止
Node<datetype>* s = new Node<datetype>; s->date = x; s->next = p->next; p->next = s; } } ``` #### 删除操作 `cachu` 方法用于删除指定位置的元素。首先找到待删除节点的前一个节点,然后修改其 next ...