문제 해결 연습 | 개인정보 수집 유효기간

주싱·2023년 1월 8일
1

링크 : 프로그래머스 > Level 1 > 개인정보 수집 유효기간

문제

고객의 약관 동의를 얻어서 수집된 1~n번으로 분류되는 개인정보 n개가 있습니다. 약관 종류는 여러 가지 있으며 각 약관마다 개인정보 보관 유효기간이 정해져 있습니다. 당신은 각 개인정보가 어떤 약관으로 수집됐는지 알고 있습니다. 수집된 개인정보는 유효기간 전까지만 보관 가능하며, 유효기간이 지났다면 반드시 파기해야 합니다. 예를 들어, A라는 약관의 유효기간이 12 달이고, 2021년 1월 5일에 수집된 개인정보가 A약관으로 수집되었다면 해당 개인정보는 2022년 1월 4일까지 보관 가능하며 2022년 1월 5일부터 파기해야 할 개인정보입니다.당신은 오늘 날짜로 파기해야 할 개인정보 번호들을 구하려 합니다. 모든 달은 28일까지 있다고 가정합니다.

소요 시간

70분

해결과정 회고

  • 처음에 문제 해결 단계를 무작정 작게 나누고 각 단계별로 중간 데이터를 저장하는 클래스를 선언해 사용했다. 돌아보니 궁극적으로 필요하지 않은 중간 과정 데이터를 저장하고 처리해서 코드가 장황해 진 것 같다. 즉시 계산해서 처리함으로 단계 별로 해결해야하는 문제의 크기를 조금 더 크게 잡는게 나았던 것 같다.
  • 문제를 해결하는 코드 작성과 테스트 실행이 길다란 한 호흡에 이루어 졌다. 단계별로 확인하면서 나아갔다면 조금더 안정적인 마음으로 문제를 풀었을 것 같다.
  • 처음에 문제를 해결하기 위한 계획을 메서드 위에 주석으로 달았는데 중간에 주석을 코드 안으로 옮기는 불필요한 동작을 했다. 코드를 작성하는 영역에 즉시 문제 해결 과정을 주석으로 다는 것이 낫겠다.

학습한 것

  • List를 Int[] 타입으로로 바꾸는 Stream API를 사용했습니다.
  • LocalDate.isBefore() 메서드는 같은 두 날짜가 같은 경우 false를 반환한다.
  • String.split() 메서드 인자로 정규표현식이 들어가는데 “.” 문자는 특수문자임으로 “\.”과 같이 Escape 문자 처리를 해주어야 한다.

리팩토링된 최종 코드

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

public class PersonalInformationCollectionValidityPeriodTest {
    @Test
    void tester() {
        int[] solution = solution("2022.05.19", new String[]{"A 6", "B 12", "C 3"},
                                  new String[]{"2021.05.02 A",
                                               "2021.07.01 B",
                                               "2022.02.19 C",
                                               "2022.02.20 C"});
        System.out.println(Arrays.toString(solution));
        Assertions.assertArrayEquals(new int[]{1,3}, solution);
    }

    public int[] solution(String today, String[] strTerms, String[] strPrivacies) {

        // 약관 데이터 구조를 생성합니다.
        HashMap<String, Integer> termsMap = new HashMap<>();
        Arrays.stream(strTerms)
                .forEach(strTerm -> {
                    String[] idAndLimitMonth = strTerm.split(" ");
                    termsMap.put(idAndLimitMonth[0], Integer.parseInt(idAndLimitMonth[1]));
                });

        // 각 개인정보의 유효한 날짜를 계산합니다.
        List<LocalDate> validDates = Arrays.stream(strPrivacies)
                                           .map(strPrivacy -> strPrivacy.split(" "))
                                           .map(collectDateAndTerm -> toValidDate(collectDateAndTerm, termsMap))
                                           .collect(Collectors.toList());

        // 오늘 날짜와 비교하여 파기 여부를 판단합니다.
        List<Integer> destroyPrivacies = new ArrayList<>();
        LocalDate todayDate = toDate(today);
        IntStream.range(0, validDates.size())
                 .forEach(index -> {
                     if (validDates.get(index).isBefore(todayDate)) {
                         destroyPrivacies.add(index + 1);
                     }
                 });

        // 정수 배열로 변환하여 반환합니다.
        return destroyPrivacies.stream()
                               .mapToInt(i -> i)
                               .toArray();
    }

    LocalDate toValidDate(String[] collectDateAndTerm, HashMap<String, Integer> termsMap) {
        LocalDate collectDate = toDate(collectDateAndTerm[0]);
        String termId = collectDateAndTerm[1];
        Integer validMonth = termsMap.get(termId);

        LocalDate validDate = collectDate.plusMonths(validMonth).minusDays(1);
        if (collectDate.getDayOfMonth() == 1) {
            validDate = LocalDate.of(validDate.getYear(), validDate.getMonth(), 28);
        }
        return validDate;
    }

    LocalDate toDate(String date) {
        String[] todayItems = date.split("\\.");
        return LocalDate.of(Integer.parseInt(todayItems[0]), // 년
                            Integer.parseInt(todayItems[1]), // 월
                            Integer.parseInt(todayItems[2])); // 일
    }
}

초기 문제 해결 코드

70분이라는 시간동안 테스트 케이스를 통과하도록 작성한 날 것 그대로의 코드입니다.

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.stream.IntStream;

public int[] solution(String today, String[] strTerms, String[] strPrivacies) {

    // 특별히 유의해야할 제약사항은 보이지 않습니다.

    class Privacy {
        String term;
        LocalDate collectedDate;
    }

    // 1. 각 개인정보의 파기 날짜를 계산합니다.
    // 약관을 참조하기 쉽도록 맵으로 만듭니다.
    HashMap<String, Integer> termsMap = new HashMap<>();
    Arrays.stream(strTerms)
            .forEach(strTerm -> {
                String[] idAndLimitMonth = strTerm.split(" ");
                termsMap.put(idAndLimitMonth[0], Integer.parseInt(idAndLimitMonth[1]));
            });

    // 개인정보를 참조하기 쉽도록 객체 목록으로 만듭니다.
    List<Privacy> privacies = new ArrayList<>();
    Arrays.stream(strPrivacies)
            .forEach( strPrivacy -> {
                String[] collectionAndTerm = strPrivacy.split(" ");
                Privacy privacy = new Privacy();
                String[] dateItems = collectionAndTerm[0].split("\\.");
                privacy.collectedDate = LocalDate.of(Integer.parseInt(dateItems[0]), // 년
                                                     Integer.parseInt(dateItems[1]), // 원
                                                     Integer.parseInt(dateItems[2])); // 일
                privacy.term = collectionAndTerm[1];
                privacies.add(privacy);
            });

    // 개인정보.약관을 참조하여 개인정보.번호와 파기날짜로 구성된 객체를 생성한다.

    class PrivacyDueDate {
        int privacyId;
        LocalDate dueDate;
    }

    List<PrivacyDueDate> privacyDueDates = new ArrayList<>();

    IntStream.range(0, privacies.size())
             .forEach(index -> {
                 PrivacyDueDate privacyDueDate = new PrivacyDueDate();

                 privacyDueDate.dueDate = toDueDate(privacies.get(index).collectedDate,
                                                    termsMap.get(privacies.get(index).term));
                 privacyDueDate.privacyId = index + 1;

                 privacyDueDates.add(privacyDueDate);
             });

    // 오늘 날짜와 비교하여 파기 여부를 판단합니다.
    String[] todayItems = today.split("\\.");
    LocalDate todayDate = LocalDate.of(Integer.parseInt(todayItems[0]),
                                   Integer.parseInt(todayItems[1]),
                                   Integer.parseInt(todayItems[2]));

    List<Integer> dueDatePrivacies = new ArrayList<>();
    privacyDueDates.forEach( privacyDueDate -> {
        // 날짜 비교는 어떻게 수행할 것인가? 개인정보 파기 날짜보다 작거나 같은지 비교해야 한다.
        if (privacyDueDate.dueDate.isBefore(todayDate)) {
            dueDatePrivacies.add(privacyDueDate.privacyId);
        }
    });

    // 배열 인덱스에서 1씩 더하여 개인정보 번호를 반환합니다.
    int[] answer = new int[dueDatePrivacies.size()];
    IntStream.range(0, dueDatePrivacies.size())
                     .forEach( index -> {
                         answer[index] = dueDatePrivacies.get(index);
                     });
    return answer;
}

LocalDate toDueDate(LocalDate startDate, int month) {
    LocalDate dueDate = startDate.plusMonths(month).minusDays(1);
    if (startDate.getDayOfMonth() == 1) {
        dueDate = LocalDate.of(dueDate.getYear(), dueDate.getMonth(), 28);
    }
    return dueDate;
}
profile
소프트웨어 엔지니어, 일상

0개의 댓글