프로그래머스 옹알이 (1) JS
문제 설명
머쓱이는 태어난 지 6개월 된 조카를 돌보고 있습니다. 조카는 아직 "aya", "ye", "woo", "ma" 네 가지 발음을 최대 한 번씩 사용해 조합한(이어 붙인) 발음밖에 하지 못합니다. 문자열 배열 babbling이 매개변수로 주어질 때, 머쓱이의 조카가 발음할 수 있는 단어의 개수를 return하도록 solution 함수를 완성해주세요.
✅ 내 코드
function solution(babbling) {
return babbling.map(word => word.replaceAll(/aya|ye|woo|ma/gi, '')).filter(a => a ==='').length
}
위의 방법을 생각하고 완성하기 전에 여러가지 방법을 시도했는데 다 안돼서 계속 머리를 쥐어짜다가 갑자기 replace가 생각나서 풀었다!!
뿌듯 😄
물론 저 방법을 생각하기전까지 많은 방법을 시도했는데 안돼서 계속 생각 또 생각했다 ㅋㅋ
❌ 시도들
우선 처음에는 각각의 배열을 돌면서 무언가를 해주면서도 또 특정한 값을 포함했는지를 확인해야하기때문에 includes()와 filter()를 생각했다. includes()를 통해서 값을 가지고 있는지를 확인하고 filter를 통해서 거르는거였는데 문제가 aya와 같이 일치하는 값은 인식하지만 ayaaa와 같은 값은 일치한다고 인식하지 못하였다. 또한 includes는 첫번째로 오는 값 하나만을 처리하는 것도 문제였다.
위의 문제를 해결하기 위해서는 ["ayaye", "uuuma", "ye", "yemawoo", "ayaa"]와 같이 배열안에서도 값을 배열처럼 또한번 돌아야한다는 생각이 들었는데 배열안의 배열을 처리하는게 어려웠다.
그래서 계속 생각하다가 생각난 것이 replace였다!!
map을 통해서 각각의 요소들을 돌면서 aya,ye,woo,ma가 있으면 ''빈문자열로 replace하도록 한다.
replaceAll()통해서 처음에 오는 값 뿐만아니라 값이 뒤에 또 오더라도 replace한다. 빈문자열을 넣어줌으로써 사실상 값을 제거하고 인덱스만 남겨두는 역할을 하도록 만든다.
✅ replaceAll()이용해서 푼 코드
우선 map을 돌리면서 replaceAll()만 해주면 값이 아래와 같이 나온다. (딱 이 코드만 실행시킨 값)
function solution(babbling) {
return babbling.map(word => word.replaceAll(/aya|ye|woo|ma/gi, ''))
}
이 방법을 이용함으로써 includes()를 사용하면 첫번째 한번만을 검사하는것의 문제점을 해결할 수 있다.
그뒤에는 filter를 통해서 각각의 요소중에서 ""의 값만을 추출한다. 그러면 배열안에는 ""만 남는다.
function solution(babbling) {
return babbling.map(word => word.replaceAll(/aya|ye|woo|ma/gi, '')).filter(a => a ==='')
}
마지막으로 length로 배열의 길이를 구한다.
function solution(babbling) {
return babbling.map(word => word.replaceAll(/aya|ye|woo|ma/gi, '')).filter(a => a ==='').length
}
이렇게하면 위의 주어진 소리와 정확하게 일치하는 값만을 가지고 있는 것의 수를 셀 수 있다.
(머쓱이의 조카가 발음할 수 있는 단어의 개수)
💻 다른사람 코드중에 배울 것
function solution(babbling) {
var answer = 0;
const regex = /^(aya|ye|woo|ma)+$/;
babbling.forEach(word => {
if (regex.test(word)) answer++;
})
return answer;
}
regex 정규식을 이용해서 푼 코드이다. 전부터 정규식을 푼 코드를 볼때마다 궁금했는데 알고 싶었어서 이번 기회로 공부해보았다.
정규식을 이용하면 여러가지 원하는 작업을 할 수 있는데, 우선은 처음이기때문에 이 문제를 기준으로 공부했다.
정규식이 무엇인지, 이 문제에서 어떻게 쓰였는지를 중점적으로 학습하였다.
RegExp(정규식)이란?
RegExp 생성자는 패턴을 사용해 텍스트를 판별할 때 사용 / 정규식은 문자열에서 특정 문자 조합을 찾기 위한 패턴
공식문서를 보면 정규식에 대한 정의가 위와 같이 되어있다.
쉽게 풀어서 쓰면 정규식은 패턴을 정의하고 이를 이용하여 문자열을 비교하거나 조작하게 해준다. 패턴은 특정문자나 문자열의 조합, 특정패턴의 반복등을 표현할 수 있다. 문자열의 검색, 대체, 추출등 다양한 작업을 할 수 있다.
1. RegExp 객체 생성
정규식은 주로 RegExp 객체를 생성하여 사용한다. RegExp 객체는 리터럴 표기법과 생성자로써 생성할 수 있다.
1️⃣ 리터럴 표기법
const re = /ab+c/
- 리터럴 표기법의 매개변수는 두 빗금으로 감싸야 하며 따옴표를 사용하지 않는다
- 리터럴 표기법은 표현식을 평가할 때 정규 표현식을 컴파일. 정규 표현식이 변하지 않으면 리터럴 표기법을 사용.
- (예를 들어, 반복문 안에서 사용할 정규 표현식을 리터럴 표기법으로 생성하면 정규 표현식을 매번 다시 컴파일하지 않음/성능향상)
2️⃣ 생성자함수
const re = new RegExp('ab+c')
- 생성자 함수의 매개변수는 빗금으로 감싸지 않으나 따옴표를 사용한다.
- 런타임에 컴파일. 패턴이 변할 가능성이 있거나, 사용자 입력과 같이 알 수 없는 외부 소스에서 가져오는 정규 표현식의 경우 사용
- 생성자 함수를 사용할 경우 보통의 문자열 이스케이프 규칙(특수 문자를 문자열에 사용할 때 앞에 역빗금(\)을 붙이는 것)을 준수
let re = /\w+/
let re = new RegExp('\\w+')
2. 정규표현식 패턴 작성하기
1️⃣ 단순패턴사용
/abc/
/abc/ 패턴은 문자열에서 정확한 순서로 "abc"라는 문자의 조합이 나타나는 부분과 일치한다. 중간에 다른값이 들어가거나, 공백이 있으면 안되면 정확하게 값은 값만을 의미한다.
2️⃣ 특수문자사용
하나 이상의 "b"를 찾는다거나 공백 문자를 찾는 등 직접적인 일치 이상의 탐색이 필요할 땐 특수 문자를 사용
/ab*c/
하나의 "a" 이후에 0개 이상의 "b", 그 뒤의 "c""와 일치, "b" 뒤의 *는 "이전 항목의 0번 이상 반복"
( "cbbabbbbcdebc"에 대해 사용하면, 일치하는 부분 문자열은 "abbbbc" )
🔫 정규표현식 특수문자중 자주 사용되는 것들 🔫
- \d: 숫자를 의미. [0-9]와 동일
- \w: 단어 문자를 의미. [a-zA-Z0-9_]와 동일
- \s: 공백 문자를 의미. 공백, 탭, 개행 등의 공백 문자를 포함
- \b: 단어 경계를 의미. 단어 문자와 단어 문자가 아닌 문자 사이의 경계를 나타냄
- \A: 문자열의 시작 부분을 의미
- \Z: 문자열의 끝 부분을 의미
- .: 임의의 한 문자를 의미. 개행 문자를 제외한 모든 문자와 매치
- []: 문자 클래스를 나타내며, 해당 위치에 있는 문자 중 하나와 매치
- +: 앞의 패턴이 하나 이상의 반복을 나타냄
- *: 앞의 패턴이 0개 이상의 반복을 나타냄
- ?: 앞의 패턴이 0개 또는 1개의 반복을 나타냄
2. 정규표현식 메서드 사용
정규식의 메서드는 종류가 많아서 그때그때 필요한 것으로 찾아서 쓰면 될것같다. 아래 mdn사이트를 참고
일단 이렇게 정규식을 공부했다. 그러면 다시 아까의 코드를 보면서 해석을 해봐야겠다.
function solution(babbling) {
var answer = 0;
const regex = /^(aya|ye|woo|ma)+$/;
babbling.forEach(word => {
if (regex.test(word)) answer++;
})
return answer;
}
우선 regex라는 변수에 리터럴 표기법으로 /^(aya|ye|woo|ma)+$/를 할당했다.
패턴이 고정되어있으므로 생성자함수를 쓰는것보다 리터럴표기법을 사용하는 것이 성능상의 이점을 가진다.
(정규식패턴이 변경되지 않는 한 컴파일 단계에서 패턴이 한번만 생성됨)
/^(aya|ye|woo|ma)+$/
- ^는 문자열의 시작을, $는 문자열의 끝을 나타냄
- (aya|ye|woo|ma)는 aya, ye, woo, ma 중 하나와 일치하는 것을 의미
- +는 이전 패턴이 하나 이상 반복되는 것을 의미
- ➡️ 정규식은 aya, ye, woo, ma 로만 이루어진 문자열을 찾는다
babbling.forEach(word => {
if (regex.test(word)) answer++;
})
forEach를 이용하여 요소를 하나하나 돌면서 regex.test(word)를 사용하여 정규식이 word에 일치하는지 확인한다.
.test를 통해서 일치여부를 Boolean값으로 반환한다.
만약 일치하면 answer의 값을 1증가시킨다.
🙋♀️ 내 생각
정규식에서는 시작을 ^, 끝을 $로 나타내는데 위의 문제를 풀때 사용했다. 내가 처음에 이 문제를 starsWith(), endsWith()를 사용해서 풀려고 했었는데 그 접근법과 유사한것 같아서 이런 방향으로 푸는것도 맞는것같다는 생각도 들었다.
이번에 이 문제를 풀면서 정규식을 처음으로 공부했는데 재밌었다. 알면 코드에서 정말 유용하게 쓸수있을것같다. 사용법이 아직 익숙하지는 않지만 알아두면 좋을것같아서 앞으로 문제를 풀때 정규식을 통해서 푸는 방법으로도 연습을 많이 해야겠다.
알고리즘 문제를 풀면서 가장 좋다고 느꼈던것은 문제를 풀면서 문제해결능력이 올라가거나 하는 것도 있지만, 언어와 친숙해지기 때문이다. 내가 코딩공부를 하면서 제일 고민이었던 부분이 치는것이 약하다는 것이었다. '생각하는것을 이렇게 한국어로 글을 적듯이 표현할수있을면 얼마나 좋을까?'라는 생각을 많이 했는데 문제를 풀면서 계속 이런저런 방법들을 치면서 결과값을 보고 고치고 또 다른 방법도 해보고 코드가 어떤점이 문제인지를 공부하고 하면서 언어와 친숙해지고 있다는 느낌이 든다. 왜 언어라고 하는지를 공부할수록 알 것같다.
더 익숙하게 치고 더 재미를 느낄때까지 계속 공부해야지!! 나중에는 이렇게 글을 쓰듯이 자연스럽게 자바스크립트를 치고 싶다 🤘