[프로그래머스] 2021 KAKAO BLIND _ 신규 아이디 추천
Algorithm/Algorithm Study

[프로그래머스] 2021 KAKAO BLIND _ 신규 아이디 추천

반응형

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 변수에 할당을 했다.

로직에 필터링 등이 있어 배열로 처리를 하면 좋겠다는 생각이 들어 배열타입으로 진행을 했는데 그것 때문에 오히려 복잡해보이는 코드가 된 것은 아닐까 생각이 좀 든다.

다른 식으로 데이터를 다뤘다면 좀 더 좋은 코드가 나올 수 있었을텐데 하는 아쉬움에 자료구조에 대한 공부의 필요성을 느꼈던 문제였다.

 

 

 

반응형