ExcelHome技术论坛

 找回密码
 免费注册

QQ登录

只需一步,快速开始

快捷登录

搜索
EH技术汇-专业的职场技能充电站 妙哉!函数段子手趣味讲函数 Excel服务器-会Excel,做管理系统 效率神器,一键搞定繁琐工作
HR薪酬管理数字化实战 Excel 2021函数公式学习大典 Excel数据透视表实战秘技 打造核心竞争力的职场宝典
让更多数据处理,一键完成 数据工作者的案头书 免费直播课集锦 ExcelHome出品 - VBA代码宝免费下载
用ChatGPT与VBA一键搞定Excel WPS表格从入门到精通 Excel VBA经典代码实践指南
楼主: ggmmlol

[Excel 程序开发] [第127期]替换特定规则范围内的字符[已总结评分]

[复制链接]

TA的精华主题

TA的得分主题

 楼主| 发表于 2019-12-11 18:27 | 显示全部楼层
总结

本期竞赛题,主要考察对VBS正则表达式知识的综合运用。
第一节、审题分析。

1、本题目的目标,概括起来,就是要求把多个特定位置字母'e'替换为数字字符'2'。

对于这种问题,常规的解决办法是:使用循环语句,把源字符串中符合条件要求的'e'逐一找出来,并改写为'2'。但是答题要求中,禁止在新添加的代码中使用循环语句或有循环执行代码功能的语句,所以此路不通。

2、如果不使用循环而对一个字符串的多处字符进行替换,还有以下3种工具可用:a)工作表的替换函数SUBSTITUTE;b)单元格区域的替换方法Replace;c)VBA的替换函数Replace,但这3种方法对于目标字符的匹配能力有限,它们都可以把字符串的每个字母'e'都替换成一个数字字符'2',但做不到选择性地排除那些不符合题目要求的字母'e'。所以,这种方法仍然行不通。

3、在VBA代码中,能够不用循环语句、而对字符串中符合特定规则的多处字符一次性进行精确匹配和替换的工具,只有“正则表达式”对象。

4、现有的高级编程语言几乎都有各自可以使用的正则表达式引擎,这些引擎的功能强弱不一。一些功能较全面的高级正则表达式引擎,可以同时支持“顺序”和“逆序”两种零宽断言的分组,如果使用这种高级正则表达式引擎来解答本问题,就会非常简单。比如,用.Net平台的公用的正则表达式引擎,可以把本题目源字符串中的目标字符(即:紧接在任意两个数字字符之间的连续的一个或多个字母'e'中的每一个e”)以正则表达式精准地描述为:(?<=\de*)e(?=e*\d)使用它的Replace方法,就可以实现准确替换。如下图中用RegexBuddy测试软件所示:

5、但是,本题目的答题要求中,又规定只可以使用VBA语言,禁止直接或间接引用其它编程语言。这样一来,想要引用其它编程语言中的高级正则表达式引擎的路子也是行不通了。分析至此,有一个初步结论:唯有使用VBA语言中可以直接引用的正则表达式引擎的解法才能完全实现本题要求。


6、按第5步分析的结论,需要寻找在VBA语言中可以直接引用的正则表达式引擎,及其相应的具体解决方案。其一,是VBA语言可以直接引用的高级正则表达式引擎,比如本论坛中有过介绍的“增强的正则表达式引擎”NewXing.RegExp;其二,是VBA语言可以直接引用的“VBScript.RegExp”正则表达式引擎。

7、如果引用NewXing.RegExp正则表达式引擎,由于它同时支持“顺序”、“逆序”两种形式的零宽断言分组,按第4步分析的结果,可以确定此方式是能够解决本问题,而且代码也很简单,即便是从没有使用过它的VBAer,在查看过它的帮助文档后也可以正确完成解答,具体代码就不在此列出了。但是,此方式有2点局限:NewXing.RegExp是由第三方应用软件提供的,所以需要安装相应的软件才能使用;而且该软件目前只有32位版本,因此在64位的Office环境中就无法使用了。因为它的此项局限性,如果使用它来解答本问题,将不能获评满分。


8、所以,需要寻找VBA中可以直接引用、且同时兼容于32位和64位的Office环境的正则表达式引擎,目前实际上也仅有“VBScript.RegExp”正则引擎符合这两个要求。但是,在它的帮助文档中,说明它只是“提供简单的正则表达式支持功能”,而不是“全面”的功能。与本题目相关的,就是它不支持“逆序零宽断言”的分组,也就是说,表达式'(?<=\de*)'对于它是无效的,必须把这个表达式转换成VBScript.RegExp支持的且等效的表达式,才能完整地解决本问题。(见下图所示,VBScript.RegExp正则表达式语法所支持的4种分组模式)。

9、经过以上8个步骤的分析,可以看出:本竞赛题根本目标,就是要为“VBScript.RegExp”正则表达式引擎,找出一个突破其“逆序”零宽断言限制的通用方法。现在已知“VBScript.RegExp”正则表达式引擎支持“捕获”分组,以及“顺序零宽断言”的分组,它们都可以用来确定特定的位置,如果可以把“逆序零宽断言”的分组转换成等效的“捕获”分组或“顺序零宽断言”的分组,问题就可以得到解决。这也是解决本题的基本思路。


评分

1

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2019-12-11 18:29 | 显示全部楼层
[广告] Excel易用宝 - 提升Excel的操作效率 · Excel / WPS表格插件       ★免费下载 ★       ★ 使用帮助




本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?免费注册

x

评分

3

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2019-12-11 19:26 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册

第二节 预置参考答案剖析
参考答案,首先在Form_Initialize过程中,对于正则表达式对象Reg预设它的Global和Pattern两项属性,以便随后供ReplaceRestrictive函数调用。
正则表达式的替换方法中的第2参数字符串与Pattern的属性值必须相互紧密配合,对这两项的取值是本题参考答案中最核心的代码,也是正则表达式的精髓所在。


参考答案中,在Form_Initialize过程的代码中,一共列出了8个正则表达式,它们都是正确的,任选其一即可;在ReplaceRestrictive函数的代码里,也提供了“通用解法”和“本题特解”两种选择。下面将对它们逐步剖析。



首先来分析参考答案中Pattern属性的第1式:
((?:(?:[^\de]|^)e+|[\d\D]*?)*)(e(?=e*\d)|$)

它符合以下基本结构形式:

((?:eXp1|[\d\D]*?)*)(eXp0|$)


可以看出,以上基本结构形式的表达式,总体是由两个捕获分组的子表达式,前后顺序连接构成,以此可以把源字符串精确地区分为依次相连的“非目标字符”和“目标字符”两个部分。
其中:eXp0和eXp1都分别是一个合格的正则表达式。
eXp0是匹配目标字符的必要条件的最基本的表达式,可以称为“基本项”;
eXp1则是匹配被目标字符规则所明确排斥的字符的表达式。
如果eXp0不是匹配目标字符的充分条件,那么,就必须以eXp1优先排除掉所有可能对eXp0的期望目标造成“干扰”的所有的例外项。因此,可以把eXp1简称为“干扰项”。
只有在排除了相对于“基本项”的所有“干扰项”之后,“基本项”最终所匹配到的才会是完全符合要求的“目标项”。



本题中,
“基本项”eXp0等于'e(?=e*\d)'
“干扰项”eXp1等于'(?:[^\de]|^)e+'
对于源字符串中的任意一段目标字符,都可以用它的“本身”、“后缀”、“前缀”3项条件来完成限定。
“基本项”eXp0的表达式,可以对目标字符的“本身”和它的“后缀”同时做出限定。
本题中,eXp0描述了目标字符段的“本身”和“后缀”,即分别为'e'和'(?=e*\d)'。
“干扰项”eXp1的表达式,可以用来排除源字符串中,所有那些“本身”已经符合目标字符要求、仅“前缀”条件不符合目标要求的“干扰项”。这样就可以间接地对目标字符的“前缀”做出限定,实现对“逆序零宽断言”表达式的等效转换
在本题中,这些“干扰项”,如果以“逆序零宽断言”的正则表达式来描述,即'(?<!\de*)e',转换成不含“逆序零宽断言”的等价的正则表达式,即'(?:(?!e)\D|^)e+',简化为'(?:[^\de]|^)e+'
在源字符串中,除了“干扰项”eXp1所明确界定的非目标字符之外,还会有基本项eXp0干扰项eXp1所同时排斥的字符,这些字符是既不确定内容、也不确定长度的,因此必须以懒惰模式表示出来,即基本结构形式中的'[\d\D]*?',可以称之为“未定项
源字符串中,它的每一段连续的“非目标”字符,必然完全是由“干扰项”eXp1“未定项”'[\d\D]*?'这两者任意排列组合而成,即:
(?:eXp1|[\d\D]*?)*
它们构成基本结构形式中的第1个捕获分组的内部表达式。因此,基本结构形式第1个捕获分组的表达式是:
((?:eXp1|[\d\D]*?)*)
在源字符串中,它的某一段连续的非目标字符,在无特别限定的情况下,是可以出现在源字符串的开始、中间、或末尾的任意位置。当它出现在字符串的末尾位置时,它的后面就不存在任何字符(包括目标字符)了,自然也就不能匹配目标字符的“基本项”eXp0,因此,还需要把表示字符串末尾位置的元字符$与目标字符的表达式eXp0以分支运算符"|"并列起来,这样就构成了基本形式中的第2个捕获分组的表达式
(eXp0|$)
对于以非目标字符结尾的源字符串,在表达式中添加此“后缀”的分支条件,可以避免以下非预期的匹配:当源字符串不是以“目标字符”结尾时,如果没有此“分支”选择,则正则表达式在最后位置的匹配失败,将会回溯到最后一个“干扰项”eXp1,并把此“干扰项”eXp1的内容,拆分成eXp0的内容和其它字符,而完成一次非预期的匹配。而有此分支时,即使源字符不以目标字符结尾,第1个捕获分组所匹配的“非目标字符”也可以匹配到源字符的末尾位置,并被'$'元字符所锚定,这样就不会发生回溯,自然也就不会把最后的“干扰项”强行拆分匹配。

最后,把以上所得的两个捕获分组做顺序连接,就得到了前述的基本结构形式的表达式。

这种顺序结构,表示了源字符串中的“非目标项”和“目标项”总是“交替”出现的。如果其中有些“目标项”看起来像是连续多次匹配到的,那是因为此时它前面的“非目标项”所匹配到的都是0长度的字符串。

从基本结构形式的表达式中,还可以看出:它能够匹配任意的(包括空或非空的)字符串,这是因为该表达式的展开式中,必定包含一个优先级最低的顶级分支'[\d\D]*?$',它可以在前面所有分支都匹配失败的情况下,做到最后的“保底”匹配。
综上分析,可以从理论上确认以下两点结论:
基本结构形式的表达式'((?:eXp1|[\d\D]*?)*)(eXp0|$)',可以匹配任意源字符串(包括空或非空的字符串);通过优先排除掉所有对eXp0的“干扰项”eXp1,再以“基本项”的表达式eXp0为条件,从而匹配得到所有符合要求的精准无遗漏的目标内容。

如前所述,由于顶级分支'[\d\D]*?$'这种“保底匹配”的存在,任何非空的源字符串都会增加1至2次额外的匹配。但这种额外的匹配,只会发生在字符串的末尾处,而且只会导致替换的结果比预期的结果在末尾位置增加固定长度的固定字符(以本题为例,其替换结果总是在末尾多出固定的2个字符,即“22”,见下图所示),因此,这种额外匹配总是可预期的、因而也是可控的,可以很简单地消除它所带来的影响:从替换结果字符串的左侧开始截取其总字符数减去相应固定长度值的字符。如参考答案中的“通用解法”的代码所示。


而在本题中,因为总是把单个字母'e'替换成单个数字'2',这种替换所得的字符串必定保持与源字符串的长度不变,因此,可以省略计算过程,直接按源字符串长度从替换所得结果字符串的左侧开始进行截取。参考答案中的“本题特解”就是采用这种方式消除在末尾额外增加的字符。

以上分析过程,从理论上阐明了:该基本结构形式的表达式,可以做为VBScript.RegExp正则表达式引擎突破“逆序零宽断言”局限的通用公式。

而且,从事实上,依据该基本结构形式,不仅列出了本题目预置参考答案中的正则表达式第1式,还逐步推导出了满足本题目要求的第2至8式。
其中:
第2式,是把第1式的第1个捕获分组展开而得到;
第3式,是把第2式进行简化,去掉了对替换结果无影响的部分表达式而得到;
第4式,是把第1式的第2个捕获分组的“顺序零宽断言”条件予以再次拆分出例外项,并把例外项转换为第1捕获分组的组成部分而得到;
第5式,是对第4式中的第2个捕获分组简化而得(该简化只能适应于eXp0只匹配单个字符的情况);
第6式,由第5式转换得到,它验证了一个理论:当可以明确而精准地表达所有非目标字符时,则目标字符就可以表示为排除这些非目标字符后的任意字符。
第7式、第8式,则分别是把第5式、第6式中的第1个捕获分组中的零宽断言表达式转换成非零宽断言而得到,以至于它们之中都不再包含任何零宽断言的子表达式,这对于本题目来说,简直就是一个匪夷所思的答案!

根据以上理论分析论证和实践验证的双重结果,证实了基本结构形式的表达式'((?:eXp1|[\d\D]*?)*)(eXp0|$)'是完全正确而且通用的,它所包含的原理,不仅适用于VBScript.RegExp正则表达式引擎,也同样适用于其它功能更全面的高级正则表达式引擎。因此,我把它称为正则表达式的“终极万能公式”。

总结完毕,谢谢大家。(各位帅锅美女把你们的鲜花都冲我扔吧!)

其它应用实例:
实例1、
千分位-正则表达式
http://club.excelhome.net/thread-1451761-1-1.html
帖子的第7、9、10楼中,用正则表达式的“终极万能公式”的套路,编写出了为字符串中的数值添加千位分节符的自定义函数。
实例2、
VBS正则练习题——标记并提取成对括号内的数值求和
http://club.excelhome.net/thread-1507887-1-1.html
(出处: ExcelHome技术论坛)
见帖子第53楼的附件。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?免费注册

x

评分

8

查看全部评分

TA的精华主题

TA的得分主题

发表于 2019-12-11 19:37 | 显示全部楼层

这段可以作为正则学习的进阶资料了……而且一般地方是查阅不到的,因为是原创心得!!!

这道题对于我的好处是:促使我把liu-aguang的帖子:

正则表达式入门与提高---VBA平台的正则学习参考资料
http://club.excelhome.net/thread-1128647-1-1.html
(出处: ExcelHome技术论坛)

打印出来,粗粗地看完了……

虽然现在半生不熟,但好歹多知道了一些……

还是不错的。

TA的精华主题

TA的得分主题

发表于 2019-12-11 22:05 | 显示全部楼层
测试字符串:eeeeeedeegr12345ee56789fgheeeee234eeeee6789kse213eee123
字符串长度:55

正则表达式:((?:(?:[^\de]|^)e+|[\d\D]*?)*)(e(?=e*\d)|$)

(一)
(?:[^\de]|^)e+      用来匹配形如:eeeeee、dee、heeeee、se的部分;

(二)
[\d\D]*?     将匹配到56个字符位置,如果字符串是:ab,则会匹配到3个位置,分别是a前、a后b前(ab之间)、b后;

(三)
(?:(?:[^\de]|^)e+|[\d\D]*?)    非捕获性分组匹配上述(一)、(二);

(四)
(?:(?:[^\de]|^)e+|[\d\D]*?)*    末尾加*旨在将(一)匹配到的连续内容合并:eeeeeedee、heeeee、se;

(五)
((?:(?:[^\de]|^)e+|[\d\D]*?)*)   两端再加括号,旨在产生捕获性分组1,也即:$1;

(六)
(e(?=e*\d))     用来匹配     数字前、若干个e加数字前     的单个e,并同时产生捕获性分组2,也即:$2;


(七)
(e(?=e*\d)|$)    用来在(六)的基础上匹配字符串尾位置,为两部分正则表达式(五)和(七)合并起来能够匹配到字符串尾123做准备,我横竖都觉得,这个    e()|$    的分支选择结构是最为巧妙的;

(八)
((?:(?:[^\de]|^)e+|[\d\D]*?)*)(e(?=e*\d)|$)   由(五)、(七)合并

匹配到的整体结果(共12项):
eeeeeedeegr12345e
e
56789fgheeeee234e
e
e
e
e
6789kse213e
e
e
123
空(字符串尾位置)

$1结果:
eeeeeedeegr12345

56789fgheeeee234




6789kse213


123
空(字符串尾)

$2结果:
e
e
e
e
e
e
e
e
e
e

空(字符串尾)



(九)
替换原理:将整体匹配到的结果替换为分组1连接字符2,即:$12    。


楼主一番操作真可谓是鬼斧神工!


评分

1

查看全部评分

TA的精华主题

TA的得分主题

发表于 2019-12-12 11:02 | 显示全部楼层
ggmmlol 发表于 2019-12-11 19:26
第二节 预置参考答案剖析
参考答案,首先在Form_Initialize过程中,对于正则表达式对象Reg ...

是不是EH开设竞赛题以来第一次无人获得技术分啊?这也是开创了先河了。。。

TA的精华主题

TA的得分主题

发表于 2019-12-12 11:14 | 显示全部楼层
kuangben8 发表于 2019-12-12 11:02
是不是EH开设竞赛题以来第一次无人获得技术分啊?这也是开创了先河了。。。

应该是有评分权限的版主没空评分吧!

点评

已评分。  发表于 2019-12-14 10:36

TA的精华主题

TA的得分主题

 楼主| 发表于 2019-12-12 13:52 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
点评

本期竞赛题目,选题方向为字符串处理。这种问题,如果允许使用循环语句编程处理,解答起来是没有什么技术难度的。
而在“答题要求”中,因为禁止了使用循环语句等多种常用手段,问题的难度达到了可以做为竞赛题目的标准。

经过统计,本期有2个完全正确的解答,为ykqrs和jsgj2023两位分别在12楼和16楼提交的答卷。

具体每个参与人员的最终有效答卷情况如下:
  
会员ID
  
交卷楼层
评卷结果
理由
  
liuguansky
  
2楼
不符合“答题要求”的规定。
使用了表达式求值的Evaluate方法(函数)
  
一把小刀闯天下
  
3楼
不符合“答题要求”的规定。
使用Goto语句构建循环
  
zopey
  
7楼
不符合“答题要求”的规定。
“手动”循环语句,,方法不通用。另外,正则表达式中没有使用“顺序零宽断言”,也没有“顺序零宽断言”转换为等效语句,仅能适应题目中的样本数据,不能适应其它源数据,即使源数据中的连续的e的长度不超过4个,也可能出错。
  
月关
  
9楼
不符合“答题要求”的规定。
使用了递归过程。
  
ykqrs
  
12楼
正确。
符合答题要求,结果正确,方法可通用。
  
mmlzh
  
13楼
不符合“答题要求”的规定。
“手动”循环语句,方法不通用。
  
aoe1981
  
14楼
不符合“答题要求”的规定。
使用了表达式求值的Eval方法(函数)
  
sayhi95
  
15楼
不符合“答题要求”的规定。
使用了两个函数互相调用,外部调用ReplaceRestrictive函数一次,它实际会循环执行多次,属于“障眼法”的Do...Loop循环。
  
jsgj2023
  
16楼
正确。
结果正确,方法可通用。基本符合答题要求,有一个不影响本质效果的小瑕疵:以注释方式删除掉了原CommandButton1_Click过程代码中的'Call Form_Initialize'语句(正确的处理方式应当是在该过程外面添加一个空的Form_Initialize过程。)

对于2个正确答案的分析点评:
1、共同点:都采用了正则表达式、分两步走的替换方法,即预先把“非目标字符”中的干扰项排除在外,再把剩余的符合条件的字符按题意进行替换,最后把此前排除的“干扰项”恢复原状。
2、不同点:预先把“非目标字符”中的干扰项排除在外时,所采用的具体手段有两点不同:
a)、找出“干扰项”的方式不同
12楼,用STRREVERSE函数把源字符串“前后反转”,再用“顺序零宽断言”来匹配出此前应由“逆序零宽断言”来表示的“干扰项”,即在《总结》中“审题分析第9步”提到的第一种基本思路把“逆序零宽断言”转换成等价的“顺序零宽断言”的;16楼,直接把“逆序零宽断言”转换为等效的“捕获分组”,为《总结》中“审题分析第9步”提到的第二种基本思路。
b)、排除“干扰项”影响的方式不同。
12楼,直接把每个“干扰项”替换为一个由生僻字符组成的字符串常量,当完成对“目标项”的处理之后,再把特定的生僻字符串还原为原来的干扰项;16楼,只是在“干扰项”后面添加一个固定的生僻字符作为标记,以便随后的替换时用“顺序零宽断言”就能区分出来,不至被误判处理,最后只需要清除这个生僻字即可。
两相比较,应属第16楼的方法更好。因为当目标字符不是固定的常量时,“干扰项”也是不固定的,12楼的方法把这些“干扰项”替换成固定的生僻字符串时,后续将无法还原。形象比喻,12楼相当于给每个疑 犯理个光头做标记,而16楼相当于给每个疑 犯仅在脸上画个记号做标记。

提前n天起草“总结”的稿子,又花两天时间,终于把总结点评的工作完成!耶!
最后,感谢大家的支持,并让我们一起欢迎、感谢版主大人给大家颁奖!

评分

2

查看全部评分

TA的精华主题

TA的得分主题

 楼主| 发表于 2019-12-12 16:31 | 显示全部楼层
[广告] VBA代码宝 - VBA编程加强工具 · VBA代码随查随用  · 内置多项VBA编程加强工具       ★ 免费下载 ★      ★使用手册
月关 发表于 2019-12-11 15:56
确实强大。
我也想过把每个e单独匹配出来替换,但是正则表达式写不出来。而且,看了楼主的表达式才知道, ...

上一次我出的那个正则练习题的帖子的48楼,我专门用红字体提到“要优先排除干扰字符,然后在剩余的范围内匹配符合目标条件的字符”,还强调了这句话是正则表达式的万能公式的核心原理,你还在那里给我送了花。
可是你没有把这句提示理解透彻啊,要不然这次的技术分你就“妥妥的到手了”,哪会是如今让它“悄悄地溜走了”呢

TA的精华主题

TA的得分主题

 楼主| 发表于 2019-12-12 17:00 | 显示全部楼层
上传参考答案的附件。
并把ykqrs和jsgj2023两人的正确解答也都收录到附件里,并添加用正则表达式自定义函数展示这3种解决方案的工作表。


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?免费注册

x

评分

1

查看全部评分

您需要登录后才可以回帖 登录 | 免费注册

本版积分规则

关闭

最新热点上一条 /1 下一条

手机版|关于我们|联系我们|ExcelHome

GMT+8, 2024-12-24 04:18 , Processed in 0.041176 second(s), 6 queries , Gzip On, MemCache On.

Powered by Discuz! X3.4

© 1999-2023 Wooffice Inc.

沪公网安备 31011702000001号 沪ICP备11019229号-2

本论坛言论纯属发表者个人意见,任何违反国家相关法律的言论,本站将协助国家相关部门追究发言者责任!     本站特聘法律顾问:李志群律师

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