본문으로 건너뛰기

JavaScript 의 RegExp

무료2015-09-26#JS#Regular_Expression#JavaScript正则表达式#test#exec#match#search#replace#regex

작은 문제이지만, 상당한 시간을 보냈습니다. JS 의 정규식을 다시 한번 살펴보겠습니다

일.문제

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');
ObjectProperty/IndexDescriptionExample
result[0]The full string of characters matchedQuick Brown Fox Jumps
[1], ...[n ]The parenthesized substring matches, if any. The number of possible parenthesized substrings is unlimited.[1] = Brown
[2] = Jumps
indexThe 0-based index of the match in the string.4
inputThe original string.The Quick Brown Fox Jumps Over The Lazy Dog
relastIndexThe index at which to start the next match. When "g" is absent, this will remain as 0.25
ignoreCaseIndicates if the "i" flag was used to ignore case.true
globalIndicates if the "g" flag was used for a global match.true
multilineIndicates if the "m" flag was used to search in strings across multiple line.false
sourceThe text of the pattern.quick\s(brown).+?(jumps)

주의: indexexec 의 반환값의 속성이며, lastIndexregex 의 속성입니다. 쉽게 혼동할 수 있습니다

우리는 index 에만 주목하며, 이는 매칭 성공한 위치를 보유하고 있습니다. 그러나 위의 구현에는 아직 버그가 존재합니다:

'aWally Wally' => 6

왜 6 이고 7 이 아닐까? 우리의 정규표현식 (^| )Wally($|[.' ]) 를 자세히 보면, 이번 성공한 매칭은 스페이스에서 시작한다는 것을 알 수 있습니다. 스페이스의 위치는 확실히 7 입니다. 이는 간단히 수정할 수 있습니다:

if (string.charAt(pos) === ' ') {
  pos++;
}

실제로는 string.replace(regex, func(match, p1, p2..., offset, string)) 를 사용하여 같은 작업을 완료할 수도 있습니다. offsetindex 와 같은 정보를 보유하고 있습니다

특별주의: 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)을 제공합니다

댓글

아직 댓글이 없습니다

댓글 작성