다트 게임을 예전에 굉장히 더럽게(?) 풀었던 기억이 있어요.
그런데 이번에 정규표현식을 공부하게 되면서, 연습차원에서 해당 문제를 풀어야겠다!고 문득 생각이 들었습니다. 결과는, 굉장히 선언적이어서 만족스러워요! 🎉
이번에 문제를 푸는데, 굉장히 정규표현식에 대해 이해할 수 있던 계기가 되었던 것 같아요. 🥰
어떻게 풀었는지, 살펴볼까요?
먼저 문제를 보았을 때 저는 다음과 같이 생각했어요.
현재 * 2
+ 이전 값 * 2
로 업데이트 하면 된다.저는 이렇게 따라서 2가지를 주안점을 두어 문제를 접근했습니다.
일단 정규표현식으로 다음과 같이 접근했어요.
const regExp = /([0-9]+)(S|D|T)(\#?)/g;
이 뜻을 살펴보자면, 크게 전역 플러그와 3가지 큼직한 패턴이 있는 거에요.
해석하자면,
g
)[0-9]
)S
D
T
의 문자가 들어오고라는 거에요!
💡 엇! 그런데 왜
()
으로 감쌌죠?
사실 저것을 굳이 감싸지 않아도 결과 값을 만들어낼 수는 있어요.
그렇지만! 저는 이번에 정규표현식을 공부하면서 알게된 replace
의 2번째 인자를 응용해보고자 블록으로 감쌌습니다.
String.prototype.replace(RegExp, function)
replace
의 2번째 인자로 콜백함수를 전달할 수 있다는 사실! 알고 계셨나요?
이 function
은 가변인자를 받게 됩니다.
이때
Args
는현재 전체 표현식
,패턴 블록 1
,패턴 블록 2
, ...,패턴 블록 n
,offset
,원래 문자열
을 전달하게 돼요!
따라서, 저는 이 인수를 전달 받아 새로운 값으로 치환해주려 합니다.
바로, 다음과 같이 말이죠!
const getInteger = (now, p1, p2, p3) => (p3 ? -1 : 1) * p1 ** ['', 'S', 'D', 'T'].indexOf(p2) + 'S'
const solution = (dartResult) => dartResult
.replace(/([0-9]+)(S|D|T)(\#?)/g, getInteger)
💡 엇, 왜
S
를 붙이셨나요?
S
를 붙인 이유는 저 값이 제곱근으로 1, 즉 다시 연산을 설사 하더라도 자기 자신을 나타내기 때문에 이를 구분자로 적용한 것입니다. (ex:13S === 13 ** 1 === 13
)
이렇게 하지 않으면, 만약 여러 개의 숫자를 붙였을 때 현재 값을 구분할 수 없겠죠?
현재 * 2
+ 이전 값 * 2
로 업데이트 하면 된다.그렇다면, 이제 우리는 *
을 적용해야 하는데요!
저는 이런 로직을 세웠어요.
*
은 현재 점수랑 직전 다트 점수를 각각 2배 해줘야 한다.[특정 정수][S|D|T]*
인 경우는 반드시 2배를 해줘야 한다.replace
를 거쳐 *
을 앞으로 전달해주면서, 자신을 2배해주면, 이전의 값이 다음 replace
메서드에서 2배로 업데이트 되지 않을까?따라서 이를 정규표현식으로 적용했답니다.
const getInteger = (now, p1, p2, p3) => (p3 ? -1 : 1) * p1 ** ['', 'S', 'D', 'T'].indexOf(p2) + 'S'
const passStarForward = (now, p1, p2, p3) => p3 + (p1 * 2) + p2
const solution = (dartResult) => dartResult
.replace(/([0-9]+)(S|D|T)(\#?)/g, getInteger)
.replace(/([-|0-9]+)(S|D|T)(\*)/g, passStarForward)
.replace(/([-|0-9]+)(S|D|T)(\*)/g, passStarForward)
결과적으로 우리는, 다음과 같은 문자열을 얻을 수 있겠네요!
(([\*?]+)([-|0-9]+)(S))+
1. 앞으로 별이 옮겨져 있고(최대 2개), 2. 정수, 3. S가 결합된 문자열
이제 우리는 이 문자열을 다시 결과값으로 바꿔줘야 해요.
이제 여기서 우리가 필요한 것은 무엇일까요? 바로 정수입니다!
따라서 정수를 구하기 위한 정규표현식을 만듭니다.
이때, 우리는 깔끔하게 배열로 받아와서, 나중에 reduce
로 누산해주면 편하겠죠?
따라서 저는 String.prototype.match(RegExp)
메서드를 사용할 거에요.
const solution = (dartResult) => dartResult
.replace(/([0-9]+)(S|D|T)(\#?)/g, getInteger)
.replace(/([-|0-9]+)(S|D|T)(\*)/g, passStarForward)
.replace(/([-|0-9]+)(S|D|T)(\*)/g, passStarForward)
.match(/[-|0-9]+/g)
이제 우리는 number[]
인 배열을 구했네요. 이제 누산을 해줍시다.
(전체 코드 참조)
const getInteger = (now, p1, p2, p3) => (p3 ? -1 : 1) * p1 ** ['', 'S', 'D', 'T'].indexOf(p2) + 'S'
const passStarForward = (now, p1, p2, p3) => p3 + (p1 * 2) + p2
const solution = (dartResult) => dartResult
.replace(/([0-9]+)(S|D|T)(\#?)/g, getInteger)
.replace(/([-|0-9]+)(S|D|T)(\*)/g, passStarForward)
.replace(/([-|0-9]+)(S|D|T)(\*)/g, passStarForward)
.match(/[-|0-9]+/g).reduce((acc, cur) => acc + Number(cur), 0)
정규표현식 정말 재밌네요!
이렇게 정규표현식을 알게 되면 좋은 점이 정~말 많아요.
정규표현식 문법으로 더욱 선언적으로 유효성 검사를 할 수 있기도 하고, 실제 연산의 효율성 역시 무시 못할 정도로 빨라집니다. (선형적으로 패턴을 검색하므로)
다들, 이참에 정규표현식의 매력에 빠지는 것도 좋겠군요!
그럼, 다들 즐거운 공부하시길! 이상 🌈