str
이 주어질 때, str
의 길이가 5 또는 7이면서 숫자(0~9)로만 구성되어 있는지를 확인해 주는 함수// 정규표현식 사용 function solution(str) { return /^\d{5}$|^\d{7}$/.test(str); } // 정규표현식 미사용 function solution(str) { if(str.length === 5 || str.length === 7) { for(let i = 0; i < str.length; i++) { if(typeof Number(str[i]) !== 'number') return false; } return true; } return false; }
let pattern = /c/; // 찾고자 하는 문자열 pattern.exec('codestates') // 검색하려는 대상을 exec 메소드의 첫 번째 인자로 전달. // 이 경우 'c' 가 포함되어 있으므로, ['c'] 를 반환합니다.
let pattern = /c/; pattern.test('codestates'); // 이 경우는 'codestates'가 'c'를 포함하고 있으므로 true 를 리턴
let pattern = /c/; let str = 'codestates'; str.match(pattern); // str 안에 pattern 이 포함되어 있으므로, ['c'] 를 반환합니다.
let pattern = /c/; let str = 'codestates'; str.replace(pattern, 'C'); // str 안에서 pattern 을 검색한 후 'C' 로 변경하여 그 결과를 리턴합니다. // 여기서는 'Codestates'가 반환됩니다.
"123,456,789".split(",") // ["123", "456", "789"] "12304560789".split("0") // ["123", "456", "789"]
"JavaScript".search(/script/); // -1 대소문자를 구분합니다 "JavaScript".search(/Script/); // 4 "codestates".search(/ode/); // 1
let withi = /c/i; let withouti = /c/; "Codestates".match(withi); // ['C'] "Codestates".match(withouti); // null
let withg = /c/g; let withoutg = /c/; "coolcodestates".match(withg); // ['c', 'c'] "coolcodestates".match(withoutg); // ['c'] g 가 없으면 첫 번째 검색 결과만 반환합니다
let str = `1st : cool 2nd : code 3rd : states`; str.match(/c/gm) // 3개의 행을 검색하여 모든 c 를 반환합니다. // ['c', 'c'] str.match(/c/m) // m은 다중행을 검색하게 해 주지만, g 를 빼고 검색하면 검색 대상을 찾는 순간 검색을 멈추기 때문에 // 첫 행의 ['c'] 만 리턴합니다.
^
^
는 문자열의 처음을 의미하며, 문자열에서 ^
뒤에 붙은 단어로 시작하는 부분을 찾는다. "coding is fun".match(/^co/); // ['co'] "coding is fun".match(/^fun/); // null
$
$
는 문자열의 끝을 의미하며, 문자열에서 $
앞의 표현식으로 끝나는 부분을 찾는다. ^
와 비슷하지만 ^
는 문자열의 시작을 찾는 반면, $
는 문자열의 마지막 부분을 찾는다. "coding is fun".match(/un$/); // ['un'] "coding is fun".match(/is$/); // null "coding is fun".match(/^coding is fun$/); // 문자열을 ^ 와 $ 로 감싸주면 그 사이에 들어간 문자열과 정확하게 일치하는 부분을 찾습니다 // ["coding is fun"]
*
*
는 *
의 바로 앞의 문자가 0번 이상 나타나는 경우를 검색합니다. 아래와 같은 문자열이 있을 때에 /ode*/g
을 사용하게 되면 "od" 가 들어가면서 그 뒤에 "e"가 0번 이상 포함된 모든 문자열을 리턴합니다."co cod code codee coding codeeeeee codingding".match(/ode*/g); // ["od", "ode", "odee", "od", "odeeeeee", "od"]
+
+
도 *
와 같은 방식으로 작동하며, 다만 +
바로 앞의 문자가 1번 이상 나타나는 경우를 검색한다는 점이 *과 다를 뿐입니다."co cod code codee coding codeeeeee codingding".match(/ode+/g); // ["ode", "odee", "odeeeeee"]
?
?
는 *
또는 +
와 비슷하지만, ? 앞의 문자가 0번 혹은 1번 나타나는 경우만 검색합니다. *?
또는 +?
와 같이 ?
는 *
혹은 +
와 함께 쓰는 것도 가능하며, 함께 사용하였을 경우 검색 결과가 어떻게 달라지는지 아래 예시를 통해 비교해 보세요."co cod code codee coding codeeeeee codingding".match(/ode?/g); // ["od", "ode", "ode", "od", "ode", "od"] "co cod code codee coding codeeeeee codingding".match(/ode*?/g); // ["od", "od", "od", "od", "od", "od"] "co cod code codee coding codeeeeee codingding".match(/ode+?/g); // ["ode", "ode", "ode"]
{}
{}
는 *
, *?
, +
, +?
의 확장판으로 생각할 수 있습니다. *
, *?
, +
, +?
가 '0개 이상' 또는 '1개 이상' 검색이 전부였던 반면, {}
는 직접 숫자를 넣어서 연속되는 개수를 설정할 수 있습니다. 아래 예시와 함께 위 표에서 {}
와 *
, *?
, +
, +?
의 차이를 다시 한 번 비교해 보세요.
"co cod code codee coding codeeeeee codingding".match(/ode{2}/g); // 2개의 "e"를 포함한 문자열을 검색합니다. // ["odee", "odee"] "co cod code codee coding codeeeeee codingding".match(/ode{2,}/g); // 2개 이상의 "e"를 포함한 문자열을 검색합니다. // ["odee", "odeeeeee"] "co cod code codee coding codeeeeee codingding".match(/ode{2,5}/g); // 2개 이상 5개 이하의 "e"를 포함한 문자열을 검색합니다. // ["odee", "odeeeee"]
|
는 or 조건으로 검색하여 |
의 왼쪽 또는 오른쪽의 검색 결과를 반환합니다."Cc Oo Dd Ee".match(/O|D/g); // ["O", "D"] "Cc Oo Dd Ee".match(/c|e/g); // ["c", "e"] "Cc Oo Dd Ee".match(/D|e/g); // ["D", "e"] "Ccc Ooo DDd EEeee".match(/D+|e+/g); // + 는 1번 이상 반복을 의미하기 때문에 // ["DD", "eee"] 를 반환합니다.
[]
안에 명시된 값을 검색합니다.[abc] // a or b or c 를 검색합니다. or(|) Operator 로 작성한 a|b|c 와 동일하게 작동합니다. [a-c] // [abc] 와 동일합니다. - 로 검색 구간을 설정할 수 있습니다. "Ccc Ooo DDd EEeee".match(/[CD]+/g); // [] 에 + 등의 기호를 함께 사용할 수도 있습니다. // C or D 가 한 번 이상 반복된 문자열을 반복 검색하기 때문에 // ["C", "DD"] 가 반환됩니다. "Ccc Ooo DDd EEeee".match(/[co]+/g); // ["cc", "oo"] "Ccc Ooo DDd EEeee".match(/[c-o]+/g); // - 때문에 c ~ o 구간을 검색하여 // ["cc", "oo", "d", "eee"] 가 반환됩니다. "AA 12 ZZ Ad %% Az !# dd 54 zz".match(/[A-Za-z]+/g); // a~z 또는 A~Z 에서 한 번 이상 반복되는 문자열을 반복 검색하기 때문에 // ["AA", "ZZ", "Ad", "Az", "dd", "zz"] 를 반환합니다. "AA 12 ZZ Ad %% Az !# dd 54 zz".match(/[A-Z]+/gi); // flag i 는 대소문자를 구분하지 않기 때문에 위와 동일한 결과를 반환합니다. // ["AA", "ZZ", "Ad", "Az", "dd", "zz"] "AA 12 ZZ Ad %% Az !# dd 54 zz".match(/[0-9]+/g); // 숫자도 검색 가능합니다. // ["12", "54"] "aAbB$#67Xz@9".match(/[^a-zA-Z]+/g); // [] 안에 ^ 를 사용하면 anchor 로서의 문자열의 처음을 찾는것이 아닌 // 부정을 나타내기 때문에 [] 안에 없는 값을 검색합니다. // ["$#67", "@9"]
\d
와 \D
\d
는 digit 을 의미하며 0 ~ 9 사이의 숫자 하나를 검색합니다. [0-9] 와 동일합니다.\D
는 not Digit 을 의미하며, 숫자가 아닌 문자 하나를 검색합니다. [^0-9] 와 동일합니다."abc34".match(/\d/); // ["3"] "abc34".match(/[0-9]/) // ["3"] "abc34".match(/\d/g); // ["3", "4"] "abc34".match(/[0-9]/g) // ["3", "4"] "abc34".match(/\D/); // ["a"] "abc34".match(/[^0-9]/); // ["a"] "abc34".match(/\D/g); // ["a", "b", "c"] "abc34".match(/[^0-9]/g); // ["a", "b", "c"]
\w
와 \W
\w
는 알파벳 대소문자, 숫자, (underbar) 중 하나를 검색합니다. [a-zA-Z0-9]와 동일합니다.\W
는 알파벳 대소문자, 숫자, (underbar)가 아닌 문자 하나를 검색합니다. [^a-zA-Z0-9]와 동일합니다."ab3_@A.Kr".match(/\w/); //["a"] "ab3_@A.Kr".match(/[a-zA-Z0-9_]/) // ["a"] "ab3_@A.Kr".match(/\w/g); //["a", "b", "3", "_", "A", "K", "r"] "ab3_@A.Kr".match(/[a-zA-Z0-9_]/g) // ["a", "b", "3", "_", "A", "K", "r"] "ab3_@A.Kr".match(/\W/); // ["@"] "ab3_@A.Kr".match(/[^a-zA-Z0-9_]/); // ["@"] "ab3_@A.Kr".match(/\W/g); // ["@", "."] "ab3_@A.Kr".match(/[^a-zA-Z0-9_]/g); // ["@", "."]
()
()
는 그룹으로 묶는다는 의미 이외에도 다른 몇 가지 의미가 더 있습니다. 하나하나 살펴보겠습니다.1) 그룹화
표현식의 일부를 ()
로 묶어주면 그 안의 내용을 하나로 그룹화할 수 있습니다. 아래 예시를 통해 그룹화와 그렇지 않은 결과의 차이를 비교해 봅시다.
let co = 'coco'; let cooo = 'cooocooo'; co.match(/co+/); // ["co", index: 0, input: "coco", groups: undefined] cooo.match(/co+/); // ["cooo", index: 0, input: "cooocooo", groups: undefined] co.match(/(co)+/); // ["coco", "co", index: 0, input: "coco", groups: undefined] cooo.match(/(co)+/); // ["co", "co", index: 0, input: "cooocooo", groups: undefined] co+ 는 "c"를 검색하고 + 가 "o"를 1회 이상 연속으로 반복되는 문자를 검색해 주기 때문에 "cooo"가 반환되었습니다. 하지만 (co)+ 는 "c" 와 "o" 를 그룹화하여 "co"를 단위로 1회 이상 반복을 검색하기 때문에 "coco"가 반환되었습니다. 여기서 특이한 점은 일치하는 문자열로 반환된 결과가 2개입니다. 이제 이 이유에 대해 알아봅니다.
2) 캡처
()
로 그룹화한다고 하였고, 이를 캡처한다 라고 합니다. 그럼 아래 예시를 통해 캡처했을 경우의 작동방식을 확인합니다.
co.match(/(co)+/); // ["coco", "co", index: 0, input: "coco", groups: undefined]
()
로 "co"를 캡처- 캡처한 "co" 는 일단 당장 사용하지 않고,
+
가 "co"의 1회 이상 연속 반복을 검색- 이렇게 캡처 이외 표현식이 모두 작동하고 나면, 캡처해 두었던 "co"를 검색
- 따라서 2번 과정에 의해 "coco" 가 반환되고, 3번에 의해 "co"가 반환되는 것입니다.
한가지 예시를 더 살펴보겠습니다. 아래 예시 코드의 검색 및 결과 반환 순서는 다음과 같습니다.
"2021code".match(/(\d+)(\w)/); // ["2021c", "2021", "c", index: 0, input: "2021code", groups: undefined]
()
안의 표현식을 순서대로 캡처 ⇒ \d+ 와 \w- 캡처 후 남은 표현식으로 검색 ⇒ 이번 예시에는 남은 표현식은 없습니다.
\d
로 숫자를 검색하되 + 로 1개 이상 연속되는 숫자를 검색 ⇒ 2021\w
로 문자를 검색 ⇒ c- 3번과 4번이 조합되어
"2020c"
가 반환- 첫 번째 캡처한
(\d+)
로 인해2021
이 반환- 두 번째 캡처한
(\w)
로 인해"c"
가 반환
3) 문자열 대체 시 캡처된 값 참조
캡처된 값은 replace() 메소드를 사용하여 문자 치환 시 참조 패턴으로 사용될 수 있습니다. 아래 예시를 살펴보세요. 우선 첫 번째 (\w+)
가 code
를 캡처하고, 두 번째 (\w+)
가 states
를 캡처합니다. (/(\w+)\
와 (\w+)/\
사이의 .
은 .
앞에 역슬래시가 사용되었기 때문에 '임의의 한 문자'가 아닌 기호로서의 온점 . 을 의미합니다.) 각 캡처된 값은 첫 번째는 $1
이 참조, 두 번째는 $2
이 참조하기 때문에 이 참조된 값을 "$2.$1"
이 대체하게 되어 code
와 states
가 뒤바뀐 "states.code"
가 반환됩니다.
"code.states".replace(/(\w+)\.(\w+)/, "$2.$1"); //states.code
()
를 사용하면 그룹화와 캡처를 한다고 하였습니다. 하지만 (?:)
로 사용하면 그룹은 만들지만 캡처는 하지 않습니다.let co = 'coco'; co.match(/(co)+/); // ["coco", "co", index: 0, input: "coco", groups: undefined] co.match(/(?:co)+/); // ["coco", index: 0, input: "coco", groups: undefined] // 위 "캡처" 예시의 결괏값과 비교해 보시기 바랍니다.
(?=)
는 검색하려는 문자열에 (?=여기)
에 일치하는 문자가 있어야 (?=여기)
앞의 문자열을 반환합니다."abcde".match(/ab(?=c)/); // ab 가 c 앞에 있기 때문에 ["ab"] 를 반환합니다. "abcde".match(/ab(?=d)/); // d 의 앞은 "abc" 이기 때문에 null을 반환합니다.
(?!)
는 (?=)
의 부정입니다."abcde".match(/ab(?!c)/); // null "abcde".match(/ab(?!d)/); // ["ab"]
정규표현식은 응용을 통해 유효성 검사뿐 아니라 데이터 스크래핑, 문자열 파싱 등과 같은 다양한 상황에서 사용할 수 있습니다. 더불어 사용법을 숙지해 놓으면 알고리즘 문제에서 더욱 다양한 해결 아이디어를 얻을 수 있습니다
참고 : Regexr