문제:
수포자는 수학을 포기한 사람의 준말입니다. 수포자 삼인방은 모의고사에 수학 문제를 전부 찍으려 합니다.
수포자는 1번 문제부터 마지막 문제까지 다음과 같이 찍습니다.
  - 1번 수포자가 찍는 방식: 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, ...
  - 2번 수포자가 찍는 방식: 2, 1, 2, 3, 2, 4, 2, 5, 2, 1, 2, 3, 2, 4, 2, 5, ...
  - 3번 수포자가 찍는 방식: 3, 3, 1, 1, 2, 2, 4, 4, 5, 5, 3, 3, 1, 1, 2, 2, 4, 4, 5, 5, ...
1번 문제부터 마지막 문제까지의 정답이 순서대로 들은 배열 answers가 주어졌을 때,
가장 많은 문제를 맞힌 사람이 누구인지 배열에 담아 return 하도록 solution 함수를 작성해주세요.

제한 조건
  - 시험은 최대 10,000 문제로 구성되어있습니다.
  - 문제의 정답은 1, 2, 3, 4, 5중 하나입니다.
  - 가장 높은 점수를 받은 사람이 여럿일 경우, return하는 값을 오름차순 정렬해주세요.

 

문제 풀어보기: https://school.programmers.co.kr/learn/courses/30/lessons/42840

 

풀이보기
더보기
function score(userAnswers, answers) {
  let count = 0;

  for (let i = 0; i < answers.length; i++) {
    let index = i % userAnswers.length;
    if (answers[i] === userAnswers[index]) count++;
  }

  return count;
}

function solution(answers) {
  let student1 = [1, 2, 3, 4, 5];
  let student2 = [2, 1, 2, 3, 2, 4, 2, 5];
  let student3 = [3, 3, 1, 1, 2, 2, 4, 4, 5, 5];

  let corretCounts = [];

  corretCounts.push(score(student1, answers));
  corretCounts.push(score(student2, answers));
  corretCounts.push(score(student3, answers));

  let maxCount = Math.max(...corretCounts);
  let topScorers = [];

  for (let i = 0; i < corretCounts.length; i++) {
    if (maxCount === corretCounts[i]) topScorers.push(i + 1);
  }

  return topScorers;
}

 처음에는 반복되는 코드인 맞은 문제 수를 리턴해주는 score 함수를 따로 만들어서 풀었다.

학생마다 찍는 방식을 미리 선언해주고 score 함수를 통해 얻은 count를 correctCounts에 저장해준다.

그 후 correctCounts에서의 최댓값을 구해서 maxCount에 넣어주고,

문제를 많이 맞춘 학생의 번호를 topScores에 넣어줄 것이다.

그리고 maxCount와 correctCounts[i]가 같다면 topScore에 i + 1을 푸쉬하고,

마지막으로 topScores를 리턴해주면 된다.

 

그리고 다른 사람 풀이를 보다가 생각도 못했던 filter를 쓰는 방법도 있어서 filter로도 풀어봤다.

function solution(answers) {
    let student1 = [1, 2, 3, 4, 5];
    let student2 = [2, 1, 2, 3, 2, 4, 2, 5];
    let student3 = [3, 3, 1, 1, 2, 2, 4, 4, 5, 5];
    
    let correctCount1 = answers.filter((answer, i) => answer === student1[i % student1.length]).length;
    let correctCount2 = answers.filter((answer, i) => answer === student2[i % student2.length]).length;
    let correctCount3 = answers.filter((answer, i) => answer === student3[i % student3.length]).length;
    
    let maxCount = Math.max(correctCount1, correctCount2, correctCount3);
    let result = [];
    
    if(maxCount === correctCount1) result.push(1);
    if(maxCount === correctCount2) result.push(2);
    if(maxCount === correctCount3) result.push(3);
    
    return result;
}

이 문제는 다양하게 풀이할 수 있어서 좋은 것 같다!

 

문제:
한국중학교에 다니는 학생들은 각자 정수 번호를 갖고 있습니다.
이 학교 학생 3명의 정수 번호를 더했을 때 0이 되면 3명의 학생은 삼총사라고 합니다.
예를 들어, 5명의 학생이 있고, 각각의 정수 번호가 순서대로 -2, 3, 0, 2, -5일 때,
첫 번째, 세 번째, 네 번째 학생의 정수 번호를 더하면 0이므로 세 학생은 삼총사입니다.
또한, 두 번째, 네 번째, 다섯 번째 학생의 정수 번호를 더해도 0이므로 세 학생도 삼총사입니다.
따라서 이 경우 한국중학교에서는 두 가지 방법으로 삼총사를 만들 수 있습니다.
한국중학교 학생들의 번호를 나타내는 정수 배열 number가 매개변수로 주어질 때,
학생들 중 삼총사를 만들 수 있는 방법의 수를 return 하도록 solution 함수를 완성하세요.

 

문제 풀어보기: https://school.programmers.co.kr/learn/courses/30/lessons/131705

 

풀이보기
더보기
function solution(number) {
    let result = [];
    
    for(let i = 0; i < number.length - 2; i++) {
        for(let j = i + 1; j < number.length - 1; j++) {
            for(let k = j + 1; k < number.length; k++) {
                if(number[i] + number[j] + number[k] === 0) {
                    result.push([i, j, k]);
                }
            }
        }
    }
    
    return result.length;
}

3중 for문을 이용하여 문제를 쉽게 풀 수 있다.

i는 0부터 number.length - 2까지, j는 i + 1부터 number.length - 1까지, k는 j + 1부터 number.length까지 해주면

중복되는 구간없이 전체적으로 경우의 수를 따질 수 있다.

따라서 각 number[i  / j / k]가 같다면 해당 값을 result에 푸쉬해주면 된다.

마지막으로 result의 길이를 반환해주면 끝!

 

 

최근 기존 프로젝트를 리팩토링하면서 느낀 점이 있었다.

지금은 혼자하기 때문에 PR 단위를 크게 인식하지 않았는데 나중을 대비하여 미리 습관을 들여놓으면 좋을 것 같다는 생각을 했다.

왜냐하면 지금 올리는 PR이 최근에는 file change가 40개가 넘어가는 그런 것도 있었기에 .. (머쓱)

 

그렇다면 좋은 PR이란 무엇일까?


좋은 PR이란? 

 

 

    1. 작고 명확한 목적을 가진다

        하나의 PR은 하나의 목적(기능 추가, 버그 수정, 리팩터링 등)을 가져야 한다.

        변경 범위가 넓으면 리뷰하기 어렵고, 롤백도 힘들어지기 때문!

    2. 의미 있는 커밋 메시지

         커밋 메시지는 무엇을 왜 바꿨는지를 간단히 설명해야 한다. (예: feat: 로그인 실패 시 에러 메시지 추가)

 

    3. 충분한 설명

         PR 설명란에는 변경 이유, 구현 방식, 참고 이슈 등을 적는다.

         복잡한 경우 스크린샷이나 GIF 첨부하는 것도 좋다.

   

    4. 자동화 통과 확인

         GitHub Actions나 CI에서 lint, test 등이 통과된 PR이 바람직하다.

 

    5. 리뷰어 입장을 고려

         리뷰어가 빠르게 이해할 수 있도록 변수명, 함수명, 구조 등을 신경써야한다.


PR의 단위

 

    1. 단일 책임 원칙

         하나의 PR하나의 "일"만 하도록 구성한다. (예: 로그인 기능을 추가하는 PR은 UI 리팩터링과는 분리해야 함.)

 

    2. 리뷰 가능한 크기

         너무 많은 파일/라인을 바꾸지 말 것, 일반적으로 300줄 이하의 변경이 가장 이상적이라고 한다. (물론 프로젝트에 따라 다름)

 

    3. 점진적인 변경

         큰 기능은 작은 PR로 나눠서 순차적으로 머지하는 것이 좋다.

 

    4. 기능별 혹은 작업 단위별 브랜치 관리

         예: feature/login, fix/signup-bug, refactor/header-ui

 

문제:
네오는 평소 프로도가 비상금을 숨겨놓는 장소를 알려줄 비밀지도를 손에 넣었다.
그런데 이 비밀지도는 숫자로 암호화되어 있어 위치를 확인하기 위해서는 암호를 해독해야 한다.
다행히 지도 암호를 해독할 방법을 적어놓은 메모도 함께 발견했다.

  1. 지도는 한 변의 길이가 n인 정사각형 배열 형태로, 각 칸은 "공백"(" ") 또는 "벽"("#") 두 종류로 이루어져 있다.
  2. 전체 지도는 두 장의 지도를 겹쳐서 얻을 수 있다. 각각 "지도 1"과 "지도 2"라고 하자.
      지도 1 또는 지도 2 중 어느 하나라도 벽인 부분은 전체 지도에서도 벽이다.
      지도 1과 지도 2에서 모두 공백인 부분은 전체 지도에서도 공백이다.
  3. "지도 1"과 "지도 2"는 각각 정수 배열로 암호화되어 있다.
  4. 암호화된 배열은 지도의 각 가로줄에서 공백 부분을 0, 벽 부분을 1로 부호화했을 때 얻은 이진수에 해당하는 값의 배열이다.

네오가 프로도의 비상금을 손에 넣을 수 있도록, 비밀지도의 암호를 해독하는 작업을 도와줄 프로그램을 작성하라.

 

문제 풀어보기: https://school.programmers.co.kr/learn/courses/30/lessons/17681

 

풀이보기
더보기
function solution(n, arr1, arr2) {
    let map1 = [];
    let map2 = [];
    let allMap = [];
    
    for(let i = 0; i < arr1.length; i++) {
        map1.push(arr1[i].toString(2));
        map2.push(arr2[i].toString(2));
        
        map1[i] = '0'.repeat(n - map1[i].length) + map1[i];
        map2[i] = '0'.repeat(n - map2[i].length) + map2[i];
        
        map1[i] = map1[i].replace(/0/g, ' ').replace(/1/g, '#');
        map2[i] = map2[i].replace(/0/g, ' ').replace(/1/g, '#');
    }
    
    for(let i = 0; i < map1.length; i++) {
        allMap.push([]);
        
        for(let j = 0; j < map1[i].length; j++) {
            if(map1[i][j] === ' ' && map2[i][j] === ' ') {
                allMap[i].push(' ');
            } else {
                allMap[i].push('#');
            }
        }
        
        allMap[i] = allMap[i].join('');
    }
    
    return allMap;
}

ㅎㅎ.. 처음에는 단순히 문제 해결에만 집중해서 풀었더니 이런 코드가 나왔다.

 

문제 그대로 먼저 arr을 각각 2진수로 바꾼후 map에 넣어주고, 

그 다음 n에 맞게 길이를 0으로 채워준 뒤 0 ,1을 각각 공백과 #으로 바꿔주었다.

그리고 각 map을 기준으로 하나씩 비교하며 둘 다 공백일 때만 공백을 넣어주고 그 외에는 #을 넣어주었다.

마지막으로 allMap[i]를 문자열로 바꿔주고 allMap을 리턴해주면 끝이다.

 

물론 정답이긴 하지만 굉장히 반복되는 코드도 많고 마음에 들지 않았다.

근데 도저히 어떻게 풀지 생각이 안나서 여러 메서드를 찾아보았다.

그러던 중에 알게 된 padStart가 repeat 대신 쓸 수 있는 간단한 메서드 였다.

padStart는 padStart(원하는 문자열 길이, 채울 문자열) 이렇게 사용하며 간단하다.

그럼 코드가 아래와 같이 바뀌게 된다.

// 변경 전
for(let i = 0; i < arr1.length; i++) {
        map1.push(arr1[i].toString(2));
        map2.push(arr2[i].toString(2));
        
        map1[i] = '0'.repeat(n - map1[i].length) + map1[i];
        map2[i] = '0'.repeat(n - map2[i].length) + map2[i];
        
        map1[i] = map1[i].replace(/0/g, ' ').replace(/1/g, '#');
        map2[i] = map2[i].replace(/0/g, ' ').replace(/1/g, '#');
    }

// 변경 후
for(let i = 0; i < arr1.length; i++) {
        map1.push(arr1[i].toString(2).padStart(n, '0'));
        map2.push(arr2[i].toString(2).padStart(n, '0'));
        
        map1[i] = map1[i].replace(/0/g, ' ').replace(/1/g, '#');
        map2[i] = map2[i].replace(/0/g, ' ').replace(/1/g, '#');
    }

 

간결해지긴 했지만 지피티의 도움을 얻어 더 효율적인 코드를 얻을 수 있었다 ㅎㅎ .. 

function solution(n, arr1, arr2) {
    let allMap = [];
    
    for(let i = 0; i < n; i++) {
        let binary = (arr1[i] | arr2[i]).toString(2).padStart(n, '0');
        let row = binary.replace(/1/g, '#').replace(/0/g, ' ');
        allMap.push(row);
    }
    
    return allMap;
}

비교도 안되게 짧아진 코드 ㅎㅎ .. 

그 이유는 (arr1[i] | arr2[i])를 통해서 더 간결해진건데 두 숫자의 비트 연산자를 수행하여 값이 나오게되는 것이다.

(예: 9 | 14 → 1001 | 1110 = 1111 → 10진수로 15)

그렇기 때문에 이전과 같이 map1, map2를 따로 선언해서 계산한 뒤 푸쉬해줄 필요가 없는 것이다.

먼저 두 arr을 연산해주기 때문에 마지막에 이 값을 #과 공백으로 바꿔준 뒤 allMap에 푸쉬하면 끝이다!

 

 

하루에 한 문제씩은 꼭 코딩 테스트를 풀고 있는 요즘 .. 메서드의 중요성을 크게 깨닫고 있다 ㅎㅎ ..

그래서 준비해본 자바스크립트에서의 다양한 메서드 알아보기 ! 🧐

 

문자열 관련 메서드

 

1. chatAt(index): 문자열 특정 인덱스의 문자 반환

const str = "Hello";
console.log(str.charAt(1));  // "e"

 

2. split(separator): 문자열을 특정 구분자로 나누어 배열로 반환

const str = "apple,banana,orange";
str.split(",");  // ["apple", "banana", "orange"]

 

3. replace(searchValue, newValue): 문자열에서 특정 패턴을 찾아 다른 값으로 교체

const str = "Hello, world!";
str.replace("world", "everyone");  // "Hello, everyone!"

 

4. toUpperCase(): 문자열을 대문자로 변환

  + toLowerCase(): 문자열을 소문자로 변환

const str = "Hello";
str.toUpperCase();  // "HELLO"
str.toLowerCase();  // "hello"

 

5. trim(): 문자열 양옆의 공백 제거

const str = "   Hello, World!   ";
str.trim();  // "Hello, World!"

 

6. repeat(count): 문자열을 지정한 횟수만큼 반복

const str = "abc";
str.repeat(3);  // "abcabcabc"

배열 메서드

 

1. push(element): 배열의 끝에 요소 추가

const arr = [1, 2, 3];
arr.push(4);  // [1, 2, 3, 4]

 

2. pop(): 배열의 마지막 요소 제거

const arr = [1, 2, 3];
arr.pop();  // [1, 2]

 

3. unshift(element): 배열의 첫 번째에 요소 추가

const arr = [1, 2, 3];
arr.unshift(0);  // [0, 1, 2, 3]

 

4. shift(): 배열의 첫 번째 요소 제거

const arr = [1, 2, 3];
arr.shift();  // [2, 3]

 

5. join(separator): 배열의 모든 요소를 연결하여 하나의 문자열로 반환

const array = [1, 2, 3];
array.join("");  // "123"

 

6. forEach(callback): 각 요소에 대해 콜백 함수 실행

// 배열
const arr = [1, 2, 3];
arr.forEach(num => console.log(num));

 

7. map(callback): 배열의 각 요소에 함수를 적용하여 새로운 배열 반환

const arr = [1, 2, 3];
arr.map(num => num * 2);  // [2, 4, 6]

 

8. filter(callback): 배열에서 조건을 만족하는 요소만 필터링하여 새로운 배열 반환

const arr = [1, 2, 3, 4, 5];
arr.filter(num => num % 2 === 0);  // [2, 4]

 

9. reduce(callback, initialValue): 배열을 하나의 값으로 축소

const arr = [1, 2, 3, 4];
arr.reduce((acc, num) => acc + num, 0);  // 10

 

10. sort(compareFunction): 배열을 정렬

const arr = [3, 1, 2];
arr.sort((a, b) => a - b);  // [1, 2, 3]

문자열과 배열 모두 사용할 수 있는 메서드

 

1. includes(searchValue): 특정 값이 포함되어 있는지 확인

const str = "Hello, world!";
const array = [1, 2, 3];

console.log(str.includes("world"));  // true
console.log(str.includes("Hi"));     // false

console.log(array.includes(2));      // true
console.log(array.includes(5));      // false

 

2. indexOf(searchValue):  특정 값의 첫 번째 위치를 반환

  + lastIndexOf(searchValue): 특정 값의 마지막 위치를 반환

const str = "Hello, world!";
const array = [1, 2, 1, 3];

str.indexOf("o");      // 4
str.lastIndexOf("l");  // 10

array.indexOf(1);      // 0
array.indexOf(1);      // 2

 

3. slice(startIndex, endIndex): 특정 부분을 잘라내어 반환

     * endIndex는 포함되지 않음.

const str = "Hello, world!";
const array = [1, 2, 3, 4];

str.slice(0, 5);  // "Hello"
array.slice(0, 2);  // [1, 2]

 

4. concat(value1, value2, ...): 여러 문자열이나 배열 결합

const str1 = "Hello";
const str2 = "World";
str1.concat(" ", str2);  // "Hello World"

const arr1 = [1, 2];
const arr2 = [3, 4];
arr1.concat(arr2)  // [1, 2, 3, 4]

문자열 메서드 의미 배열 메서드 의미 배열 메서드 의미
chatAt() 특정 인덱스의 문자 반환 push() 마지막 요소 추가 forEach() 각 요소에 대해 콜백 함수 실행
split() 특정 구분자로 나누어 배열로 반환 pop() 마지막 요소 제거 map() 각 요소에 함수를 적용하여
새로운 배열 반환
replace() 특정 값을 다른 값으로 교체 unshift() 첫 번째 요소 추가 filter() 조건을 만족하는 요소만
필터링하여 새로운 배열 반환
toUpperCase()
toLowerCase()
대문자로 변환
문자로 변환
shift() 첫 번째 요소 제거 reduce()  배열을 하나의 값으로 축소
trim() 양옆의 공백 제거 join() 모든 요소를 연결하여 하나의 문자열로 반환 sort() 배열 정렬
repeat() 지정한 횟수만큼 반복  
문자열 + 배열 공통 메서드 의미
includes() 특정 값이 포함되어 있는지 확인
indexOf()
lastIndexOf()
특정 값의 첫 번째 위치를 반환
특정 값의 마지막 위치를 반환
slice(startIndex, endIndex) 특정 부분을 잘라내어 반환
concat(value1, value2, ...) 여러 문자열이나 배열 결합

객체 관련 메서드

 

1. Object.keys(): 객체의 key를 배열로 반환

const obj = { a: 1, b: 2, c: 3 };
const keys = Object.keys(obj);
console.log(keys);  // ["a", "b", "c"]

 

2. Object.values(): 객체의 value를 배열로 반환

const obj = { a: 1, b: 2, c: 3 };
const values = Object.values(obj);
console.log(values);  // [1, 2, 3]

 

3. Object.entries(): 객체의 key-value 쌍을 배열로 반환

const obj = { a: 1, b: 2, c: 3 };
const entries = Object.entries(obj);
console.log(entries);  // [["a", 1], ["b", 2], ["c", 3]]

 

4. Object.assign(): 객체를 다른 객체로 복사하거나 병합

const obj1 = { a: 1 };
const obj2 = { b: 2 };
const merged = Object.assign({}, obj1, obj2);
console.log(merged);  // { a: 1, b: 2 }

 

5. Object.freeze(): 객체를 동결하여 수정 불가하게 만들기

const obj = { a: 1 };
Object.freeze(obj);
obj.a = 2;  // 수정 불가
console.log(obj.a);  // 1

 

6. Object.seal(): 객체를 봉인하여 프로퍼티 삭제 불가하게 만들기 (수정은 가능)

const obj = { a: 1 };
Object.seal(obj);
obj.a = 2;  // 수정 가능
delete obj.a;  // 삭제 불가
console.log(obj.a);  // 2

 

7. Object.hasOwnProperty(searchValue): 객체에 특정 속성이 존재하는지 확인

const obj = { a: 1, b: 2 };
console.log(obj.hasOwnProperty("a"));  // true
console.log(obj.hasOwnProperty("c"));  // false

 

8. Object.fromEntries(): 배열이나 Map을 객체로 변환

const arr = [["a", 1], ["b", 2]];
const obj = Object.fromEntries(arr);
console.log(obj);  // { a: 1, b: 2 }

Map 관련 메서드

 

1. set(key, value): key-value 쌍을 Map에 추가

const map = new Map();
map.set("a", 1);
map.set("b", 2);
console.log(map);  // Map(2) { 'a' => 1, 'b' => 2 }

 

2. get(key): Map에서 특정 key 값을 가져옴

const map = new Map([["a", 1], ["b", 2]]);
console.log(map.get("a"));  // 1
console.log(map.get("c"));  // undefined

 

3. has(key): Map에 특정 key가 존재하는지 확인

const map = new Map([["a", 1], ["b", 2]]);
console.log(map.has("a"));  // true
console.log(map.has("c"));  // false

 

4. delete(key): Map에서 특정 key-value 쌍을 삭제

const map = new Map([["a", 1], ["b", 2]]);
map.delete("a");
console.log(map);  // Map(1) { 'b' => 2 }

 

5. clear(): Map의 모든 요소 삭제

const map = new Map([["a", 1], ["b", 2]]);
map.clear();
console.log(map);  // Map(0) {}

 

6. size(): Map의 요소 개수 확인

const map = new Map([["a", 1], ["b", 2]]);
console.log(map.size);  // 2

 

7. keys(): Map의 key들을 iterator 형태로 반환

const map = new Map([["a", 1], ["b", 2]]);
const keys = [...map.keys()];
console.log(keys);  // ["a", "b"]

 

8. values(): Map의 value들을 iterator 형태로 반환

const map = new Map([["a", 1], ["b", 2]]);
const values = [...map.values()];
console.log(values);  // [1, 2]

 

9. entries(): Map의 key-value 쌍들을 iterator 형태로 반환

const map = new Map([["a", 1], ["b", 2]]);
const entries = [...map.entries()];
console.log(entries);  // [["a", 1], ["b", 2]]

객체와 Map에 공통적인 메서드

 

1. forEach(): Map의 각 요소에 대해 콜백 함수 실행

// 객체
const obj = { a: 1, b: 2 };
Object.entries(obj).forEach(([key, value]) => {
  console.log(`${key}: ${value}`);
});

// Map
const map = new Map([["a", 1], ["b", 2]]);
map.forEach((value, key) => {
  console.log(`${key}: ${value}`);
});
[객체와 Map의 차이점 💡]
1) 객체는 주로 문자열을 key로 사용하고, 순서가 보장되지 않음.
    (비록 ES6 이후 객체는 키의 순서를 어느 정도 보장하지만 Map보다는 덜 신뢰할 수 있음)
2) Map임의의 자료형을 key로 사용 가능하고, 항상 순서를 보장하며, 성능이 더 뛰어날 때가 많음.

객체 메서드 의미 Map 메서드 의미
Object.keys() key를 배열로 반환 set(key, value) key-value 쌍을 Map에 추가
Object.values() value를 배열로 반환 get(key) 특정 key 값을 가져옴
Object.entries() key-value 쌍을 배열로 반환 has(key) 특정 key가 존재하는지 확인
Object.assign() 다른 객체로 복사하거나 병합 delete(key) 특정 key-value 쌍을 삭제
Object.freeze() 동결하여 수정 불가하게 만들기 clear() 모든 요소 삭제
Object.seal() 봉인하여 프로퍼티 삭제 불가하게 만들기 (수정은 가능) size() 요소 개수 확인
Object.hasOwnProperty() 특정 속성이 존재하는지 확인 keys() key들을 iterator 형태로 반환
Object.fromEntries() 배열이나 Map을 객체로 변환 values() value들을 iterator 형태로 반환
    entries()

key-value 쌍들을 iterator 형태로 반환
객체 + Map 공통적인 메서드 의미
forEach() Map의 각 요소에 대해 콜백 함수 실행

 

문제:
슈퍼 게임 개발자 오렐리는 큰 고민에 빠졌다.
그녀가 만든 프랜즈 오천성이 대성공을 거뒀지만, 요즘 신규 사용자의 수가 급감한 것이다.
원인은 신규 사용자와 기존 사용자 사이에 스테이지 차이가 너무 큰 것이 문제였다.
이 문제를 어떻게 할까 고민 한 그녀는 동적으로 게임 시간을 늘려서 난이도를 조절하기로 했다.
역시 슈퍼 개발자라 대부분의 로직은 쉽게 구현했지만, 실패율을 구하는 부분에서 위기에 빠지고 말았다.
오렐리를 위해 실패율을 구하는 코드를 완성하라.

실패율은 다음과 같이 정의한다.
  = 스테이지에 도달했으나 아직 클리어하지 못한 플레이어의 수 / 스테이지에 도달한 플레이어 수
전체 스테이지의 개수 N, 게임을 이용하는 사용자가 현재 멈춰있는 스테이지의 번호가 담긴 배열 stages가 매개변수로 주어질 때,
실패율이 높은 스테이지부터 내림차순으로 스테이지의 번호가 담겨있는 배열을 return 하도록 solution 함수를 완성하라.

제한사항
  - 스테이지의 개수 N은 1 이상 500 이하의 자연수이다.
  - stages의 길이는 1 이상 200,000 이하이다.
  - stages에는 1 이상 N + 1 이하의 자연수가 담겨있다.
     : 각 자연수는 사용자가 현재 도전 중인 스테이지의 번호를 나타낸다.
     : 단, N + 1 은 마지막 스테이지(N 번째 스테이지) 까지 클리어 한 사용자를 나타낸다.
  - 만약 실패율이 같은 스테이지가 있다면 작은 번호의 스테이지가 먼저 오도록 하면 된다.
  - 스테이지에 도달한 유저가 없는 경우 해당 스테이지의 실패율은 0 으로 정의한다.

 

문제 풀어보기: https://school.programmers.co.kr/learn/courses/30/lessons/42889

 

풀이보기
더보기
function solution(N, stages) {
  let playersFailedStage = [];
  let playersReachedStage = [];

  for (let i = 1; i <= N; i++) {
    let failCount = 0;
    let stageCount = 0;

    for (let stage of stages) {
      if (i === stage) failCount++;
      if (i <= stage) stageCount++;
    }

    playersFailedStage.push(failCount);
    playersReachedStage.push(stageCount);
  }

  let failureRate = new Map();
  for (let i = 0; i < N; i++) {
    failureRate.set(i + 1, playersFailedStage[i] / playersReachedStage[i]);
  }

  const sorted = new Map(
    [...failureRate.entries()].sort((a, b) => b[1] - a[1])
  );
  return [...sorted.keys()];
}

먼저 실패율을 구하기 위해 스테이지를 실패한 사람 수와 스테이지에 도달한 사람들을 구해줬다.

그 후 이 값들을 이용해서 failureRate에 스테이지 번호와 실패율을 넣어주었다.

그리고 값을 기준으로 정렬한 뒤 key들을 반환해주었다.

 

하지만 뭔가 .. 마음에 들지 않았던 코드 .. 

그래서 조금 수정해보았다.

function solution(N, stages) {
    let stageStats = Array(N).fill(0).map(() => ({ fail: 0, reached: 0 }));

    for (let stage of stages) {
        for (let i = 0; i < N; i++) {
            if (stage === i + 1) stageStats[i].fail++;
            if (stage >= i + 1) stageStats[i].reached++;
        }
    }
    
    const failureRate = stageStats.map((stat, index) => ({
        stage: index + 1,
        rate: stat.fail / stat.reached
    }));

    failureRate.sort((a, b) => b.rate - a.rate);

    return failureRate.map(stat => stat.stage);
}

먼저 각각 따로 다른 배열에 저장했었던 실패한 사람과 도달한 사람 수를 합쳐주었다.

그렇다면 stageStats의 형태는 아래와 같다.

[
  { fail: 0, reached: 0},
  { fail: 0, reached: 0},
  { fail: 0, reached: 0},
  ...
]

그 다음 이전 코드와 비슷하게 stages의 stage 번호와 스테이지 1부터 N번 까지의 번호와 같으면 fail++,

stage 번호랑 같거나 작으면 reached를 증가시켜준다.

 

그리고 비슷한 형태로 실패율을 저장해주면 아래와 같이 나온다.

[
  { stage: 1, rate: 0.125 },
  { stage: 2, rate: 0.42857142857142855 },
  { stage: 3, rate: 0.5 },
  { stage: 4, rate: 0.5 },
  { stage: 5, rate: 0 }
]

그럼 이제 이 배열을 실패율을 기준으로 정렬한 뒤 stage만 배열로 반환해주면 된다!

이전 코드보다 조금 더 가독성있고 효율적인 코드를 만들 수 있었다 ㅎ

 

 

들어가기 앞서, React는 컴포넌트 기반의 UI 라이브러리이다.

컴포넌트란, 마치 레고 블록처럼 조립이 가능한 작은 UI 조각들이며,

컴포넌트끼리 정보를 주고받고 화면이 바뀌기도 하는데, 그때 props와 state라는 개념이 사용되는 것이다.


Props에 대해 알아보자!

Props란?

     props는 컴포넌트가 부모 컴포넌트로부터 받는 데이터 또는 설정 값을 말한다.

function Welcome(props) {
  return <h1>안녕, {props.name}!</h1>; // 화면에 "안녕, 길동!" 출력
}

<Welcome name="길동" />

 


Props의 특징
  • 읽기 전용(Read-Only): 컴포넌트는 받은 props를 변경할 수 없음.
  • 부모 → 자식 방향으로만 전달 가능
  • 컴포넌트 간의 통신 수단이 될 수 있음.
[Props는 어떤 상황에서 쓰는게 좋을까?]
1) 화면에 어떤 내용을 보여줄지 부모 컴포넌트가 결정하게 하고 싶을 때
2) 재사용 가능한 컴포넌트를 만들고 싶을 때

State에 대해 알아보자!

State란?

     state컴포넌트 자체가 저장하고 관리하는 값이다.

     사용자의 입력, 버튼 클릭, API 호출 결과 등 변화하는 값을 저장하는데 쓰인다.

import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>카운트: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

State의 특징
  • 컴포넌트 내부에서만 관리
  • 값이 바뀌면 자동으로 리렌더링
  • 함수형 컴포넌트에서는 useState 훅을 사용해서 만듦.
[state는 어떤 상황에서 쓰는게 좋을까?]
1) 사용자 입력을 저장하고 싶을 때 (예: 입력 폼)
2) 버튼 클릭 횟수를 세고 싶을 때
3) 특정 UI의 표시 여부를 토글할 때
=> 한마디로 변화하는 값을 사용하고 싶을 때!

Props와 State 한눈에 비교하기 🔍
항목 Props State
데이터 소유자 부모 컴포넌트 현재 컴포넌트
수정 가능 여부 ❌ 수정 불가 (읽기 전용) ⭕️ 가능 (setState 사용)
사용 목적 외부에서 받은 데이터로 렌더링 컴포넌트 내부 동적 상태 표현
주 사용처 재사용 가능한 컴포넌트 만들기 사용자 상호작용 반영
전달 방향 부모 → 자식 (단방향) 컴포넌트 내부 전용

 

'공부 > React' 카테고리의 다른 글

[React] React에서의 렌더링 방식 알아보기 🔍  (0) 2025.04.24
문제:
팀의 막내인 철수는 아메리카노와 카페 라테만 판매하는 카페에서 팀원들의 커피를 사려고 합니다.
아메리카노와 카페 라테의 가격은 차가운 것과 뜨거운 것 상관없이 각각 4500, 5000원입니다.
각 팀원에게 마실 메뉴를 적어달라고 하였고, 그 중에서 메뉴만 적은 팀원의 것은 차가운 것으로 통일하고
"아무거나"를 적은 팀원의 것은 차가운 아메리카노로 통일하기로 하였습니다.
각 직원이 적은 메뉴가 문자열 배열 order로 주어질 때, 카페에서 결제하게 될 금액을 return 하는 solution 함수를 작성해주세요. 

 

문제 풀어보기: https://school.programmers.co.kr/learn/courses/30/lessons/181837

 

풀이보기
더보기
function solution(order) {
    let result = 0;
    
    order.forEach((menu) => {
        menu.includes('latte')? result += 5000 : result += 4500;
    });
    
    return result;
}

order를 순회하면서 latte가 포함되어있으면 5000원, 그 외는 4500원을 더해주도록 하면 끝이다!

엄청 간단한 문제라 별다른 풀이는 없다.

대신 한 줄로 풀어보자면 아래와 같이 풀 수 있다.

 

const solution = (order) => order.reduce((total, current) => total + (current.includes('latte') ? 5000 : 4500), 0);

 

+ Recent posts