[프로그래머스] 다트 게임 (Lv.1, 자바스크립트, 정규표현식)

young_pallete·2022년 10월 7일
1

Algorithm

목록 보기
28/32

🚦 본론

다트 게임을 예전에 굉장히 더럽게(?) 풀었던 기억이 있어요.
그런데 이번에 정규표현식을 공부하게 되면서, 연습차원에서 해당 문제를 풀어야겠다!고 문득 생각이 들었습니다. 결과는, 굉장히 선언적이어서 만족스러워요! 🎉

이번에 문제를 푸는데, 굉장히 정규표현식에 대해 이해할 수 있던 계기가 되었던 것 같아요. 🥰

어떻게 풀었는지, 살펴볼까요?

풀이 과정

먼저 문제를 보았을 때 저는 다음과 같이 생각했어요.

  1. 현재 점수들을 어떤 특정 정수로 변환해야겠다. (정수인 이유는, 아차상의 경우 -1을 곱하므로)
  2. 스타상은 결국 현재 * 2 + 이전 값 * 2로 업데이트 하면 된다.

저는 이렇게 따라서 2가지를 주안점을 두어 문제를 접근했습니다.

1. 현재 각 다트 점수들을 정수로 변환한다.

일단 정규표현식으로 다음과 같이 접근했어요.

const regExp = /([0-9]+)(S|D|T)(\#?)/g;

이 뜻을 살펴보자면, 크게 전역 플러그와 3가지 큼직한 패턴이 있는 거에요.
해석하자면,

  1. 전역적으로 살피되 (g)
  2. 처음에는 0 ~ 10의 숫자로([0-9])
  3. 이후 S D T의 문자가 들어오고
  4. 추가로 아차상이 있는 표현식을 뽑아낸다.

라는 거에요!

💡 엇! 그런데 왜 ()으로 감쌌죠?

사실 저것을 굳이 감싸지 않아도 결과 값을 만들어낼 수는 있어요.
그렇지만! 저는 이번에 정규표현식을 공부하면서 알게된 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)
이렇게 하지 않으면, 만약 여러 개의 숫자를 붙였을 때 현재 값을 구분할 수 없겠죠?

3. 스타상은 결국 현재 * 2 + 이전 값 * 2로 업데이트 하면 된다.

그렇다면, 이제 우리는 *을 적용해야 하는데요!
저는 이런 로직을 세웠어요.

  1. *은 현재 점수랑 직전 다트 점수를 각각 2배 해줘야 한다.
  2. 그렇다면, 만약 [특정 정수][S|D|T]*인 경우는 반드시 2배를 해줘야 한다.
  3. 그렇다면 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가 결합된 문자열

3. 결과값 반환

이제 우리는 이 문자열을 다시 결과값으로 바꿔줘야 해요.
이제 여기서 우리가 필요한 것은 무엇일까요? 바로 정수입니다!

따라서 정수를 구하기 위한 정규표현식을 만듭니다.
이때, 우리는 깔끔하게 배열로 받아와서, 나중에 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)

결과

🌈 마치며

정규표현식 정말 재밌네요!
이렇게 정규표현식을 알게 되면 좋은 점이 정~말 많아요.
정규표현식 문법으로 더욱 선언적으로 유효성 검사를 할 수 있기도 하고, 실제 연산의 효율성 역시 무시 못할 정도로 빨라집니다. (선형적으로 패턴을 검색하므로)

다들, 이참에 정규표현식의 매력에 빠지는 것도 좋겠군요!

그럼, 다들 즐거운 공부하시길! 이상 🌈

profile
People are scared of falling to the bottom but born from there. What they've lost is nth. 😉

0개의 댓글