2021-07-03 알고리즘 문제 풀이
문제 URL : https://programmers.co.kr/learn/courses/30/lessons/72410
코딩테스트 연습 - 신규 아이디 추천
카카오에 입사한 신입 개발자 네오는 "카카오계정개발팀"에 배치되어, 카카오 서비스에 가입하는 유저들의 아이디를 생성하는 업무를 담당하게 되었습니다. "네오"에게 주어진 첫 업무는 새로
programmers.co.kr
tmi
종종 프론트엔드 개발자는 백엔드만큼의 알고리즘 능력을 요구하지 않는다. 그렇게 많이 필요한 것 같진않다..라는 말을 듣곤 하는데
코딱지 주제에 내가 감히 뭐라고.. 알고리즘!! 필요없다!!! 옳다!! 그르다!! 할 수는 없는 문제이지만, 일하면서 느낀 게 좀 있었다.
우선 입사한 지 한 달이 채 안되었을 때, 대용량의 데이터를 받아 localStorage에 조건에 맞게 저장하고 처리하는 로직을 프론트에서 모듈화하여 구현하는 작업을 했었는데 다른 작업이랑 병행을 하긴 했지만 그 작업이 최종 배포되기까지 3주간의 기간이 소요됐다.
아무리 그래도 왜그렇게 오래 걸렸나? 라고 물어본다면, 이미 작성되어 있는 코드들을 보고 이해하는 게 아직 낯설었고, 로직은 맞지만 코드의 퀄리티가 좋지 않은, 남이 읽기 힘든 코드였다. 부끄러운 얘기지만 내가 작성해놓고 내 코드를 이해하지 못하는 상황까지 갔었다.
코드의 퀄리티를 수정하고 나면 디버깅을 하면서 또 놓친 처리를 발견하게 되고, 수많은 버그의 늪에 빠졌었다. 물론 코드리뷰를 통해서 3주간 다듬어졌지만..
그 기간을 겪으면서, 왜 진작 이런 코드를 생각하지 못했을까? 왜 이 조건을 생각하지 못했지? 등의 생각이 아주 많이 엄청 많이 들었다.
내가 좀 더 논리적으로 파악했다면 훨씬 시간이 줄어들지 않았을까? 라는 생각에 알고리즘 공부의 필요성을 다시 한 번 느끼게 되는 계기가 되었는데, 여러가지 이유로 내 자신을 합리화하며 알고리즘 공부를 제대로 하지않았다.
그런데 그 이후로도 회사의 빠른 서비스 확장으로 인해, 프론트엔드의 대규모 업데이트가 계속 진행되고 있는데, 백엔드로 요청할 api의 데이터 형식을 정리한다던가 대용량 데이터를 어떻게 처리할 지, 어떤 예외 조건이 있을 지에 대한 생각을 할 때마다 역시 알고리즘과 자료구조를 안다면 삽질하는 시간을 줄일 수 있겠다라는 생각이 들었다.
그래서 ! 알고리즘 스터디를 시작하기로 했다.
문제 설명
유저가 입력한 아이디가 카카오의 아이디 요구사항과 일치하는 지 확인하고, 7단계의 처리 과정을 거친 추천 아이디를 만들어야 하는 문제이다.
풀이에 앞서 의사 코드를 먼저 작성했다.
의사코드(pseudo-code)
requirement
결과의 길이는 3자 이상, 15자 이하
알파벳 소문자 숫자, 빼기(-), 밑줄, 마침표(.)문자만 사용
접두 접미 (.) 불가, 이외의 경우 마침표(.)연속 사용 불가
// step
// result 변수 선언
1 - new_id의 모든 대문자를 대응되는 소문자로 치환합니다.
// string 프로토타입의 toLowerCase 메소드로 전체 문자열을 소문자로 변환 후 배열화(split 메소드)=> result 할당
2 - new_id에서 알파벳 소문자, 숫자, 빼기(-), 밑줄(_), 마침표(.)를 제외한 모든 문자를 제거합니다.
// 소문자 조건은 1단계에서 처리 완료
// 숫자, 빼기(-), 밑줄(_), 마침표(.)를 상수에 담고 상수에 포함된 것들만 필터링(filter 메소드) => result 할당
3 - new_id에서 마침표(.)가 2번 이상 연속된 부분을 하나의 마침표(.)로 치환합니다.
// filter 메소드를 사용해 중복 마침표 제거
// 만약, 현재 요소가 '.'가 아닐경우 포함
// 만약, 현재 요소가 '.'이고, 이전 요소가 '.'가 아닐 경우 포함
4 - new_id에서 마침표(.)가 처음이나 끝에 위치한다면 제거합니다.
// 6단계 마침표 필터링 재사용 필요, 함수로 구현
// id 배열을 인자로 받는 clearUnnecessaryDotElement 함수 선언
- 만약 id[0]이 '.'인 경우
// 배열을 인자로 받고, findIndex 메소드를 이용, '.'이 아닌 접두사 인덱스를 찾는다
// 찾은 접두사 인덱스로 slice하여 배열을 잘라낸다
- 만약 id[id.length - 1]이 '.'인 경우
// 위 조건에서 받은 result를 reverse로 뒤집고, 다시 위와 같은 처리를 반복한다
// 나온 결과를 뒤집어서 리턴하는 함수로 만든다 (접두, 접미 '.'가 모두 제거된 값)
두 조건 모두 동일 로직 활용, removeDot 함수 선언
// id 배열을 인자로 받는 removeDot 함수 선언
// findIndex 메소드로 "."을 갖고 있는 첫번째 인덱스를 찾는다.
// 인덱스가 있는 경우 slice 메소드로 배열을 자른다
// 없는 경우 [""]리턴
5 - new_id가 빈 문자열이라면, new_id에 "a"를 대입합니다.
// 만약 4단계를 거친 결과가 빈문자열이면 정답에 'a'를 할당
6 - new_id의 길이가 16자 이상이면, new_id의 첫 15개의 문자를 제외한 나머지 문자들을 모두 제거합니다.
만약 제거 후 마침표(.)가 new_id의 끝에 위치한다면 끝에 위치한 마침표(.) 문자를 제거합니다.
// 만약 결과의 길이가 16자 이상이면, 15개만 나올 수 있도록 slice로 잘라낸다
// 제거 후, 마지막 요소가 '.'이면 clearUnnecessaryDotElement 함수 다시 호출
7 - new_id의 길이가 2자 이하라면, new_id의 마지막 문자를 new_id의 길이가 3이 될 때까지 반복해서 끝에 붙입니다.
// while문을 통해 result의 마지막 요소를 추가해 result의 길이가 3이되면 리턴한다.
풀이
function removeDot(arr) {
const index = arr.findIndex((text) => text !== ".");
if (index > -1) {
return arr.slice(index);
}
return [""];
}
function clearUnnecessaryDotElement(id) {
let simplifyId = id; //array
if (simplifyId[0] === ".") {
simplifyId = removeDot(simplifyId);
}
if (simplifyId[simplifyId.length - 1] === ".") {
simplifyId = removeDot(simplifyId.reverse()).reverse();
}
return simplifyId;
}
function solution(new_id) {
const necessaryString = 'abcdefghijklmnopqrstuvwxyz0123456789-_.';
let result; // array
result = new_id.toLowerCase().split("");
result = result.filter((text)=> necessaryString.includes(text));
result = result.filter((text, idx) => {
if (text !== "." || text === "." && result[idx - 1] !== ".") {
return true;
}
})
result = clearUnnecessaryDotElement(result);
if (result.join("") === "") {
result = ["a"];
}
if (result.length >= 16) {
result = result.slice(0, 15);
if (result[result.length - 1] === ".") {
result = clearUnnecessaryDotElement(result);
}
}
if (result.length <= 2) {
const lastEl = result[result.length - 1];
while (result.length < 3) {
result.push(lastEl);
}
}
return result.join("");
}
o(n)의 시간복잡도를 가진 코드를 구현했다.
result 변수에 계속해서 재할당을 하는 코드가 괜찮은가에 대해서 생각을 해보았는데, array의 메소드들을 모두 연결해서 처리를 하게 되면 처리되는 단계에대한 파악이 어렵지않을까?라는 생각이 들어 각 단계마다 result 변수에 할당을 했다.
로직에 필터링 등이 있어 배열로 처리를 하면 좋겠다는 생각이 들어 배열타입으로 진행을 했는데 그것 때문에 오히려 복잡해보이는 코드가 된 것은 아닐까 생각이 좀 든다.
다른 식으로 데이터를 다뤘다면 좀 더 좋은 코드가 나올 수 있었을텐데 하는 아쉬움에 자료구조에 대한 공부의 필요성을 느꼈던 문제였다.
'Algorithm > Algorithm Study' 카테고리의 다른 글
[프로그래머스] lv3 2019 카카오 블라인드_ 추석 트래픽 (0) | 2021.07.18 |
---|---|
[프로그래머스] 2019 카카오 인턴쉽 _ 크레인 인형뽑기 (0) | 2021.07.18 |
[프로그래머스] 2020 KAKAO 블라인드 _ 문자열 압축 (0) | 2021.07.11 |
[프로그래머스] 2019 KAKAO 블라인드 _ 오픈 채팅방 (0) | 2021.07.11 |
[프로그래머스] 2021 KAKAO 블라인드 _ 메뉴 리뉴얼 (0) | 2021.07.04 |