시작하기, 뭐든

코딩테스트 뿌시기 1 - 2021 KAKAO BLIND RECRUITMENT 신규 아이디 추천(정규식을 이용한 풀이) 본문

코딩테스트

코딩테스트 뿌시기 1 - 2021 KAKAO BLIND RECRUITMENT 신규 아이디 추천(정규식을 이용한 풀이)

Gascon 2022. 1. 4. 23:24

시작하기, 뭐든 - 기록 16일차

 

새로운 카테고리를 만들었다.

바로 "코딩테스트"!! 이제 당분간은 코딩테스트 뿌시기를 기록해야겠다.

거의 2년만에 자바를 만져보는데, 처음 자바 접했을 때의 그 설렘으로 다시 시작해보자!

 

오늘 풀어본 문제는 2021 KAKAO BLIND RECRUITMENT 신규 아이디 추천이다.

코딩테이스로 가장 유명한 프로그래머스에서 레벨1로 시작!

(로또의 최고 순위와 최저 순위도 풀긴 했는데 신규 아이디 추천부터 포스팅 시작할거니깐..)

 

각설하고, 해당 문제는 입력된 String 값에서 조건 7개 맞춰서 신규 아이디를 추천해주는 서비스를 만드는 것이다.

 

[신규 아이디 조건]
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이 될 때까지 반복해서 끝에 붙입니다.

이 문제를 읽고 처음에는 String을 char 배열에 한글자씩 넣어서 for문을 이용해 하나씩 전부 비교하려고 했다.

이게 바로 코딩테스트 카테고리를 만들어서 포스팅하게된 계기가 됐다.....

 

그런데 막상 char배열을 만들어서 Stirng값을 하나하나 넣고 나니, 더이상 진도가 안나가는 것이었다.

        char[] id_arr= new char[new_id.length()];
   
        for(int i=0 ; i<new_id.length(); i++ ) {
            id_arr[i] = new_id.toLowerCase().charAt(i);
            
            System.out.println(id_arr[i]);
        }

그럴수밖에.... 이 문제는 정규식으로 풀어내야 간단한건데 for문 돌려서 하나씩 점검하다가는 영어, 숫자, 특수문자 등등 고려해야할 것들이 많았다.

영어, 문자, 특수문자를 걸려내려면 정규식이 필요했는데 Pattern, Matcher 클래스들은 import를 해야 쓸 수 있었다.

이래저래 멘붕이 와있을 때 replaceAll 함수를 사용해 정규식으로 풀어야한다는 걸 알게됐다.

 

일단, 바로 풀이를 해보자.

1단계 소문자로 치환

//1단계 new_id의 모든 대문자를 대응되는 소문자로 치환합니다.
new_id = new_id.toLowerCase();
System.out.println("1단계 : "+new_id);

1단계는 매우 간단했다. toLowerCase() 함수를 사용하면 소문자로 변경할 수 있다.

 

2단계 기준에 맞지 않는 문자 제거

//2단계 new_id에서 알파벳 소문자, 숫자, 빼기(-), 밑줄(_), 마침표(.)를 제외한 모든 문자를 제거합니다.
new_id = new_id.replaceAll("[^a-z\\d\\-._]", "");
System.out.println("2단계 : "+new_id);

여기서부터 아주 매력적인 정규식이 나온다. (이 문제를 풀고 정규식 팬이 되었다.)

new_id = new_id.replaceAll(regex, replacement);

replaceAll함수는 인자로 regex(정규식), replacement(정규식 조건에 대상을 변경할 문자)를 가진다.

new_id = new_id.replaceAll("[^a-z\\d\\-._]", "");

이 코드는, [^a-z\\d\\-._] 정규식 대상에 맞는 문자를 ""로 지우라는 얘기를 담고 있다.

이 한줄로 해결되다니, 정말 너무 아름다울 수 없었다....(for문으로 하나씩 검사해서 어찌저찌 해보려는 생각은 얼마나 어리석었는가.)

 

그렇다면 정규식이 어떤 뜻을 담고 있는지를 알아볼 차례다.

정규식 정규식 의미
[ ] 정규식 조건의 범위를 나타낸다. 문자 사이의 "-"를 넣으면 문자 사이의 모든 문자들이 조건으로 들어간다.
ex) [a-z] : a부터 z까지 모든 문자가 정규식 대상 -> 영어 소문자를 찾을 때 사용하는 정규식 
^ 문자열의 시작을 나타낸다.
ex) ^[1] : 문자열 시작이 1인 경우

단, [ ] 안에서 맨 앞에 있을 경우 "포함하지 않음"을 나타낸다.
ex) [^a-z] : a부터 z까지 모든 문자를 포함하지 않는 경우 -> 영어 소문자를 제외하고 나머지를 찾을 때 사용하는 정규식
\d 숫자 0부터 9까지 정규식 표현과 동일
\d = [0-9]
\D 숫자를 제외한 문자를 찾는 정규식
\D = [^0-9]
\w 알파벳이나 숫자를 찾는 정규식
\w = [a-zA-Z0-9]
\W 알파벳이나 숫자를 제외한 문자를 찾는 정규식
\W = [^a-zA-Z0-9]

여기서 정규식이 얼마나 매력적인지 알 수 있다.

\\d를 써서 [0-9]를 줄였는데 \w를 써서 \\d뿐만 아니라 [a-z]마저도 줄일 수 있다!

new_id = new_id.replaceAll("[^\\w\\-._]", "");

 

3단계 연속된 마침표를 하나로 치환

//3단계 new_id에서 마침표(.)가 2번 이상 연속된 부분을 하나의 마침표(.)로 치환합니다.                                     
new_id = new_id.replaceAll("\\.{2,}", ".");
System.out.println("3단계 : "+new_id);
정규식 정규식 의미
{ } 회수 또는 범위
숫자 뒤에 ","가 들어가면 "이상"을 뜻함. 숫자 사이에 들어가면 범위를 뜻함.
ex) .{2,} : 임의의 문자를 2글자 이상 들어가야함.
     .{2,4} : 임의의 문자를 2글자에서 4글자 사이로 들어가야함.
. 임의의 한 문자

여기서 "\(백슬래시)"를 언급하고 가야한다.

정규식 표현에서는 "."은 임의의 한 문자를 나타낸다. 하지만 우리가 필요한건 "." 문자 그대로의 마침표를 찾아야 한다.

이때 사용하는게 \(백슬래시)다.

 

\(백슬래시)  뒤에 일반 문자가 오면 특수문자로 인식되고, \(백슬래시) 다음에 특수문자가 오면 문자 그 자체를 의미한다.

ex) .{2,} -> "."은 임의의 한 문자를 의미

    \\.{2,} -> "."은 문자 그대로 마침표를 의미

백슬래시 두번을 써야 마침표를 찾을 수 있다! 

 

4단계 마지막에 위치한 마침표 제거

//4단계 new_id에서 마침표(.)가 처음이나 끝에 위치한다면 제거합니다.                                    
new_id = new_id.replaceAll("^[.]|[.]$", "");
System.out.println("4단계 : "+new_id);
정규식 정규식 의미
[ ] 범위 안에 속한 한 개의 문자
ex) [abc] : a, b, c 중 만족하는 한 개의 문자
^ 문자열의 시작을 나타냄
| 정규식 패턴 안에서 or 연산을 나타냄
$ 문자열의 끝을 나타냄

앞에서 [ ]은 범위를 지정할 때 썼는데, 한 개의 문자를 찾을 때도 사용할 수 있다.

^[.] -> 하나의 문자 "."를 문자열 시작에 가지고 있다.

| -> 가지고 있거나

[.]$ -> 하나의 문자 "."를 문자열 마지막에 가지고 있다.

이 셋을 합치면, 4단계 조건도 완성!

 

5단계 문자열이 비었을 때 a 대입

//5단계 new_id가 빈 문자열이라면, new_id에 "a"를 대입합니다.
if(new_id.length() == 0) {
   new_id = "a";
}
System.out.println("5단계 : "+new_id);

5단계는 정규식이 필요없고, 아주 간단하므로 넘어가도록 한다.

 

6단계 문자열 길이제한 및 마지막에 오는 마침표 제거

//6단계 new_id의 길이가 16자 이상이면, new_id의 첫 15개의 문자를 제외한 나머지 문자들을 모두 제거합니다.
        만약 제거 후 마침표(.)가 new_id의 끝에 위치한다면 끝에 위치한 마침표(.) 문자를 제거합니다.
if(new_id.length() >= 16) {
   new_id = new_id.substring(0,15);
   new_id = new_id.replaceAll("[.]$", "");
}
System.out.println("6단계 : "+new_id);
System.out.println("6단계 : "+new_id.substring(new_id.length()-1));

길이제한은 substring으로 간단하게 제거해주고, 마지막 마침표는 4단계에서 했던걸 반토막해서 가져오면 된다.

정규식 하나로 이렇게 간편하다니..기가찰 노릇이다 정말

 

7단계 문자열 길이를 최소 3글자로 맞추기

//7단계 new_id의 길이가 2자 이하라면, new_id의 마지막 문자를 new_id의 길이가 3이 될 때까지 반복해서 끝에 붙입니다.
        if(new_id.length() <= 2) {
        	for(int i=0; i<2; i++) {
        		new_id = new_id.concat(new_id.substring(new_id.length()-1));
        		System.out.println("7단계 for: "+i+"번째 "+new_id);
        		if(new_id.length() ==3) {
        			break;
        		}
        	}
        }
        System.out.println("7단계 : "+new_id);

7단계 코드가..사실 제일 맘에 안든다. 더 깔끔하고 좋게 바꾸고 싶었는데 아직은 이대로 두었다.

정규식 포스팅이기도 하고, 지저분하지만 의도는? 설명하지 않아도 보이니 설명은 이만..

 

이대로 짜면 테스트는 무난히 통과할 수 있다.

비록 스스로 풀지 못해서 정규식을 공부하는 코딩테스트가 되었지만, 그래도 좋았다!

뭐든, 시작하는게 지금의 목표니깐!

 

끝!

(생각보다 포스팅이 오래걸려서 당황했다..)

Comments