문제 해결 연습 > 프로그래머스 > 과일장수

주싱·2023년 2월 27일
0

문제

링크 : 프로그래머스 > Level 1 > 과일장수

소요 시간

오랜 시간이 걸려서 천천히 학습하면서 문제를 풀었습니다.

문제 해결 코드

package solving.twentythree.feb;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class FruitSellerTest {
    static Stream<Arguments> testCaseSupplier() {
        return Stream.of(Arguments.arguments(3, 4, new int[] { 1, 2, 3, 1, 2, 3, 1 }, 8),
                         Arguments.arguments(4, 3, new int[] { 4, 1, 2, 2, 4, 4, 4, 4, 1, 2, 4, 2 }, 33));
    }

    @ParameterizedTest
    @MethodSource("testCaseSupplier")
    void testSolution(int maxScore, int numberInBox, int[] scores, int expected) {
        int result = solution(maxScore, numberInBox, scores);
        Assertions.assertEquals(expected, result);
    }

    /**
     * 과일 장수가 박스에 사과를 담을 때 얻을 수 있는 최대 점수를 반환합니다. 과일 장수가 획득할 수 있는 점수는 박스 개수 x 한 상자에 담긴 사과의 개수 x 박스 안에서 최저 사과 점수입니다.
     * @param maxScore 사과 점수 최고점
     * @param numOfApplePerBox 한 상자에 담을 수 있는 박스 개수
     * @param appleScores 사과의 점수 배열
     * @return 과일 장수가 박스에 사과를 담을 때 얻을 수 있는 최대 점수
     */
    public int solution(int maxScore, int numOfApplePerBox, int[] appleScores) {
        // 1. 사과의 점수를 기준으로 내림차순 정렬합니다.
        Integer[] sortedAppleScores = Arrays.stream(appleScores)
                .boxed()
                .sorted(Comparator.reverseOrder())
                .toArray(Integer[]::new);

        // 2. 점수가 높은 사과부터 차례로 박스에 담습니다.
        List<List<Integer>> boxes = IntStream.range(0, appleScores.length / numOfApplePerBox)
                .sorted()
                .mapToObj(boxIndex -> packAppleBox(boxIndex, numOfApplePerBox, sortedAppleScores))
                .collect(Collectors.toList());

        // 3. 전체 박스의 점수를 계산하고 합산합니다.
        return boxes.stream()
                .mapToInt(box -> calculateBoxScore(box))
                .sum();
    }

    /**
     * 사과의 점수를 기준으로 내림차순 정렬된 사과 점수 배열에서 박스에 담을 사과 점수 목록을 반환합니다.
     * @param boxIndex 박스 인덱스
     * @param numOfApplePerBox 한 상자에 담을 수 있는 박스 개수
     * @param sortedAppleScores 사과의 점수 배열
     * @return 박스에 담을 사과 점수 목록
     */
    private static List<Integer> packAppleBox(int boxIndex, int numOfApplePerBox, Integer[] sortedAppleScores) {
        return Arrays.stream(sortedAppleScores,
                        boxIndex * numOfApplePerBox,
                        (boxIndex + 1) * numOfApplePerBox)
                .collect(Collectors.toList());
    }

    /**
     * 박스에 담긴 사과의 점수 목록을 받아서 사과의 점수를 계산합니다. (사과 개수 x 박스 안에서 최저 사과 점수)
     * @param appleScores 박스에 담긴 사과의 점수 목록
     * @return 박스에 담긴 사과의 점수
     */
    private static int calculateBoxScore(List<Integer> appleScores) {
        // appleScores 정렬되어 있어야 한다는 숨겨진 제약이 있는데 개선 방법이 없을까?
        return appleScores.size() * getMinScore(appleScores);
    }

    /**
     * 박스에 담긴 사과의 점수 목록에서 최저 점수를 반환합니다. (박스에 담긴 사과의 점수 목록은 내림차순 정렬되어 있어야 합니다.)
     * @param box 박스에 담긴 사과의 점수 목록
     * @return 박스에 담긴 사과의 최저 점수
     */
    private static int getMinScore(List<Integer> box) {
        return box.get(box.size() - 1);
    }
}

문제 풀이 과정 회고

결론적으로 문제 자체를 푸는 해법은 간단했습니다. 박스에 담을 수 있는 고정된 개수 만큼 높은 점수의 사과부터 넣은 다음, 박스를 가득 채우지 못하는 낮은 점수 사과는 버리면 되었습니다. 이 해법이 맞는지 확신이 서지 않아서 코드를 작성해서 확인해 봐야 했는데 코드를 자유롭게 작성할 수 없었습니다. Stream API를 사용해 데이터를 자유자재로 조작하는데 익숙하지 못했던 것 같습니다. for 문으로 직접 구현해도 되었지만 그렇게 하고 싶지 않았던 것 같습니다. 문제를 푸는데 1시간이 넘어가 빨리 푸는 건 포기하고 API를 하나씩 학습하면서 풀기 시작했습니다. 특정 문제 해결에 필요한 API가 무엇인지 ChatGPT에게 질의를 했는데 예제와 함께 정확하고 빠르게 가르쳐주는 것 같습니다. 학습에 잘 활용하면 유익할 것 같습니다.

잘한 점

  • 끝까지 포기하지 않고 문제를 푼점 칭찬하고 싶습니다.

학습한 것

  • (N > M) 정수 N을 M으로 나눌 때 M으로 나누어 떨어지지 않는 경우 남은 것을 한 그룹으로 묶는다고 할 때 몇 개로 나누어 질 수 있는지 계산하는 공식 (N+M-1)/M
  • java.util.Arrays::stream(int[]) 메서드는 기본형 int를 처리하는 IntStream을 반환합니다.
  • java.util.Arrays::stream(int[] arr, int startInclusive, int endExclusive) 메서드를 활용하면 정수 배열의 부분적인 Stream을 생성할 수 있습니다.
  • java.util.stream.Stream::sorted() 메서드 인자로 Comparator.reverseOrder()를 사용하기 위해서는 Stream의 요소가 java.lang.Comparable 인터페이스를 구현하고 있어야 합니다. 그런데 IntStream의 경우에는 기본형 타입인 int를 요소로 가짐으로 Comparable 인터페이스를 구현하고 있지 않습니다. 따라서 reverseOrder()를 사용할 수 없습니다. 이를 해결하기 위해 먼저 boxing 과정을 거칠 수 있습니다.
  • java.util.stream.Stream::sorted() 메서드는 기본적으로 오름차순 정렬을 수행합니다.
  • java.util.stream.Stream::mapToInt() 메서드를 활용하면 박싱 타입인 Stream을 언박싱 타입은 IntStream으로 변경할 수 있습니다.
  • Stream<List>와 같은 타입이 있으면 mapToInt를 통해내부 리스트에 어떤 계산을 수행한 값들에 대한 외부 stream sum() 같은 동작을 수행하기 유용합니다.
  • Arrays.sort(int[])를 활용해 오름차순 정렬이 가능하다. Comparator를 두 번째 인자로 받는 sort 메서드는 첫 번째 인자로 T[] 타입을 받기 때문에 기본형 타입 변수는 내림차순 정렬이 불가능하다.
  • Stream 타입의 toArray() 메서드는 기본적으로 Object[] 타입을 반환한다. 인자 toArray(Integer[]::new) 와같이 전달하면 Integer[] 타입을 받을 수 있다.
profile
소프트웨어 엔지니어, 일상

0개의 댓글