JavaScript From Zero to Hero_Chapter 1. 문자열과 정규 표현식_2

강명모(codingBear)·2022년 2월 10일
0
post-thumbnail

Chapter 1 – 문자열과 정규 표현식 p.29-46


Keywords in Chaper 1

Regular Expressions
Strings
Methods used to process the strings


Keywords of today's reading

Regular Expressions
Literal Characters
Regular Expression character classes
Flags/Modifiers/Quantifiers


Regular Expressions

Regular Expression(정규 표현식)은 특정 패턴과 문자열이 일치하는지 찾는 데 쓴다.
JavaScript에도 다른 언어들과 마찬가지로 Regular Expression가 존재하는데 RegExp라는 class로 대표된다. RegExp는 String class와 비슷하여 패턴이 복잡한 일치하는 값 찾기 작업을 간단히 하는 데 도움을 준다.

Regular Expression를 쓰기에 앞서 Regular Expression의 문법부터 이해해보자. 우리는 Regular Expression를 짜는 데는 크게 두 가지 방식이 있다.
첫 번째 방식은 regular expression literal을 쓰는 것, regular expression은 아래 보기처럼 '/'로 감싸서 만든다.

let myPattern = /a$/;

두 번째 방식은 RegExp 객체의 constructor function을 쓰는 것이다. RegExp() constructor function을 씀으로써 위의 첫 번째 보기와 똑같은 결과물을 만들 수 있다.

let myPattern = new RegExp('a$');

위의 예시들은 'a'로 끝나는 모든 문자열과 매칭된다. 간단한 regular expression을 예로 들자면 /abc/와 같이 나타낼 수 있는데 'abc'가 연속하는 문자열을 찾을 때 쓰인다. 참고로 regular expression의 약어는 'regexr'로 쓴다.

const myString = `Hi, do you know your abc's`;
const regex = /abc/;
//const regex = new RegExp('abc');
console.log(regex.test(myString));

//test(): regular expression과 특정 문자열이 일치하는지 확인하여 boolean형식으로 반환하는 method

위의 예시를 실행해보면 결과로 'true'를 반환한다. 왜냐하면 우리가 찾으려는 'abc'라는 패턴이 'myString'이라는 변수에 존재하기 때문이다. 다음 예도 함께 살펴보자.

const myString1 = `Hi, do you know your abc's`;
const regex1 = /ac/;
//const regex1 = new RegExp('ac');
console.log(regex1.test(myString1));//false

위의 예는 'false'를 반환한다. 왜냐하면 'ac'와 정확하게 일치하는 문자열이 존재하지 않기 때문이다.


Literal Characters

알파벳이나 숫자의 경우 regular expression과 맞아떨어진다. 하지만 때때로 알파벳이 아닌 문자의 일치 여부를 확인해야 하는데 이 같은 기능도 JavaScript에서 지원한다. 아래 표를 확인하자.
문법과 자료형 MDN_Link

CharacterMatches
\tTab
\nNewline
\vForm Feed
\fCarriage Return
\xmmLatin character; example \x0A is same as \n
\()The Null Character

Flags/modifiers in Regular Expressions

FlagsDescription
standard
gglobal
icase-insensitive matching
mthis performs multiline matching
uUnicode
ysticky
  • standard
    standard flag/modifier는 어떤 flag도 붙지 않은 regular expression을 뜻한다.
    ex) /abc/
  • g MDN_link
    global mode를 뜻한다. 처음 매칭하는 문자열에서 멈추지 않고 나머지 문서 혹은 문자열을 전부 확인하여 일치 여부를 알아낸다.
    ex) /abc/g
  • i MDN_link
    이 flag는 형식에 구애받지 않고(case-insensitive) 문자열 일치 여부를 확인한다. 예를 들어 /Abc/i라고 입력한다면 대소문자 상관 없이 'abc'인 값을 찾는다.
    ex) /abc/i
  • m MDN_link
    만약 여러 줄인 텍스트를 찾는다면 m을 쓰는 것이 좋다.
  • u MDN_link
    ES6에서 도입된 flag인데 문자열 내 Unicode문자를 인식한다.
  • y MDN_link
    y flag는 지정된 lastIndex에서 뒤에 있는 문자열만을 탐색하여 일치 여부를 boolean값으로 반환한다.


    다음 예제들을 통해 이 flag들이 어떻게 쓰이는지 알아보자.
const btn = document.querySelector('#clickBtn');
const pTag = document.querySelector('.result');
const myString = `Regular expressions are patterns used to match character combinations in strings.
In JavaScript, regular expressions are also objects`;
const regPattern = /ions/;
btn.addEventListener('click',(e)=>{
	const result = myString.match(regPattern);
	pTag.innerHTML = result;
});

button을 누르면 아래와 같은 결과가 반환된다.

위 예제의 myString에서 regular expression으로 준 'ions'가 3군데(expressions, combinations, expressions)에 존재함에도 결과값은 ions 단 하나만 반환되는 것일까? 아무런 flag를 쓰지 않고서는 처음 일치하는 값만을 반환한다. 이 regular expression에 g flag를 붙이면 어떻게 될까?

const regPattern = /ions/g;//global

전체 영역을 탐색하면서 결과값은 'ions, ions, ions'가 된다.
그렇다면 시작값과 끝값은 같으나 중간값이 다른 문자열의 일치 여부는 어떻게 확인할까?

const myString = `Mam, Mom, Mum`;
const regPattern = /M/g;//global

만약 이렇게 g flag만을 주게 되면 결과값은 'M, M, M'이 된다. 단순히 M이 아닌 단어 전체를 반환하려면 어떻게 해야 할까? regular expression을 다음과 같이 쓰면 된다.

const regPattern = /M.m/g;//global

마침표.는 위의 예에서 글자 가운데의 문자 하나를 대체한다. 따라서 아래 예와 같이 중간값의 길이가 길어진다면 작동하지 않는다. 왜냐하면 마침표는 공백을 제외하고 단 하나의 문자만을 대체하기 때문이다.

const myString = `Mam, Mom, Mum, Magm`;

그렇다면 소수점이 포함된 문자열 탐색은 어떻게 할까? 아래 예를 살펴보자.

const myString = `5.00, 510, 570`;
const regPattern = /5.0/g;

위 예제는 '5.0, 510, 570'을 반환한다. 여기서 '5.0'만을 반환하려면 어떻게 해야 할까? 바로 아래 예처럼 regular expression에 백슬래시\.앞에 넣어준다.

const regPattern = /5\.0/g;

이렇게 되면 \바로 뒤의 문자는 무시가 되고 결과적으로 '5'와 '0'사이의 문자를 .으로 대체한 값을 탐색하는 것이 아니라 '5.0' 을 탐색하게 되는 것이다.
참고로 quoatation mark는 regular character 취급을 하기 때문에 탐색 시 \를 덧붙여 쓰지 않아도 된다.


RegExp Class

앞서 말했다시피 regular expression을 구현하는 데는 2가지 방식이 있다. 이때까지는 regular expression literal 방식을 활용하여 작성했는데 RegExp object의 constructor function을 활용해서는 어떻게 구현하는지 살펴보자.

const regExpression = new RegExp(‘ab+c’,’i’);

첫 번째 argument는 regular expression liter이고 두 번째는 flag인데 선택적 입력값이기 때문에 필요에 따라 적절한 flag값을 입력하면 된다. 또한 text(), exec() 두 가지 method도 사용 가능하다. 자세한 설명은 chapter1 후반부에 다루도록 하겠다.
test(): MDN_link
exec(): MDN_link


Character Classes together with brackets in Regular Expressions

다음 두 regular expression의 차이는 무엇일까?
/abc/
/[abc]/
첫 번째는 연속된 'abc'를 탐색하는 데 쓰이고, 두 번째는 []로 감싸줌으로써 각각의 문자를 classes로 통합하여 대괄호 내의 'a', 'b', 'c' 어떤 값이든 일치 여부를 탐색 가능하게 한다. 이와 반대로 대괄호 안의 값을 제외한 모든 값을 탐색하고 싶을 때는 [^abc]와 같이 ^를 맨앞에다 덧붙이면 된다.

자주 쓰이는 표현들을 살펴보자
[0-9]: 0부터 9까지 숫자 탐색
[a-z]: a부터 z까지 소문자 알파벳 탐색
[A-Z]: A부터 Z까지 대문자 알파벳 탐색

위의 표현들은 조합하여 사용 가능하다. 예를 들어 모든 소문자, 대문자를 탐색하고 싶을 때는 [a-zA-Z]와 같이 쓰고, 모든 알파벳, 숫자를 탐색하고 싶다면 [a-zA-Z0-9]와 같이 쓰면 된다.

아래 예를 살펴보면서 개념을 정리해보자.

const myString = `Regular expressions are patterns used to match character combinations in strings.
In JavaScript, regular expressions are also objects`;
const regPattern = /[JaS]/g;//global

//expected Output: a,a,a,a,a,a,a,J,a,a,S,a,a,a

[]안의 'J', 'a', 'S'에 해당하는 값을 global mode로 탐색해서 모두 반환하는 것이다. 반대의 예도 살펴보자.

const regPattern = /[^JaS]/g;//global
/* expected output
R,e,g,u,l,r, ,e,x,p,r,e,s,s,i,o,n,s, ,r,e, ,p,t,t,e,r,n,s, ,u,s,e,d, ,t,o, ,m,t,c,h, ,c,h,r,c,t,e,r, , , , , , , , ,
,c,o,m,b,i,n,t,i,o,n,s, ,i,n, ,s,t,r,i,n,g,s,., ,I,n, ,v,c,r,i,p,t,,, ,r,e,g,u,l,r, ,e,x,p,r,e,s,s,i,o,n,s, ,r,e, ,l,s,o,
,o,b,j,e,c,t,s */

[]안의 'J', 'a', 'S'를 제외한 모든 값을 반환하는 것을 볼 수 있다. 이는 숫자에서도 똑같이 적용된다.


Regular Expression character classes

아래 표는 대표적인 metacharacter 모음이다.

metachractersdescription
\tIt matches the tab character
\vIt matches the vertical tab character
\rIt matches the carriage return character
\fIt matches the form feed character
\bIt finds a match at the beginning or the end of a word
\BIt tries to find a match but not at the beginning or the end of a word
\sIt matches a whitespace character
\SIt matches a non-whitespace character
\dIt matches any digit character, any ASCII digit, or same as [0-9]
\DIt matches a non-digit character
\wTries to find any ASCII word character, same as if we wrote [azA-Z0-9_]
\WTries to find a non-word character or same as [^a-zA-Z0-9_]
[...]Matches any character between the brackets
[^...]Matches any character that is not in between the brackets
\uhhhMatches a UTF-16 value WITH four hexadecimal digits.

아래 예제를 통해 어떻게 쓰이는지 알아보자.

const myString = `Regular expressions are patterns used to match character combinations in strings
100% true. In JavaScript, regular expressions are also objects!`;
const regPattern = /\w/g;//global

/* expected output
R,e,g,u,l,a,r,e,x,p,r,e,s,s,i,o,n,s,a,r,e,p,a,t,t,e,r,n,s,u,s,e,d,t,o,m,a,t,c,h,c,h,a,r,a,c,t,e,r,c,o,m,b,i,n,a,t,i,o,n,
s,i,n,s,t,r,i,n,g,s,1,0,0,t,r,u,e,I,n,J,a,v,a,S,c,r,i,p,t,r,e,g,u,l,a,r,e,x,p,r,e,s,s,i,o,n,s,a,r,e,a,l,s,o,o,b,j,e,c,t,s
*/

\w는 모든 문자의 일치 여부만을 확인하기 때문에 %, ! 빼고는 다 반환된다.

const regPattern = /\W/g;//global

/* expected output
, , , , , , , , , , , , , , , , , , ,%, ,., , ,,, , , , , ,!
*/

\W는 문자가 아닌 값 %!만을 반환한다. \d는 어떤 값을 반환할까?

const regPattern = /\d/g;//global

/* expected output
1,0,0
*/

\d는 숫자값만을 찾아 반환한다.

만일 regular expression에 \을 포함시키고 싶다면 어떻게 해야 할까? \\ 이런 식으로 포함할 \앞에 또 다른 \를 덧붙여 쓰면 된다.


Unicode: flag "u" and class \p{...}

ES2018부터 Unicode문자를 다루려면 u flag를 써야 한다. u flag를 쓴다면 character class인 \p{...}\P{...} 역시 지원된다. Unicode 내의 모든 문자는 Unicode standard에 의해 규정되는 properties를 갖는다. 예를 들어 문자는 알파벳에 속하고 숫자라면 Arabic이나 Chinese alphabets에 속한다.

\p{...} class를 쓴다면 반드시 끝에 u flag를 덧붙여줘야 한다. NumberLetter properties는 각각 NL이라는 alias를 갖는다.

다음은 u flag와 p class를 활용하여 regular expression을 구현한 예이다.

let mixedString = "Hi აბ 必 ᄇ ᄉ ";
let regex1 = /\p{L}/gu;
let regex2 = /\p{L}/g;
console.log(regex1.test(mixedString));//true
console.log(regex2.test(mixedString));//false

앞서 설명했다시피 \p{...} class는 u flag와 함께 써야 하므로 regex1은 true, regex2는 false를 반환한다.

만약 10진수(decimal number)를 탐색하는 regular expression을 작성하고 싶다면
/\p{Decimal_Number}/u
위와 같이 작성하면 된다.

중국어, 키릴문자, 그리스어, 아랍어 등과 같은 문자를 탐색할 때 쓰이는 Unicode property는 Script 인데 약어로 sc를 쓴다. 아래 실례를 살펴보자.

let mixedString = "Истражувањето на компанијата";
let regex1 = /\p{sc=Cyrillic}/gu;
console.log(regex1.test(mixedString));//true

또한 $, €, ¥와 같은 화폐기호를 찾을 때 쓰는 Unicode property도 있는데 Currency_Symbol로서 약어는 Sc로 쓴다. 숫자와 함께 쓰인 예를 보자.

let mixedString = `$5, €10, ¥109`;
let regex1 = /\p{Sc}\d/gu;
console.log( regex1.test(mixedString));//true

Quantifiers in Regular Expressions

Quantifier(수량자, 数量詞)는 해당 문자열에서 숫자가 얼마나 반복될 수 있는지 나타낼 때 쓰인다. 앞서 배웠듯 4개의 문자 일치 여부를 탐색하는 regular expression을 /\d\d\d\d/ 로 작성할 수 있다. 허나 몇 번이나 개체가 반복되어야 하는지 알아야 한다면 quantifier 문자를 사용해야 한다.
MDN_link

quantifierdescription
+This means that it will match one or more occurrences of the item
*This means that it will match zero or more occurrences of the item
?This matches zero or one occurrence of the item
{N}It matches the exact N number of occurrences of the specified item
{N,}It matches the N or more number of
{N, M}It matches any string that contains N number of occurrences, but it can be no more than M

몇 가지 예를 보면서 quantifier의 쓰임새를 알아보자. 먼저 + 이다.

const myString = `Regular expressions are awesome!`;
const regPattern = /a+/g;

console.log(myString.match(regPattern));
//expected output: a, a, a

myString 변수에서 'a'가 총 3군데(Regular expressions are awesome!) 쓰였기 때문에 'a'를 3번 반환한다.

다음은 *를 살펴보자.

const regPattern = /a*/g;
//expected output: ,,,,,a,,,,,,,,,,,,,,,a,,,,a,,,,,,,,

output이 조금 이상하게 보일 수 있다. 위의 표에서도 볼 수 있듯 *는 'a'뿐만 아니라 공백도 포함하여 출력한다. 즉 문자열에 존재하는 모든 문자를 탐색하고 일치하면 해당 문자, 일치하지 않는 위치에는 공백을 반환하는 것이다.

*?를 쓸 때 주의해야 할 부분은 일치하는 문자가 하나도 없다면 공백을 반환한다는 점이다. ?*와 쓰임새가 비슷하다. 예를 들어 be, bee, bees 세 가지 전부와 일치하는 regular expression을 짤 때 /be+s?/와 같이 쓴다.

quantifier는 숫자와 함께 쓸 수도 있다. 다음 예를 보자.

const myString = `Regular expressions are awesome!`;
const regPattern = /s{2}/g;

이 예는 awesome!s 와는 일치하지 않지만 expressionsss와는 일치한다. 만약 /s{3} 이라고 쓰면 작동하지 않는다. 왜냐하면 변수에 sss가 쓰인 곳이 한 군데도 없기 때문이다.

quantifier 중 *+greedy 라고 부른다. 이 두 개는 문자열 내에 일치하는 값을 될 수 있는 때까지 찾으려고 한다. 이 quantifier 뒤에 ? 를 붙이면 greedy가 아니게 되고(비탐욕, 非貪欲) 첫 번째로 일치하는 값을 찾자마자 멈춘다.

다음 예를 통해 문자 앞뒤의 공백까지 일치하는지 확인하는 regular expression을 살펴보자.

let longString = `As we know JavaScript is a scripting language that enables you to create
dynamically updating content,
control multimedia, animate images, and pretty much....`;
let regex2 = /\s+JavaScript\s+/g;
console.log(longString.match(regex2));
//expected output:[‘ JavaScript ’]

공백을 탐색하는 \s와 하나 이상의 값을 찾는 +를 조합하여 앞뒤에 위치한 공백까지 반환한다.

이때까지 쓰인 중요한 reserved characters를 정리하자.

  • .
  • +
  • *
  • ?
  • {}
  • |
  • ()
  • []
  • \
  • ^
  • $

이번 글의 소회

알고리즘 문제를 풀다가 몇 번인가 마주쳤던 regular expression에 대해서 깊이 있게 공부할 수 있었다. 글을 적다 모르는 부분이 나오면 이해가 될 때까지 공부하다가, 다시 적다가를 반복하다 보니 이 글을 쓰는 데만 5시간이 걸렸다. 다음부터는 책을 읽으면서 동시에 글을 요약하는 느낌으로 작성해야겠다.
책을 읽으면 인터넷으로 깔짝거리며 자료조사하는 것보단 깊이 있는 공부가 가능해서 좋지만, 다른 공부할 것도 많아서 책 정리에만 시간을 너무 많이 쏟을 수는 없을 것 같다.

profile
front-end 분야를 중점으로 공부 중!🐣

0개의 댓글