[프로그래머스 1레벨] 예산

이민선(Jasmine)·2023년 1월 9일
1
post-thumbnail

우선 나의 코드부터 기록하자면,

function solution(d, budget) {
    const sortedD = d.sort((a,b)=> a - b);
    let sum = d.reduce((s,v)=>s+=v,0);
         while(sum>budget){
        sum -= Math.max(...d);
        d.pop();
     }
    return d.length;
}

큰 금액의 예산을 요구하는 부서부터 거절할 준비를 하고 오름차순 정렬.
요구된 예산 총계를 구하기 위해 reduce 사용하고 sum 변수에 담아준다.
그리고 while문을 이용하여, 주어진 budget보다 총계가 작아질 때까지
배열에서 큰 금액 요구하는 부서부터 자름.(pop()이용)
마침내 budget보다 총계가 작아지면 남아있는 원소들의 개수를 반환.

후술하겠지만 reduce를 좀 더 기똥차게 이용할 수 있는 방법도 있다.

"reduce가 뭐에요?"

그거 그냥 배열 원소 다 더할 때 쓰는거 아닌가?

드림코딩 들을 때는 값을 접고 접어 하나로 만드는 함수? 정도로 배웠다.
사실 배열 원소들의 합계 구할 때 쓰는 것 정도로 알아도 지금까지 큰 문제는 없었다.
하지만 다음과 같은 이유로 MDN을 보며 좀 더 친해지기로 결심했다.

  • 드림코딩 때 들었던 값을 접고 접어 하나로 만든다는 뜻이 완전히 와닿지는 않았음
  • 기술면접 질문으로 나온 데에는 내가 놓치고 있는 중요한 이유가 있을 것 같다는 생각
  • reduce의 원리를 정확히 알고 있으면 좀 더 다양한 상황에 수월하게 이용할 수 있을 것 같다는 생각

MDN을 보니 reduce()메서드의 정의는 아래와 같다.

reduce() 메서드는 배열의 각 요소에 대해 주어진 리듀서 (reducer) 함수를 실행하고, 하나의 결과값을 반환합니다.

reducer 함수? 그게 뭔데?
커피가 뭔데? 커피는 커피콩으로 만든 음료입니다.
이거랑 뭐가 다르지? ㅋㅋ?

리듀서 함수는 4개의 인자를 가진다.
1. 누산기 (acc)
2. 현재 값 (cur)
3. 현재 인덱스 (idx)
4. 원본 배열 (src)

그런데 initial value를 제공했느냐 안했느냐에 따라 acc와 cur이 다르게 결정된다.

  • initial value를 제공한 경우
    acc : initial value와 같아짐.
    cur : 배열의 첫번째 값.
  • initial value를 제공하지 않은 경우
    acc : 배열의 첫번째 값
    cur : 배열의 두번째 값
[0, 1, 2, 3, 4].reduce(function(accumulator, currentValue, currentIndex, array) {
  return accumulator + currentValue;
}, 10);
// 20

만약 이런 코드가 있다면, reduce 안에 들어있는 function이 리듀서 함수가 되는 것이다.
이제 커피콩이 뭔지는 감이 좀 잡혔다.

reduce 함수는 아래와 같은 용도로도 사용할 수 있다.
MDN에 있는 예제가 너무 많아서 내 수준에 맞는 것들을 우선적으로 살펴보기로 했다.

- 중첩 배열 펼치기

[[0, 1], [2, 3], [4, 5]].reduce(
  function(accumulator, currentValue) {
    return accumulator.concat(currentValue);
  },
  []
);
// 펼친 결과: [0, 1, 2, 3, 4, 5]

초기값을 빈 배열로 주면 acc이 빈 배열이 되어 시작한다.
cur은 [0, 1]이 되고 concat으로 하나씩 붙여가는 것이다.
(하지만 flat과 스프레드 연산자를 알아버린 나,, 이렇게는 굳이 사용 안하지 않을까?라는 생각이 들지만 혹시 모르지)

- 객체 내의 값 인스턴스 개수 세기

var names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'];

var countedNames = names.reduce(function (allNames, name) {
  if (name in allNames) {
    allNames[name]++;
  }
  else {
    allNames[name] = 1;
  }
  return allNames;
}, {});
// countedNames is:
// { 'Alice': 2, 'Bob': 1, 'Tiff': 1, 'Bruce': 1 }

오오 이건 정말 유용할 것 같다
배열 내 원소들의 빈도수 세기!
initial value를 빈 객체로 선언했기 때문에 allNames는 객체이고 name은 key가 된다.
allNames라는 객체에 Alice라는 이름이 있나요?
없으면 Alice라는 key 추가하고, value에 1 할당.
있으면 Alice의 value 1만큼 증가.

- 배열의 중복 항목 제거

let arr = [1, 2, 1, 2, 3, 5, 4, 5, 3, 4, 4, 4, 4];
let result = arr.sort().reduce((accumulator, current) => {
    const length = accumulator.length
    if (length === 0 || accumulator[length - 1] !== current) {
        accumulator.push(current);
    }
    return accumulator;
}, []);
console.log(result); //[1,2,3,4,5]

이것도 딱히,, [...new Set(myArray)]를 알아버린 나,, 중복 항목 제거하려고 굳이 reduce 쓸 일이 있을까? 싶지만 우선 원리는 알아둔다.

initial value가 빈 배열이 되고, 초기에 빈배열 길이가 0이거나 누산기의 마지막 값이 current와 다르면 배열에 값을 추가하는 것이다.
나중에 조건에 맞는 특정 값만 배열에 추가할 때도 이런 원리를 사용할 수는 있을 것 같다.

MDN 참고:
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce#%EA%B0%9D%EC%B2%B4_%EB%82%B4%EC%9D%98_%EA%B0%92_%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4_%EA%B0%9C%EC%88%98_%EC%84%B8%EA%B8%B0

reduce는 이 정도로 살펴보는 것으루!

initial value가 이 정도로 중요한 역할을 하는지는 모르고 있었다.
inital value에 0을 선언하느냐, 객체를 선언하느냐, 배열을 선언하느냐에 따라
리듀서함수의 역할이 좌우되는 것이었다.. ㄷㄷ
맨날 0만 넣던 나에게 ㅋㅋㅋㅋㅋ 어차피 매일 0 넣을건데 귀찮게 왜 넣는 거야?
라고 생각했던 지난 날의 나.. 개과천선하는 날이다.

reduce와 좀 친해졌으니, reduce를 활용한 기똥찬 코드를 가져와보겠다.

function solution(d, budget) {
    return d.sort((a, b) => a - b).reduce((count, price) => {
        return count + ((budget -= price) >= 0);
    }, 0);
}

count는 누산기이므로, 초기값은 0이 된다.
price는 오름차순된 배열의 맨 처음값부터 순서대로 할당된다.
그리고 기똥찬 포인트는 바로 리듀서 함수에 boolean 값을 이용했다는 점!

((budget -= price) >= 0)
(budget = budget - price >= 0)
// budget >= price 의 참 거짓을 따짐

budget이 price보다 크면 ((budget -= price) >= 0)은 boolean true가 되어 1의 값을 가지고,
budget이 price보다 작으면 ((budget -= price) >= 0)은 boolean false가 되어 0의 값을 가진다.

맨처음, budget = 9이고 오름차순된 배열(d.sort())이 [1,2,3,4,5]라면,
budget - price = 9 - 1 = 8이 되어 0보다 크므로 아직 그 다음 부서에게 지원할 여력이 남아 있음을 의미한다.
boolean값이 1이되므로 count에 1추가.
그다음 price에 2가 할당되면 budget - price = 8 - 2 = 6 > 0이 되어
마찬가지로 boolean값 true이므로 count에 1 추가.
.
.
price에 4가 할당되면 budget - price = 3 - 4 < 0이 되어
boolean 값 0이 되어 count에 0 추가. 예산이 소진되었음을 의미.
.
.

이렇게 해서 최종 count는 3 반환!

reduce와 친해지니 좀 오버하자면 시야가 한 차원 트이는 기분이다.
아주 뿌듯하군!
특히 배열 원소 빈도수 구할 때 자주 쓸 것 같다.

그럼 20000!

profile
기록에 진심인 개발자 🌿

0개의 댓글