[Problem Solving] 신규 아이디 추천

sean·2023년 1월 9일
0

Problem Solving

목록 보기
19/130

문제

카카오에 입사한 신입 개발자 네오는 "카카오계정개발팀"에 배치되어, 카카오 서비스에 가입하는 유저들의 아이디를 생성하는 업무를 담당하게 되었습니다. "네오"에게 주어진 첫 업무는 새로 가입하는 유저들이 카카오 아이디 규칙에 맞지 않는 아이디를 입력했을 때, 입력된 아이디와 유사하면서 규칙에 맞는 아이디를 추천해주는 프로그램을 개발하는 것입니다.
다음은 카카오 아이디의 규칙입니다.

  • 아이디의 길이는 3자 이상 15자 이하여야 합니다.
  • 아이디는 알파벳 소문자, 숫자, 빼기(-), 밑줄(_), 마침표(.) 문자만 사용할 수 있습니다.
  • 단, 마침표(.)는 처음과 끝에 사용할 수 없으며 또한 연속으로 사용할 수 없습니다.

"네오"는 다음과 같이 7단계의 순차적인 처리 과정을 통해 신규 유저가 입력한 아이디가 카카오 아이디 규칙에 맞는 지 검사하고 규칙에 맞지 않은 경우 규칙에 맞는 새로운 아이디를 추천해 주려고 합니다.
신규 유저가 입력한 아이디가 new_id 라고 한다면,

1단계 new_id의 모든 대문자를 대응되는 소문자로 치환합니다.
2단계 new_id에서 알파벳 소문자, 숫자, 빼기(-), 밑줄(_), 마침표(.)를 제외한 모든 문자를 제거합니다.
3단계 new_id에서 마침표(.)가 2번 이상 연속된 부분을 하나의 마침표(.)로 치환합니다.
4단계 new_id에서 마침표(.)가 처음이나 끝에 위치한다면 제거합니다.
5단계 new_id가 빈 문자열이라면, new_id에 "a"를 대입합니다.
6단계 new_id의 길이가 16자 이상이면, new_id의 첫 15개의 문자를 제외한 나머지 문자들을 모두 제거합니다.
     만약 제거 후 마침표(.)가 new_id의 끝에 위치한다면 끝에 위치한 마침표(.) 문자를 제거합니다.
7단계 new_id의 길이가 2자 이하라면, new_id의 마지막 문자를 new_id의 길이가 3이 될 때까지 반복해서 끝에 붙입니다.

예를 들어, new_id 값이 "...!@BaT#*..y.abcdefghijklm" 라면, 위 7단계를 거치고 나면 new_id는 아래와 같이 변경됩니다.

1단계 대문자 'B'와 'T'가 소문자 'b'와 't'로 바뀌었습니다.
"...!@BaT#*..y.abcdefghijklm""...!@bat#*..y.abcdefghijklm"

2단계 '!', '@', '#', '*' 문자가 제거되었습니다.
"...!@bat#*..y.abcdefghijklm""...bat..y.abcdefghijklm"

3단계 '...'와 '..' 가 '.'로 바뀌었습니다.
"...bat..y.abcdefghijklm"".bat.y.abcdefghijklm"

4단계 아이디의 처음에 위치한 '.'가 제거되었습니다.
".bat.y.abcdefghijklm""bat.y.abcdefghijklm"

5단계 아이디가 빈 문자열이 아니므로 변화가 없습니다.
"bat.y.abcdefghijklm""bat.y.abcdefghijklm"

6단계 아이디의 길이가 16자 이상이므로, 처음 15자를 제외한 나머지 문자들이 제거되었습니다.
"bat.y.abcdefghijklm""bat.y.abcdefghi"

7단계 아이디의 길이가 2자 이하가 아니므로 변화가 없습니다.
"bat.y.abcdefghi""bat.y.abcdefghi"

따라서 신규 유저가 입력한 new_id가 "...!@BaT#*..y.abcdefghijklm"일 때, 네오의 프로그램이 추천하는 새로운 아이디는 "bat.y.abcdefghi" 입니다.

신규 유저가 입력한 아이디를 나타내는 new_id가 매개변수로 주어질 때, "네오"가 설계한 7단계의 처리 과정을 거친 후의 추천 아이디를 return 하도록 solution 함수를 완성해 주세요.

머리 싸맨 이유

정규표현식이라는 방향을 놔두고 계속 ArrayString을 이용해서만 풀려고 했다. 다음과 같은 함수도 만들어가면서 문제를 풀려고 노력했다. (아스키코드 관련 함수)

function isValid(str, idx) {
    let ascii = str.charCodeAt(idx);
    let ch = str[idx];
    //알파벳 소문자인지
    if(ascii >= 97 && ascii <= 127)
        return true;
    //숫자인지
    if(ascii >= 48 && ascii <= 57)
        return true;
    //빼기(-), 밑줄(_), 마침표(.)인지
    if(ch === '-' || ch === '_' || ch === '.')
        return true;
    
    return false;
}

정규표현식을 활용해보자 (with 체이닝)

정규표현식을 한 번도 공부해 본 적이 없어서 다른 사람의 코드를 보면서 공부해보았다.

function solution(new_id) {
    var answer = new_id
        // Step1
        .toLowerCase()
        // Step2
        .replace(/[^\w-_.]/g, "")
        // Step3
        .replace(/\.+/g, ".")
        // Step4
        .replace(/^\.|\.$/g, "")
        // Step5
        .replace(/^$/, 'a')
        // Step6
        .slice(0, 15).replace(/\.$/g, "");

    // Step7
    let len = answer.length;
    if(len <= 2)
        answer = answer.concat(answer[len-1].repeat(3-len));

    return answer;
}

여기에 사용된 정규표현식 개념은 다음과 같다.
(출처: https://wikidocs.net/4308)

Javascript에서 정규식

1. 정규 표현식 리터럴
다음과 같이 슬래시로 패턴을 감싸서 작성합니다.
const re = /ab+c/

정규 표현식 리터럴은 스크립트를 불러올 때 컴파일되므로, 바뀔 일이 없는 패턴의 경우 리터럴을 사용하면 성능이 향상될 수 있습니다.

2. RegExp 객체의 생성자 호출
const re = new RegExp('ab+c')

생성자 함수를 사용하면 정규 표현식이 런타임에 컴파일됩니다. 바뀔 수 있는 패턴이나, 사용자 입력 등 외부 출처에서 가져오는 패턴의 경우 이렇게 사용하세요.

메타 문자

메타 문자(Meta characters)는 정규 표현식의 기본 개념으로, 다음과 같은 뜻을 가지고 있다.

💡메타 문자란, 원래 그 문자가 가진 뜻이 아닌 특별한 용도로 사용하는 문자이다.

메타 문자에는 다음과 같은 문자들이 있다.

. ^ $ * + ? { } [ ] \ | ( )

정규 표현식에 위 메타 문자를 사용하면 특별한 의미를 갖게 된다.

따라서, 예를 들어 마침표를 정말 문자 하나로써의 마침표로 사용하고 싶으면, 역슬래시(이스케이프)와 함께 붙여서 써주어야(\.) 마침표가 내포한 특별한 의미가 적용되지 않는다.

문자 클래스 ([])

문자 클래스도 메타 문자로써, 문자 클래스로 만들어진 정규식은 "[ ] 사이의 문자들과 매치"라는 의미를 갖는다.

문자 클래스를 만드는 메타 문자인 [ ] 사이에는 어떤 문자도 들어갈 수 있다.

즉 정규 표현식이 [abc]라면 이 표현식의 의미는 "a, b, c 중 한 개의 문자와 매치"를 뜻한다. 이해를 돕기 위해 문자열 "a", "before", "dude"가 정규식 [abc]와 어떻게 매치되는지 살펴보자.

  • "a"는 정규식과 일치하는 문자인 "a"가 있으므로 매치
  • "before"는 정규식과 일치하는 문자인 "b"가 있으므로 매치
  • "dude"는 정규식과 일치하는 문자인 a, b, c 중 어느 하나도 포함하고 있지 않으므로 매치되지 않음

조금 더 구체적인 예시를 살펴보면 다음과 같다.

  • 문자 클래스는 [ ] 내에 지정된 모든 문자들에서 한 문자와의 일치를 뜻함

    • ex) 0부터 9까지 숫자 1개를 찾는 문자 클래스는, [0123456789]
    • ex) 영어 소문자 모음 1개를 찾는 문자 클래스는, [aeiou]
  • 문자 클래스 내 범위 지정 : - (ASCII 코드 순서에 따름)

    • ex) [0-9] → 0부터 9까지 숫자
    • ex) [a-zA-Z] → 영문 알파벳 문자
      • 참고로, - 자체를 포함하려면, [-a-z] 또는 [a-z-] 처럼 맨앞이나 맨뒤에 적으면 됨
  • 문자 클래스 내 부정 : 선두 위치^를 쓰면 부정

    • ex) [^0-9A-Za-z] → 숫자나 영문 알파벳 이외의 모든 문자에 일치함
  • \w - 문자+숫자(alphanumeric)와 매치, [a-zA-Z0-9_]와 동일한 표현식이다.

여기까지의 개념을 알면 코드에서 Step2의 코드를 이해할 수 있다. 영소문자+숫자+-+_+.를 제외한 모든 문자는 제거한다는 코드이다. 해당 코드에서 뒤에 붙은 g라는 옵션은 전역에서 검색한다는 뜻이다.

  • 플래그 g는 ‘모든 문자를 검색하겠다’라는 의미로 사용된다.
  • 따라서 g가 없는 표현식은 하나의(최초의) 검색 결과만 반환하고, g가 있는 표현식은 모든 검색 결과를 배열로 반환한다.

수량 및 반복 표현

문자의 반복을 표현할 수 있는 방법이 있는데, 간단히 몇 가지만 다뤄보겠다.

  • * : * 바로 앞에 있는 문자가 0번부터 무한번 반복될 수 있다는 의미이다.
  • + : + 바로 앞에 있는 문자가 1번부터 무한번 반복될 수 있다는 의미이다.
  • {m} : {m} 바로 앞에 있는 문자가 정확히 m번 반복된다는 의미이다.
  • {m,n} : {m,n} 바로 앞에 있는 문자가 m번 이상 n번 이하 반복될 수 있다는 의미이다.

그래서 위의 코드 Step3에서 /\.+/g라고 작성한 것은, 문자열 전역에서 한 번 이상 .이 반복되는 패턴은 그냥 . 하나로 대체한다는 의미이다.

문장의 처음과 끝의 표현

문장(행)의 처음과 끝을 명시하는 표현은 다음과 같다.

  • ^ : 문자 클래스 내부에 쓰이면 부정 표현이 되지만, 원래 ^의 의미는 문장의 처음을 명시한다.
  • $ : 문장의 끝을 나타낸다.

그래서 위의 코드 Step4에서 /^\.|\.$/g라고 작성한 것은, 문장의 처음에 마침표가 나오는 경우 또는(|) 문장의 끝에 마침표가 나오는 경우를 전역으로 찾는다는 뜻이다.
마찬가지로, Step5에서는 빈 문자열을 나타내기 위해서 문장의 처음과 끝이 붙어있는 형태의 정규 표현식을 사용하여 나타내었다. (/^$/)

느낀 점

정규 표현식을 써 보니 그냥 쌩짜로 코드를 짜는 것보다 훨씬 수월했다. 앞으로 정규 표현식을 잘 활용할 수 있도록 노력해봐야겠다!!

profile
여러 프로젝트보다 하나라도 제대로, 깔끔하게.

0개의 댓글