一.问题
Where’s Wally
Description:
Write a function that returns the index of the first occurence of the word “Wally”. “Wally” must not be part of another word, but it can be directly followed by a punctuation mark. If no such “Wally” exists, return -1.
Examples:
"Wally" => 0 "Where's Wally" => 8 "Where's Waldo" => -1 "DWally Wallyd .Wally" => -1 "Hi Wally." => 3 "It's Wally's." => 5 "Wally Wally" => 0 "'Wally Wally" => 7
摘自codewars
嗯,在字符串里找笨蛋(Wally)的位置,模式匹配问题
二.解法
2种方案:自己解析 or 正则表达式
自己解析肯定能搞定,不再赘述,这里主要讨论正则表达式解法
1.最初版本(有bug)
function wheresWally(string){console.log(string);
var regex = /(^| )Wally($|[.' ])/;
var pos = -1;
if(string.test(regex)) {
pos = string.indexOf('Wally');
}
return pos;
}
这个实现看起来好像没什么问题,其实存在bug:
'aWally Wally' => 1
期望返回结果是7,原因在于string.indexOf
只返回第一次匹配成功的位置,我们想要知道的是regex
第一次匹配成功的位置,所以string.lastIndexOf
也没有用
regex.test
只简单地返回true/false
,我们无从得知index
,所以regex.test
不适用于这个问题
2.修正版本(有bug)
function wheresWally(string){console.log(string);
var regex = /(^| )Wally($|[.' ])/;
var pos = -1;
var res = regex.exec(string);
if (res !== null) {
pos = res.index;
}
return pos;
}
这次改用regex.exec
来做,exec
是最强大的正则表达式方法了,肯定能够提供我们需要的信息,MDN的API如下:
// Match "quick brown" followed by "jumps", ignoring characters in between
// Remember "brown" and "jumps"
// Ignore case
var re = /quick\s(brown).+?(jumps)/ig;
var result = re.exec('The Quick Brown Fox Jumps Over The Lazy Dog');
Object | Property/Index | Description | Example |
result |
[0] |
The full string of characters matched | Quick Brown Fox Jumps |
[1], ...[n ] |
The parenthesized substring matches, if any. The number of possible parenthesized substrings is unlimited. | [1] = Brown |
|
index |
The 0-based index of the match in the string. | 4 |
|
input |
The original string. | The Quick Brown Fox Jumps Over The Lazy Dog |
|
re |
lastIndex |
The index at which to start the next match. When “g” is absent, this will remain as 0. | 25 |
ignoreCase |
Indicates if the “i ” flag was used to ignore case. |
true |
|
global |
Indicates if the “g ” flag was used for a global match. |
true |
|
multiline |
Indicates if the “m ” flag was used to search in strings across multiple line. |
false |
|
source |
The text of the pattern. | quick\s(brown).+?(jumps) |
注意:index
是exec
返回值的属性,而lastIndex
是regex
的属性,很容易搞错
我们只关注index
,它携带了匹配成功的位置。但上面的实现还存在bug:
'aWally Wally' => 6
为什么是6而不是7?仔细看看我们的正则表达式(^| )Wally($|[.' ])
,发现本次成功的匹配是从空格开始的,空格的位置确实是7,这个好办,简单修复下就行:
if (string.charAt(pos) === ' ') {
pos++;
}
其实也可以用string.replace(regex, func(match, p1, p2..., offset, string))
来完成同样的任务,offset
携带了与index
相同的信息
特别注意:global
模式对exec
的影响是,如果不开g模式,regex.lastIndex
的值一直都是0,如果开了g模式,每执行一次exec
,regex.lastIndex
的值都会更新,还可以手动修改这个值,改变下一次exec
开始的位置,例如:
var regex = /^abc/;
undefined
var regex_g = /^abc/g;
undefined
var str = 'abc abcd';
undefined
regex.lastIndex;
0
regex_g.lastIndex;
0
regex.exec(str);
["abc"]
regex.lastIndex;
0
regex_g.exec(str);
["abc"]
regex_g.lastIndex;
3
regex_g.lastIndex = 1;
1
regex_g.exec(str);
null
3.网友的解法
解法1
function wheresWally(string){
return (" "+string).search(/ Wally\b/)
}
先改原串再匹配,很巧妙
解法2
function wheresWally(string) {
var match = /(^|[ ])Wally\b/.exec(string)
return match ? match.index + match[1].length : -1
}
和笔者思路一致,但简短得多,用match[1].length
巧妙解决空格问题,比if...charAt
漂亮多了
解法3
function wheresWally(string){
var mtch = " ".concat(string).match(/\sWally\b/)
var idx = mtch ? " ".concat(string).indexOf(" Wally") : -1;
return idx;
}
有空格/无空格分开处理,复杂问题简单化的一般方法:分情况
解法4
function wheresWally(string) {
var match = string.match(/(^|\s)Wally($|[^\w])/);
return match ? match.index + match[0].indexOf('Wally') : -1;
}
配合indexOf
解决空格问题,也算不错的思路
三.反思
前辈说的没错,社区是一种重要的学习途径
1.String里与RegExp有关的方法
str.match(regexp)
返回匹配结果数组,或者
null
不开g模式就只匹配一次,开g模式就把所有匹配项都装入结果数组
str.replace(regexp, func)
func
的参数依次是match, p1, p2..., offset, string
~ 匹配部分, 捕获部分1, 捕获部分2…, 匹配位置, 整串str.search(regexp)
返回匹配位置,或者-1
注意:开不开g模式都只返回第一个匹配位置,这一点和
regex.test
一样(开g模式纯属浪费)
2.RegExp
1.属性
regex.lastIndex
下一次匹配将要开始的位置,初始值是0
regex.global/regex.ignoreCase/regex.multiline
对应
g/i/m
三种模式(全局/忽略大小写/多行),返回true/false
regex.source
返回模式串本身(字面量方式中两条斜线之间的部分,或者new方式中转换为字面量后两条斜线之间的部分)
2.方法
regex.exec(str)
返回匹配结果数组,或者null
结果数组中第一个元素匹配部分,后面的元素依次是捕获部分
注意:结果数组还有两个属性
index
匹配位置
input
整串
regex.test()
返回
true/false
,开不开g模式不影响结果regex.compile()过时了,不建议使用
3.联系
regex.test(str)
等价于str.search(regex) !== -1
regex.exec(str)
相当于str.replace(regex, func)
,exec
能提供更多的控制能力(regex.lastIndex
)