如果从MATLAB的基础包(专业的工具箱除外的部分)里面投票: 哪个函数最难学?
我肯定投票给那两个正则表示式函数: regexp(regexpi的功能与regexp类似, 差别小)和regexprep.
这两个函数我学了N次, 或者半途而废, 或者学完就忘(这篇文章的目的就是写给未来的自己, 怕以后忘了, 能看完这篇文章快速想起来).
我写完正则表达式以后, 感觉到了它的强大, 并且发现它能实现几乎所有的字符处理函数功能.
当然, 我的动机并不是想要让正则表达式来代替那些函数, 而是借此机会, 去练习一下刚刚好不容易入门的正则表达式.
正则表达式的一个典型应用: 网络爬虫.
一般一个简单的网络爬虫(不考虑大规律的爬虫, 也不考虑反反爬虫)的典型流程:
1 将网页的内容下载下来.
可以使用webread函数或者urlread函数等.
2 提取出你感兴趣的部分
可以网页的内容很多, 有图片, 有文字, 有链接, 有声音, 有视频等等.
可以通过regexp等字符处理函数的功能提取出你感兴趣的部分.
其中regexp的功能最强大.
---------------正文分割线-------------------------------------------------------------
使用regexprep来实现reverse的功能
使用reverse函数, 将字符串反转.
str = ["airport","control tower","radar","runway"];
newStr = reverse(str)
newStr =
1×4 string 数组
1 至 3 列
"tropria" "rewot lortnoc" "radar"
4 列
"yawnur"
使用regexpprep实现相同的功能.
regexprep(str, '.*', '${fliplr($0)}')
ans =
1×4 string 数组
1 至 3 列
"tropria" "rewot lortnoc" "radar"
4 列
"yawnur"
'.*'中的'.'表示任意字符, '*'表示任意长度. 连起来就是任意长度的任意字符, 也就是全选.
'${fliplr($0)}'中'${cmd}'表示执行cmd命令, 返回的结果代替被匹配的字符.
'$0'表示被匹配的字符(这里就是整个单词).
现在就也难理解.
使用regexp来实现strcmp的功能
s1 = ["A","bc";
"def","G"];
s2 = ["B","c";
"def","G"];
tf = strcmp(s1,s2)
tf =
2×2 logical 数组
0 0
1 1
限定为完整的匹配, 而不是部分匹配
"^" + s2 + "$"
ans =
2×2 string 数组
"^B$" "^c$"
"^def$" "^G$"
string支持加法运算来拼接字符串, 而且支持广播(自动扩展, bsxfun的功能), 在这里就很方便了.
"^"表示字符串的起始位置, "$"表示字符串的结束位置.
"^def$"就表示必须完全匹配单词"def", 不能多, 不能少.
regexp(s1, "^" + s2 + "$")
ans =
2×2 cell 数组
{0×0 double} {0×0 double}
{[ 1]} {[ 1]}
返回的是cell型, 而且元素内容是匹配起始位置的索引值.
通过cellfun, 来转换输出类型
cellfun(@(x) ~isempty(x), (regexp(s1, "^" + s2 + "$")))
ans =
2×2 logical 数组
0 0
1 1
使用regexp来实现strtrim的功能
chr = sprintf(' t Remove leading whitespace')
chr =
' Remove leading whitespace'
newChr = strtrim(chr)
newChr =
'Remove leading whitespace'
strtrim的功能是去除字符串头部与尾部的空白字符. 这个例子里面, 尾部没有空白字符.
newChr2 = string(regexp(chr, '^s*(w.*w)s*$', 'tokens'));
newChr2 =
'Remove leading whitespace'
'^s*(w.*w)s*$'中的"^"与"$"我就不说了, 前面说过了.
's'表示空白字符.'*'表示任意个长度.
那么's*'表示任意个空白字符.
'(w.*w)'中的括号表示我要讲将括号里面的内容看成一个整体(与数学里面的括号功能类似)
'w'表示字母或者数字或者下划线, 看到这些, 读者是否有些眼熟? 没错, 就是命名变量名能够选用的字符.
'tokens'表示提取出括号里面的内容, 而不是匹配的所有内容.
再举个例子, 头部与尾部都有空白字符, 而且是一个数组(展示一个MATLAB的向量化运算)
str = [" Gemini "," Apollo ";
" ISS "," Skylab "];
newStr = strtrim(str)
newStr =
2×2 string 数组
"Gemini" "Apollo"
"ISS" "Skylab"
newChr2 = string(regexp(str, '^s*(w.*w)s*$', 'tokens'))
newChr2 =
2×2 string 数组
"Gemini" "Apollo"
"ISS" "Skylab"
使用regexp来实现deblank的功能
str = [" li hata ", "ye da da", " li zhi ha"]
str =
1×3 string 数组
" li hata " "ye da da" " li zhi ha"
deblank(str)
ans =
1×3 string 数组
" li hata" "ye da da" " li zhi ha"
deblank只去除尾部的空白字符, 不去除头部的空白字符.
string(regexp(str, '(.*w)s*$', 'tokens'))
ans =
1×3 string 数组
" li hata" "ye da da" " li zhi ha"
使用regexprep来实现lower的功能
str = 'I Love MATLAB';
regexprep(str, '[A-Z]', '${char($0+32)}')
ans =
'i love matlab'
大写全部变成小写.
'[A-Z]'表示A到Z的字母, 也就是全体大写字母.
double('A')
ans =
65
double('a')
ans =
97
double('Z')
ans =
90
double('z')
ans =
122
double('S')
ans =
83
double('s')
ans =
115
发现了什么没有?
任意大写字母与其对应的小写字母的ASCII码正好相差32!
因此, 大写字母转化为对应的小写字母可以通过以下方式得到:
char('S'+32)
ans =
's'
regexprep(str,'[A-Z]','${char($0+32)}')
不能理解了吧? 所用的知识前面都讲到了.
再来个数组版的:
str = { ...
'Whose woods these are I think I know.' ; ...
'His house is in the village though;' ; ...
'He will not see me stopping here' ; ...
'To watch his woods fill up with snow.'};
regexprep(str, '[A-Z]', '${char($0+32)}')
ans =
4×1 cell 数组
{'whose woods these are i think i know.'}
{'his house is in the village though;' }
{'he will not see me stopping here' }
{'to watch his woods fill up with snow.'}
使用regexprep来实现upper的功能
str = 'I Love MATLAB';
regexprep(str, '[a-z]', '${char($0-32)}')
ans =
'I LOVE MATLAB'
str = { ...
'Whose woods these are I think I know.' ; ...
'His house is in the village though;' ; ...
'He will not see me stopping here' ; ...
'To watch his woods fill up with snow.'};
regexprep(str, '[a-z]', '${char($0-32)}')
ans =
4×1 cell 数组
{'WHOSE WOODS THESE ARE I THINK I KNOW.'}
{'HIS HOUSE IS IN THE VILLAGE THOUGH;' }
{'HE WILL NOT SEE ME STOPPING HERE' }
{'TO WATCH HIS WOODS FILL UP WITH SNOW.'}
这个和lower版的类似, 就不解释了.
使用regexprep来实现insertBefore的功能
任务: 在连续(至少一个)空格前面添加逗号.
str = "bread cheese wine";
regexprep(str, 's+', ',$0')
ans =
"bread, cheese, wine"
's+'中的's'表示空白字符, '+'表示一个或以上.注意与'*'的不同. '*'表示任意个, 也就是0个或以上.
使用regexprep来实现erase的功能
任务: 去除单词"the"以及后面的一个空格.
str = ["the quick brown fox jumps";
"over the lazy dog";
"the tthe then athen"];
regexprep(str, '<the ', '')
ans =
3×1 string 数组
"quick brown fox jumps"
"over lazy dog"
"tthe then athen"
"<"确保匹配的是单词的开头, 而不匹配中间部分.
''就是0长度的字符串, 用0长度的字符串来代替, 不就是相当于删除了吗?
类似的思想自数组里面经常使用:
a = 1:3
a =
1 2 3
a(2) = []
a =
1 3
作为对比:
regexprep(str, 'the ', '')
ans =
3×1 string 数组
"quick brown fox jumps"
"over lazy dog"
"tthen athen"
可以看到, 去除掉"<", 单词"tthe"中"the"被匹配进去了.
使用regexprep来实现eraseBetween的功能
任务: 去除quick与fox之间的单词, 同时, 不能有多余的空格或者缺失空格
str = "The quick brown fox";
regexprep(str, '(?<=quick )[ws]*(?=fox)', '')
ans =
"The quick fox"
'(?<=quick )[ws]*(?=fox)'解释:
'(?<=quick )'表示匹配部分的前面必须是"quick "
'(?=fox)'表示匹配部分的后面必须是"fox"
'[ws]'表示w或s
使用regexp来实现strtok的功能
任务: 提取出第一个单词.
chr = ' Four score and seven years ago';
token = strtok(chr)
token =
'Four'
allwords = regexp(chr, '<w*>', 'match')
allwords =
1×6 cell 数组
1 至 5 列
{'Four'} {'score'} {'and'} {'seven'} {'years'}
6 列
{'ago'}
'<w*>'中'<'表示单词的开始位置, '>'表示单词的结束位置.
'match'表示返回匹配的字符串.
allwords{1}
ans =
'Four'
使用regexp来实现strsplit的功能
任务: 提取出数字
data = '1.21, 1.985, 1.955, 2.015, 1.885';
C = strsplit(data,', ')
C =
1×5 cell 数组
{'1.21'} {'1.985'} {'1.955'} {'2.015'} {'1.885'}
str2double(C)
ans =
1.2100 1.9850 1.9550 2.0150 1.8850
data = '1.21, 1.985, 1.955, 2.015, 1.885';
C = regexp(data, ', ', 'split')
C =
1×5 cell 数组
{'1.21'} {'1.985'} {'1.955'} {'2.015'} {'1.885'}
'split'表示根据匹配的字符, 来切分字符串.
str2double(C)
ans =
1.2100 1.9850 1.9550 2.0150 1.8850
使用regexp来实现splitlines的功能
任务: 按行来进行切分.
chr = 'Whose woods these are I think I know.';
chr = [chr newline 'His house is in the village though;']
chr =
'Whose woods these are I think I know.
His house is in the village though;'
C = splitlines(chr)
C =
2×1 cell 数组
{'Whose woods these are I think I know.'}
{'His house is in the village though;' }
regexp(chr, 'n', 'split')'
ans =
2×1 cell 数组
{'Whose woods these are I think I know.'}
{'His house is in the village though;' }
对于其他的字符处理函数, 我就不写了, 要不然篇幅太长, 而且工作量比较大, 读者有兴趣可以举一反三写出来.
目前, 感觉有难度的字符处理函数为:
join
compose
sprintf
有没有发现有什么共同点?
没错, 都属于"增量"型的函数.
正则表达式本质是匹配出"子集", 然后又后续操作.好像与"增量"型函数功能相反.
------2018年11月14日更新--------------------------------------------------------
接触了一段时间的Cody, 发现regexp, regexprep是神器.
处理字符型问题, 就不说了, 这个它们的本职工作, 这篇文章提到了, 它们几乎可以替代所有的其他字符处理函数.
让我惊奇的是: 它们还能够解数值类的问题!
实战例子:
M31415926:CodyNote009:索引寻址系列探讨(Part.1)zhuanlan.zhihu.com见最底下的几个解法.
吐糟:
距离写这篇文章差不多有6个月了, 我又差不多忘光了这两个函数的用法了!
╮(╯▽╰)╭
原因可能是我平时编程主要是处理数值类型的问题, 字符型问题几乎接触不到, 所以, regexp, regexprep函数也用不上. 所以, 学完就忘.
现在, 发现了它能够处理数值类型的问题, 如果经常用, 应该不容易忘了吧.
创作不易, 请大家"素质三连": 点赞, 收藏, 分享.