Coding/Today I Learned (148)

02
13

1. 문제 설명

 

코딩테스트 연습 - 다음 큰 숫자

자연수 n이 주어졌을 때, n의 다음 큰 숫자는 다음과 같이 정의 합니다. 조건 1. n의 다음 큰 숫자는 n보다 큰 자연수 입니다. 조건 2. n의 다음 큰 숫자와 n은 2진수로 변환했을 때 1의 갯수가 같습니

programmers.co.kr

자연수 n이 주어졌을 때, n의 다음 큰 숫자는 다음과 같이 정의합니다.

  • 조건 1. n의 다음 큰 숫자는 n보다 큰 자연수입니다.
  • 조건 2. n의 다음 큰 숫자와 n은 2진수로 변환했을 때 1의 개수가 같습니다.
  • 조건 3. n의 다음 큰 숫자는 조건 1, 2를 만족하는 수 중 가장 작은 수입니다.

예를 들어서 78(1001110)의 다음 큰 숫자는 83(1010011)입니다.

자연수 n이 매개변수로 주어질 때, n의 다음 큰 숫자를 return 하는 solution 함수를 완성해주세요.

 

2. 제한 사항

  • n은 1,000,000 이하의 자연수입니다.

 

3. 입출력 예제

n result
78 83
15 23

 

4. 나의 접근 방식

  • 일반적인 숫자를 받아 2진수로 바꾸어 1의 개수까지 구해주는 함수를 만든다.
  • 이후, n보다 크다는 조건을 위해 n + 1부터 1씩 증가시켜 주면서 계속 비교해 간다면 모든 조건을 만족시킬 수 있다.
  • 이는 반복적인 조작을 계속 1씩 증가시키면서 하면 되기 때문에 재귀로 만드는 것이 낫다고 생각하였다. 

 

5. 결과

// 재귀함수를 위해 target을 n + 1로 선언, 할당한다.
function solution(n, target = n + 1) {
    // 2진수로 만들어 1의 갯수를 계산할 함수를 만들어 준다.
    const getOneCount = (num) => {
        let countOne = 0;
        let two = num.toString(2);
        for(let i = 0; i < two.length; i++) {
            if(two[i] === "1") countOne++;
        }
        return countOne;
    }

    // 이미 n보다 크다는 조건 1을 만족한 상태이기 때문에 조건2의 기준인 1의 갯수는
    // 위의 함수에서 계산해 주기 때문에 둘의 갯수가 같으면 그대로 반환한다.
    if(getOneCount(n) === getOneCount(target)) return target;
    else {
        // 같지 않다면 1을 증가 시켜서 다시 재귀를 돈다.
        target++;
        return solution(n, target)
    };
}

 

COMMENT
 
02
12

1. 문제 설명

 

코딩테스트 연습 - 올바른 괄호

괄호가 바르게 짝지어졌다는 것은 '(' 문자로 열렸으면 반드시 짝지어서 ')' 문자로 닫혀야 한다는 뜻입니다. 예를 들어 "()()" 또는 "(())()" 는 올바른 괄호입니다. ")()(" 또는 "(()(" 는 올바르지 않은

programmers.co.kr

괄호가 바르게 짝지어졌다는 것은 '(' 문자로 열렸으면 반드시 짝지어서 ')' 문자로 닫혀야 한다는 뜻입니다. 예를 들어

  • "()()" 또는 "(())()" 는 올바른 괄호입니다.
  • ")()(" 또는 "(()(" 는 올바르지 않은 괄호입니다.

'(' 또는 ')' 로만 이루어진 문자열 s가 주어졌을 때, 문자열 s가 올바른 괄호이면 true를 return 하고, 올바르지 않은 괄호이면 false를 return 하는 solution 함수를 완성해 주세요.

 

2. 제한 사항

  • 문자열 s의 길이 : 100,000 이하의 자연수
  • 문자열 s는 '(' 또는 ')' 로만 이루어져 있습니다.

 

3. 입출력 예제

s answer
"()()" true
"(())()" true
")()(" false
"(()(" false

 

3. 나의 접근 방식

  • s를 반으로 나누어서 열린 괄호와 닫침 괄호의 개수가 같다면 true일 것이다!라고 일차원적으로 생각했지만 실패.
  • 효율성이 떨어지며, 닫침 괄호로 시작하는 등의 경우를 생각하지 않았기 떄문에 예외 케이스가 있었다.
실패한 코드
function solution(s){
    // 열림 괄호와 닫침 괄호의 갯수를 계산한다.
    let open = 0;
    let close = 0;
    for(let i = 0; i < s.length; i++) {
        if(s[i] === "(") open++;
        if(s[i] === ")") close++;
    }
    
    if(open === close) return true;
    else return false;
}​
  • 그래서 시작이 닫침 괄호일 때는 절대로 true가 나올 수 없기 때문에 그 경우를 바로 false로 처리한 후에, 배열에 넣어서 스택처럼 열림 괄호가 나오면 배열에 넣고, 다시 닫침 괄호가 나오면 빼는 방식으로 모든 s를 돌아 검사한 후에, 그 스택에 요소가 들어 있으면 닫침과 열림의 수가 맞지 않다는 말이 되므로 false 아니면 true로 답을 낼 수 있었다.

 

4. 결과

function solution(s){
    let arr = [];
    if(s[0] === ")") return false;
    for(let i = 0; i < s.length; i++){
        if(s[i] === "(") arr.push(s[i])
        if(s[i] === ")") arr.pop();
    }
    if(arr.length === 0) return true;
    else return false;
}
COMMENT
 
02
08

진짜 개발자는 코드보다 작명에 더 오랜시간을 사용합니다.

1. 문제의 발단

항상 생각했던 문제였다. 자연스럽게 알고 있는 명명규칙을 사용하고 있었는데, 매번 헷갈렸다. 따로 공부를 하기보다는 다른 사람들이 짠 코드나 레퍼런스 등을 보고 알게 된 것일 것이다. 그래서 이 기회에 확실히 알고 넘어가려고 한다.

 

2. 작명의 중요성

  • 코드 가독성에 가장 중요한 요소가 된다.
  • 예를들어, 변수명을 'a'라고 해놨다. 아무리 const로 재선언, 재할당을 막아도 분명 겹칠 수 있고, 가장 중요한 건 어떤 변수인지 알 수가 없다.

 

3. 작명 규칙

  1. 변수와 함수는 '카멜 케이스'를 사용한다.
    • 각 네이밍 컨벤션을 알아보자. 
      1. 카멜 케이스(camelCase) : 첫번째 문자를 소문자, 띄어쓰기 대신에 대문자를 사용한다.
      2. 파스칼 케이스(PascalCase) : 각 어절마다 대문자를 사용한다.
      3. 스네이크 케이스(snake_case) : 띄어쓰기 대신 '_' (언더바)를 사용한다.
  2. 약어(ex: HTML 등)는 대문자 '스네이크 케이스'를 사용한다.
  3. 생성자 함수는 대문자의 '카멜 케이스'를 사용한다.
    • 생성자 함수 : new Array 처럼 유사한 객체를 여러 개 만들 때 사용할 수 있는 함수. 실행시킬 때는 'new'와 함께 사용한다.
  4. 상수의 이름에서 어절을 띄울때나 Private 변수는 '_' (언더바)로 시작한다.
  5. 이미 선언, 할당이 되어 있는 '예약어'(ex: if, for, const)등을 선언하지 않는다.
  6. 전역 변수 선언인 'var'을 사용하지 않는다.
  7. 선언된 이름이 뜻하는 바가 없는 암묵적인 변수(ex: foo, good, hi 등)를 사용하지 않는다.
  8. 폴더의 이름은 '-' (하이픈)을 이용해 띄어쓰기를 표현하고, 소문자를 사용한다.

 

4. 덤으로 알아보는 CSS 규칙

  1. SASS나 LESS에서는 변수를 선언할 수 있다. 이때, BEM 페턴을 사용하기 좋다.
    • BEM(Block Element Modifier) : 각 요소들의 class를 
      • B -  Block : 가장 큰 컨테이너를 말한다. class 네이밍 규칙은 소문자인 한 단어로 만든다. 그리고 id는 사용하지 않는다. 마지막으로, 다른 block 요소의 자식이 될 수 없다.
      • E - Element : block 요소안의 요소이다. class 네이밍 규칙은 "__" (언더바 두 개)로 띄어쓰기를 구분하며, 블록 이름을 앞에 적고 block__element처럼 사용한다. 
      • M - Modifier : 블럭이나 엘리먼트 요소의 상태를 변경한다. class 네이밍 규칙은 block의 상태를 변경할 때는 "--" (하이픈 두 개), element의 상태를 변경할 때에는 block--element--modifier처럼 사용한다.
  2. 간단한 프로젝트일 경우, '-'을 이용해서 띄여쓰기만 구분하기도 한다.
COMMENT
 
02
07

1. 문제 설명

 

코딩테스트 연습 - [1차] 다트 게임

 

programmers.co.kr

카카오톡에 뜬 네 번째 별! 심심할 땐? 카카오톡 게임별~

(예쁘니까 뺴지 않겠읍니다)

카카오톡 게임별의 하반기 신규 서비스로 다트 게임을 출시하기로 했다. 다트 게임은 다트판에 다트를 세 차례 던져 그 점수의 합계로 실력을 겨루는 게임으로, 모두가 간단히 즐길 수 있다.
갓 입사한 무지는 코딩 실력을 인정받아 게임의 핵심 부분인 점수 계산 로직을 맡게 되었다. 다트 게임의 점수 계산 로직은 아래와 같다.

  1. 다트 게임은 총 3번의 기회로 구성된다.
  2. 각 기회마다 얻을 수 있는 점수는 0점에서 10점까지이다.
  3. 점수와 함께 Single(S), Double(D), Triple(T) 영역이 존재하고 각 영역 당첨 시 점수에서 1제곱, 2제곱, 3제곱 (점수1 , 점수2 , 점수3 )으로 계산된다.
  4. 옵션으로 스타상(*) , 아차상(#)이 존재하며 스타상(*) 당첨 시 해당 점수와 바로 전에 얻은 점수를 각 2배로 만든다. 아차상(#) 당첨 시 해당 점수는 마이너스된다.
  5. 스타상(*)은 첫 번째 기회에서도 나올 수 있다. 이 경우 첫 번째 스타상(*)의 점수만 2배가 된다. (예제 4번 참고)
  6. 스타상(*)의 효과는 다른 스타상(*)의 효과와 중첩될 수 있다. 이 경우 중첩된 스타상(*) 점수는 4배가 된다. (예제 4번 참고)
  7. 스타상(*)의 효과는 아차상(#)의 효과와 중첩될 수 있다. 이 경우 중첩된 아차상(#)의 점수는 -2배가 된다. (예제 5번 참고)
  8. Single(S), Double(D), Triple(T)은 점수마다 하나씩 존재한다.
  9. 스타상(*), 아차상(#)은 점수마다 둘 중 하나만 존재할 수 있으며, 존재하지 않을 수도 있다.

0~10의 정수와 문자 S, D, T, *, #로 구성된 문자열이 입력될 시 총점수를 반환하는 함수를 작성하라.

 

2. 제한 사항

  • 위의 점수 계산 로직 참고

 

3. 입출력 예제

예제 dartResult answer 설명
1 1S2D*3T 37 11 * 2 + 22 * 2 + 33
2 1D2S#10S 9 12 + 21 * (-1) + 101
3 1D2S0T 3 12 + 21 + 03
4 1S*2T*3S 23 11 * 2 * 2 + 23 * 2 + 31
5 1D#2S*3S 5 12 * (-1) * 2 + 21 * 2 + 31
6 1T2D3D# -4 13 + 22 + 32 * (-1)
7 1D2S3T* 59 12 + 21 * 2 + 33 * 2

 

4. 나의 접근 방식

  • 일단, 10을 제외한 모든 점수들은 String이라도 1자리이라는 것을 이용해 숫자로 바꾼 배열을 만들면 되겠다고 생각하였다.
  • splice를 이용해 1과 0이 연달아 나올 때 빼주고, 10을 처리한 새로운 배열을 만들어 주었다.
  • 하지만 '10S10S10S'와 같이 10이 연달아 나오는 경우, index로 처리하려고 하였기 때문에 undefined가 들어간다. 실패!
실패한 코드
function solution(dartResult) {
    // arr에 숫자는 숫자대로 문자는 문자로 나눠 넣어준다.
    let arr = [];
    for(let i = 0; i < dartResult.length; i++) {
        if(dartResult[i] === "0") arr.push(0)
        else if(Number(dartResult[i])) arr.push(Number(dartResult[i]))
        else arr.push(dartResult[i])
    }

    // result에 10을 splice로 1이후 0이 따라 나올때 잘라 합쳐서 넣어준다.
    // 하지만 splice는 첫번째에 1이 나올경우, i - 1을 할 수 가 없으므로 사용할 수 없다.
    let result = [];
    for(let i = 0; i < arr.length; i++) {
        if(arr[i] === 1 && arr[i + 1] === 0) {
            arr.splice(i, 2)
            arr.splice(i - 1, 1, 10)
        }

        if(arr[i] === "S") {
            result.push(arr[i - 1]);
        }

        if(arr[i] === "D") {
            result.push(Math.pow(arr[i - 1], 2));
        }

        if(arr[i] === "T") {
            result.push(Math.pow(arr[i - 1], 3));
        }

        if(arr[i] === "*") {
            result[result.length - 1] *= 2;
            result[result.length - 2] *= 2;
        }

        if(arr[i] === "#") {
            result[result.length - 1] *= -1; 
        }
    }
    return result.reduce((acc, cur) => acc + cur);
}​
  • 그래서, 먼저 10만 모아서 다시 배열에 넣어주고 그 10을 처리한 배열을 따로 문자와 합쳐 계산한 다음에 점수 계산이 끝난 배열을 전부 더해주기로 하였다.

 

5. 결과

function solution(dartResult) {
    // arr를 숫자와 문자를 분리하는 것은 같다.
    let arr = [];
    for(let i = 0; i < dartResult.length; i++) {
        if(dartResult[i] === "0") arr.push(0)
        else if(Number(dartResult[i])) arr.push(Number(dartResult[i]))
        else arr.push(dartResult[i])
    }


    // numArr에 10을 처리한 결과를 넣을 것이다.
    let numArr = [];
    for(let i = 0; i < arr.length; i++) {
        // 만약, 1과 0이 연달아 나온다면,
        if(arr[i] === 1 && arr[i + 1] === 0) {
            // 10을 처리하여 배열에 넣고
            numArr.push(10)
            // 분리해 놓은 배열에 2칸 즉, 1과 0을 제거한다.
            arr.splice(i, 2)
        }
        // 일반적인 한자리 숫자가 나올때는 그냥 넣어준다.
        numArr.push(arr[i])
    }

    // 문자열을 이용해 점수를 계산할 배열을 만든다.
    let resultArr = [];
    for(let i = 0; i < numArr.length; i++) {
        // S가 나오면 S가 나온 한자리 앞의 요소를 더해준다.
        if(numArr[i] === "S") {
            resultArr.push(numArr[i - 1]);
        }
        
        // D가 나오면 D가 나온 한자리 앞의 요소를 제곱하여 더해준다.
        if(numArr[i] === "D") {
            resultArr.push(Math.pow(numArr[i - 1], 2));
        }

        // T가 나오면 T가 나온 한자리 앞의 요소를 3제곱 하여 더해준다.
        if(numArr[i] === "T") {
            resultArr.push(Math.pow(numArr[i - 1], 3));
        }
        
        // *가 나온다면 앞과 앞앞 요소를 제곱하여 준다. 만약, 0이나, 앞앞자리 요소가 없더라도,
        // 0이 들어가기 때문에 상관없다.
        if(numArr[i] === "*") {
            resultArr[resultArr.length - 1] *= 2;
            resultArr[resultArr.length - 2] *= 2;
        }

        // #가 나온다면 한자리 앞의 수를 -1을 곱해 빼준다.
        if(numArr[i] === "#") {
            resultArr[resultArr.length - 1] *= -1; 
        }
    }
    // 문자열과 점수 계산이 끝난 배열을 전부 더해준다.
    return resultArr.reduce((acc, cur) => acc + cur);
}

 

6. 개선점이 있다면?

  • 정규표현식으로 풀 수 도 있다고 한다! 하지만 정규표현식 사용이 서툴다. 조금 더 공부가 필요할 것 같다.
  • 선언된 배열들의 이름이 마음에 안 든다. 확실히 구분이 어렵다.
COMMENT