[프로그래머스 Lv.1] 개인정보 수집 유효기간 (자바스크립트)

2024. 5. 25. 21:10

 

🍊 문제 정보

레벨: 1

출처: 2023 KAKAO BLIND Recruitement

링크: https://school.programmers.co.kr/learn/courses/30/lessons/150370

정답까지 내 소요시간: 1시간...

 

🎮 나의 풀이

문제를 읽고 내가 핵심적으로 생각했던 요소는 다음과 같다.

(1) 정보 유형에 맞는 유효기간을 어떻게 저장해둘지?

(2) 수집 날짜로부터 유효기간 종료일을 어떻게 도출할지?

 

1️⃣ 정보 유형에 맞는 유효기간 저장하기

항상 A, B, C 등의 순차적인 알파벳으로 정의되지 않고 Z, D의 형태로 들어올 수 있기 때문에 각 유형을 key값으로 저장할 수 있어야 한다고 생각했다. 레벨0을 마스터하면서 접했던 Map 객체를 활용하여 다음과 같이 저장하였다.

// 유효기간 유형별로 저장
    const infoType = new Map();
    terms.forEach((info) => {
        infoType.set(info.split(' ')[0], Number(info.split(' ')[1]));
    })

 

terms는 "A 6"처럼 "${유형이름} ${유효기간 개월수}"형태로 들어오기 때문에 split을 통해 공백 기준으로 나누어 접근했다. 처음에 마지막 인덱스로 개월수를 담으려했는데 생각해보니 그럼 두 자리 수 이상의 개월수를 제대로 담아내지 못하게 된다.

 

2️⃣ 수집 날짜로부터 유효기간 종료일을 어떻게 도출할지?

[정확도 95점을 받았던 1st Try]

처음에 패턴을 도출하는 건 어렵지 않았다. (특별한 케이스를 제대로 잡는 것이 어려웠던 거지... 🥲)

일단 수집일자를 받아서 유효기간 보관 종료일을 구하는 함수를 따로 정의하였다.

 

수집일자를 배열 [year, month, day] 형태로 저장하여 사용했다.

내가 처음 생각했던 핵심 논리는 다음과 같다. 보관 개월수를 N이라고 할때, day는 1일만 빼주고 month에 N만큼을 더하여 12월을 초과할 때만 따로 처리하는 방식이었다.

 

테케1)의 2번째 사례를 참고해서 나의 방식을 적용했을 때 day가 0이 되는 경우를 처리하는 과정을 수정했다.

day가 0이 되면, 12월을 초과한 것을 처리하기 전에 day를 28일로 바꿔주고 month-1을 해주었다. 

month가 12월을 초과하면, year에 month를 12로 나눈 몫을 더해주고, month는 month를 12로 나눈 나머지로 바꾸었다.

 

 

이를 기반으로 작성한 보관종료일을 구하는 함수의 코드는 다음과 같았다.

/* 유효기간 종료일 구하는 함수 */
function getEndDate(created, qType, infoType) {
    let dateArr = created.split('.').map(Number); //수집일자 저장
    let duration = infoType.get(qType); //유효기간 저장
    
    
    dateArr[1] += duration; // month
    dateArr[2] -= 1; // day
    
    // 0일이 되었을 때
    if(dateArr[2] == 0) {
        dateArr[2] = 28;
        dateArr[1] -= 1;
    }
    
    // 월이 12월을 넘겼을 때
    if(dateArr[1] > 12) {
        dateArr[0] += Math.floor(dateArr[1] / 12);
        dateArr[1] = dateArr[1] % 12;
        
        if(dateArr[1] == 0) {
            dateArr[1] = 1;
        }
    }
    return dateArr.join('-');
}

 

정답은 결국 "파기해야 하는 정보 번호"를 구하는 것이었기 때문에 Date객체를 활용해서 보관종료일과 오늘날짜를 비교하였다. 종합한 전체 코드는 다음과 같다.

function solution(today, terms, privacies) {
    let answer = [];
    
    // 유효기간 유형별로 저장
    const infoType = new Map();
    terms.forEach((info) => {
        infoType.set(info.split(' ')[0], Number(info.split(' ')[1]));
    })
    
    // 각 정보 순회하여 처리 
    privacies.forEach((privacy, idx) => {
        let endDate = getEndDate(privacy.slice(0,10), privacy.slice(-1), infoType);
        
        if (new Date(today.replace('.', '-')) > new Date(endDate)) {
            answer.push(idx+1);
        }
    })
    
    return answer;
}

/* 유효기간 종료일 구하는 함수 */
function getEndDate(created, qType, infoType) {
    let dateArr = created.split('.').map(Number); //수집일자 저장
    let duration = infoType.get(qType); //유효기간 저장
    
    
    dateArr[1] += duration; // month
    dateArr[2] -= 1; // day
    
    // 0일이 되었을 때
    if(dateArr[2] == 0) {
        dateArr[2] = 28;
        dateArr[1] -= 1;
    }
    
    // 월이 12월을 넘겼을 때
    if(dateArr[1] > 12) {
        dateArr[0] += Math.floor(dateArr[1] / 12);
        dateArr[1] = dateArr[1] % 12;
        
        if(dateArr[1] == 0) {
            dateArr[1] = 1;
        }
    }
    return dateArr.join('-');
}
통과했으면 좋았을 것을 ... 95점의 정확도를 맞았다. 95점 정확도는 17번 반례 하나를 실패한 것이었다. 내가 떠올렸던 추가 테스트 케이스들은 year에 2년 이상을 더해야 하는 경우였다. 유효기간은 최대 100개월이었기 때문에 100인 케이스도 추가하였었다. 놀랍게도 통과해서 미스테리였다. 

 

[17번 반례 찾는 데만 30분 이상 걸린 2nd Try]

100달이 보관기간인 테스트 케이스, day가 0이 되는 테스트 케이스를 몇 개 더 추가해봐도 모두 통과하고, 계속 17번 케이스만 실패했다. 1시간을 넘긴 시점에 다른 분들이 프로그래머스 사이트에 제시한 여러 테스트 케이스를 참고하였다. 

 

범인은,,, month가 12월을 초과했을 때, year에 (month / 12 몫)만큼 더해주고, month를 (month % 12)로 바꾸었을 때 0이 되는 케이스였다. 

 

위의 코드와의 차이점은 month가 12를 넘겼을 때 처리 후 0이 된다면, 1월로 바꾸는 게 아니라

12월로 바꾸고, year는 1만큼 감소시키는 것이다.

function solution(today, terms, privacies) {
    let answer = [];
    
    // 유효기간 유형별로 저장
    const infoType = new Map();
    terms.forEach((info) => {
        infoType.set(info.split(' ')[0], Number(info.split(' ')[1]));
    })
    
    
    // 각 정보 순회하여 처리 
    privacies.forEach((privacy, idx) => {
        let endDate = getEndDate(privacy.slice(0,10), privacy.slice(-1), infoType);
        
        if (new Date(today.replace('.', '-')) > new Date(endDate)) {
            answer.push(idx+1);
        }
    })
    
    return answer;
}

/* 유효기간 종료일 구하는 함수 */
function getEndDate(created, qType, infoType) {
    let dateArr = created.split('.').map(Number); //수집일자 저장
    let duration = infoType.get(qType); //유효기간 저장
    
    
    dateArr[1] += duration; // month
    dateArr[2] -= 1; // day
    
    // 0일이 되었을 때
    if(dateArr[2] == 0) {
        dateArr[2] = 28;
        dateArr[1] -= 1;
    }
    //console.log("중간단계: ", dateArr)
    
    // 월이 12월을 넘겼을 때
    if(dateArr[1] > 12) {
        dateArr[0] += Math.floor(dateArr[1] / 12);
        dateArr[1] = dateArr[1] % 12;
        
        if(dateArr[1] == 0) {
            dateArr[1] = 12;
            dateArr[0] -= 1;
        }
    }
    
    return dateArr.join('-');
}

 

📩 레슨런

- 처음부터 테스트케이스를  month와 day 변화의 특이점을 기준삼아 구성했다면 더욱 좋았을 것이다.

month의 특이점은,

(1)보관기간 만큼 더했을 때 12를 초과하는 경우가 생긴다는 것

(2)year에 적절한 값을 더해주고, 그 나머지를 month로 할당하면 0월인 경우가 생긴다는 것

 

day의 특이점은,

(3)1을 뺐을 때 0일인 경우가 생긴다는 것

 

이를 바탕으로 다음과 같이 정리하면 빨간 부분이 내가 놓쳤던 처리라는 것을 더욱 쉽게 알았을 것이다. 

month가 12월을 초과함 month가 0이 됨 day가 0이 됨
O O (year에 더해주고 나머지가 0) O (수집 day가 1일임)
X X
O O X
O X O
X O (수집 month가 1월임) O
X O X
X X O

 

🧐 좋은 풀이 톺아보기

좋아요를 가장 많이 받은 풀이를 보니 접근 방식이 아예 나와 달랐다. 날짜 형태가 아니라 하나의 정수값으로 압축하여 비교했다. (어차피 모든 달을 28일로 가정하였기 때문에 더욱 적용하기 쉬운 풀이었던 것 같다.) dates에 보관기간 만큼을 더한 값이 today값보다 작은지 확인하는 방식!

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;
}

 

BELATED ARTICLES

more