일.문제
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.최초 버전(버그 있음)
function wheresWally(string){console.log(string);
var regex = /(^| )Wally($|[.' ])/;
var pos = -1;
if(string.test(regex)) {
pos = string.indexOf('Wally');
}
return pos;
}
이 구현은 문제없어 보이지만, 실제로는 버그가 존재합니다:
'aWally Wally' => 1
기대되는 반환값은 7 이지만, 이유는 string.indexOf 가 처음 매칭 성공한 위치만 반환하기 때문입니다. 우리가 알고 싶은 것은 regex 가 처음 매칭 성공한 위치이므로, string.lastIndexOf 도 쓸모없습니다
regex.test 는 단순히 true/false 만 반환하며, index 를 알 수 없습니다. 따라서 regex.test 는 이 문제에 적합하지 않습니다
##2.수정 버전(버그 있음)
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 에만 주목하며, 이는 매칭 성공한 위치를 보유하고 있습니다. 그러나 위의 구현에는 아직 버그가 존재합니다:
'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)을 제공합니다
아직 댓글이 없습니다