본문 바로가기
개발자 일기/알고리즘 공부

[프로그래머스 JS] 개인정보 수집 유효기간

by MS_developer 2023. 7. 1.

📖 문제 설명

고객의 약관 동의를 얻어서 수집된 1~n 번으로 분류되는 개인정보 n 개가 있습니다. 약관 종류는 여러 가지 있으며 각 약관마다 개인정보 보관 유효기간이 정해져 있습니다. 당신은 각 개인정보가 어떤 약관으로 수집됐는지 알고 있습니다. 수집된 개인정보는 유효기간 전까지만 보관 가능하며, 유효기간이 지났다면 반드시 파기해야 합니다.

 

예를 들어, A라는 약관의 유효기간이 12 달이고, 2021년 1월 5일에 수집된 개인정보가 A약관으로 수집되었다면 해당 개인정보는 2022년 1월 4일까지 보관 가능하며 2022년 1월 5일부터 파기해야 할 개인정보입니다.

 

당신은 오늘 날짜로 파기해야 할 개인정보 번호들을 구하려 합니다.

 

모든 달은 28일까지 있다고 가정합니다.

 

오늘 날짜를 의미하는 문자열 today, 약관의 유효기간을 담은 1차원 문자열 배열 terms와 수집된 개인정보의 정보를 담은 1차원 문자열 배열 privacies가 매개변수로 주어집니다. 이때 파기해야 할 개인정보의 번호를 오름차순으로 1차원 정수 배열에 담아 return 하도록 solution 함수를 완성해 주세요.


🚫 제한 사항

  • today는 "YYYY.MM.DD" 형태로 오늘 날짜를 나타냅니다.
  • 1 ≤ terms의 길이 ≤ 20
    • terms의 원소는 "약관 종류 유효기간" 형태의 약관 종류와 유효기간을 공백 하나로 구분한 문자열입니다.
    • 약관 종류는 A~Z중 알파벳 대문자 하나이며, terms 배열에서 약관 종류는 중복되지 않습니다.
    • 유효기간은 개인정보를 보관할 수 있는 달 수를 나타내는 정수이며, 1 이상 100 이하입니다.
  • 1 ≤ privacies의 길이 ≤ 100
    • privacies[i]는 i+1번 개인정보의 수집 일자와 약관 종류를 나타냅니다.
    • privacies의 원소는 "날짜 약관 종류" 형태의 날짜와 약관 종류를 공백 하나로 구분한 문자열입니다.
    • 날짜는 "YYYY.MM.DD" 형태의 개인정보가 수집된 날짜를 나타내며, today 이전의 날짜만 주어집니다.
    • privacies의 약관 종류는 항상 terms에 나타난 약관 종류만 주어집니다.
  • today와 privacies에 등장하는 날짜의 YYYY는 연도, MM은 월, DD는 일을 나타내며 점(.) 하나로 구분되어 있습니다.
    • 2000 ≤ YYYY ≤ 2022
    • 1 ≤ MM ≤ 12
    • MM이 한 자릿수인 경우 앞에 0이 붙습니다.
    • 1 ≤ DD ≤ 28
    • DD가 한 자릿수인 경우 앞에 0이 붙습니다.
  • 파기해야 할 개인정보가 하나 이상 존재하는 입력만 주어집니다.

💾 입출력 예시

 

today terms privacies result
"2022.05.19" ["A 6", "B 12", "C 3"] ["2021.05.02 A",
"2021.07.01 B",
"2022.02.19 C",
"2022.02.20 C"]
[1, 3]
"2020.01.01" ["Z 3", "D 5"] ["2019.01.01 D",
"2019.11.15 Z",
"2019.08.02 D",
"2019.07.01 D",
"2018.12.28 Z"]
[1, 4, 5]

 


⌨️ 나의 풀이 (코드)

 

내용이 많다보니 의사코드를 통해 필요한 로직을 정리해 보았다.

 

1. privacies의 각 요소 순회, 이때 대문자가 일치하는 약관을 terms에서 탐색
2. 일치하는 시간에 따라 privacies 요소의 날짜로부터 약관만큼 시간을 진행, 비교를 위한 임시 배열 안에 저장 (날짜 만료일만 기록)
------ privacies 순회 종료 ------
3. 임시 배열 안의 요소를 재차 순회하며 해당 배열이 today를 지났는지 확인, 지났다면 반환할 배열에 추가 (.map 을 쓰면 좋을 듯)

 

의사코드를 기반으로 코드를 작성하다보면 실수가 나오기 마련이니, 조심스럽게 과정들을 좀 더 세분화 하면서, 추가적으로 필요한 과정을 한 번 더 짚어 보았다.

 

1. 배열 내 문자열로 정리된 terms를 privacies 안에서 반복 순회할 경우, 효율이 떨어지므로 객체화하는 것이 좋아 보인다.
ex) { A: 6, B: 12, C: 3 }

2. 날짜를 비교할 때도 YYYY.MM.DD로 변형되어 있기 때문에 split 메서드를 활용해 각 년도, 월, 일을 따로 비교하는 것이 좋을 듯 하다. 이때, 문자열은 비교가 어려우므로 해당 과정에서 배열의 요소를 문자열에서 숫자로 바꿔준다.
ex) [ '2022', '05', '19' ] ->  [2022, 05, 19]

3. 약관 만큼의 일자를 추가하기 위해서는 split 메서드를 활용한 배열에서 28 * 약관 달 만큼 추가한 후, 일 에서는 1을 빼는 식으로 진행한다. 이후 해당 데이터에 달 > 12 또는 일 <= 0 이라면 이에 맞게 데이터를 변동해준다.
ex) [ 2021, 05, 02 ] 의 A 약관 (6달) -> [ 2021, 10, 01 ] 

 

정리된 내용들을 기반으로 코드를 작성해 보았다.

 

function solution(today, terms, privacies) {
  const answer = [];
  const expDate = [];
  const todayToNum = today.split(".").map(Number);
  const termsRecord = {};
  terms.forEach((term) => {
    termsRecord[term.split(" ")[0]] = Number(term.split(" ")[1]);
  });

  privacies.forEach((privacy) => {
    const date = privacy.split(" ")[0].split(".").map(Number);
    const policy = privacy.split(" ")[1];

    date[1] += termsRecord[policy];
    date[2]--;

    if (date[1] > 12 && date[2] > 0) {
      date[0]++;
      date[1] = date[1] % 12;
    } else if (date[1] > 12 && date[2] === 0) {
      date[0]++;
      date[1] = date[1] % 12;
      date[2] = 28;
    } else if (date[1] <= 12 && date[2] === 0) {
      date[1]--;
      date[2] = 28;
    }

    expDate.push(date);
  });

  expDate.forEach((date, idx) => {
    if (date[0] < todayToNum[0]) {
      answer.push(idx + 1);
    } else if (date[1] < todayToNum[1]) {
      answer.push(idx + 1);
    } else if (date[2] < todayToNum[2] && date[1] <= todayToNum[1]) {
      answer.push(idx + 1);
    }
  });

  return answer;
}

 

기본 테스트들은 모두 통과했지만, 추가 케이스들은 대부분 실패했다.

 

문제와 제한 사항, 코드를 살펴보며 문제점을 하나씩 정리해 보았다.

 

1. 만료일을 구할 때 구성된 로직이 지나치게 복잡하고, 순서가 잘못되었다. (일 -> 월 -> 년 순의 구성이 아님)
2. 만기 기간이 2년 이상이어도 년도가 1밖에 증가하지 않는다.
3. 오늘 날짜와 만료일을 비교하는 로직이 틀렸다. 연도, 월, 일을 비교할 때 앞선 조건이 성립이 되는지 확인이 필요하다.

 

고려하지 못했던 부분들이 있던 코드들을 살펴 보고, 목적에 맞게 로직을 수정했다.

 

function solution(today, terms, privacies) {
  const answer = [];
  const expDates = [];
  const todayToNum = today.split(".").map(Number);
  const termsRecord = {};
  terms.forEach((term) => {
    termsRecord[term.split(" ")[0]] = Number(term.split(" ")[1]);
  });

  privacies.forEach((privacy) => {
    const date = privacy.split(" ")[0].split(".").map(Number);
    const policy = privacy.split(" ")[1];

    date[1] += termsRecord[policy];
    date[2]--;

    if (date[2] === 0) {
      date[1]--;
      date[2] = 28;
    }

    if (date[1] > 12) {
      if (date[1] % 12 === 0) {
        date[0] += parseInt(date[1] / 12) - 1;
        date[1] = 12;
      } else {
        date[0] += parseInt(date[1] / 12);
        date[1] = date[1] % 12;
      }
    }

    expDates.push(date);
  });

  console.log("today", todayToNum);

  expDates.forEach((expDate, idx) => {
    if (expDate[0] < todayToNum[0]) {
      answer.push(idx + 1);
    } else if (expDate[0] <= todayToNum[0] && expDate[1] < todayToNum[1]) {
      answer.push(idx + 1);
    } else if (
      expDate[0] <= todayToNum[0] &&
      expDate[1] <= todayToNum[1] &&
      expDate[2] < todayToNum[2]
    ) {
      answer.push(idx + 1);
    }
  });

  return answer;
}

 

이후 테스트 케이스들을 모두 통과할 수 있었다.


📝 Note

 

늘 그렇듯, 더 함축적이고 효율적인 코드가 있었다.

 

function solution(today, terms, privacies) {
  var answer = [];
  var [year, month, date] = today.split(".").map(Number);
  var todates = year * 12 * 28 + month * 28 + date;
  var t = {};
  terms.forEach((e) => {
    let [a, b] = e.split(" ");
    t[a] = Number(b);
  });
  privacies.forEach((e, i) => {
    var [day, term] = e.split(" ");
    day = day.split(".").map(Number);
    var dates = day[0] * 12 * 28 + day[1] * 28 + day[2] + t[term] * 28;
    if (dates <= todates) answer.push(i + 1);
  });
  return answer;
}

 

로직의 방향성과 생각한 방식은 비슷하지만, 가독성이 압도적으로 좋은 코드였다. 해당 코드의 장점을 정리해 보았다.

 

1. 구조분해할당을 사용해 변수 선언에 있어 가독성을 높였다.
2. 전체 일자를 매월 28일인 점을 고려해 모두 더해 수와 수를 비교했다. 

 

 

댓글