Welcome! Everything is fine.

[프로그래머스/Lv.1] 신규 아이디 추천(2021 KAKAO BLIND RECRUITMENT) - Java 본문

프로그래머스/Lv.1

[프로그래머스/Lv.1] 신규 아이디 추천(2021 KAKAO BLIND RECRUITMENT) - Java

개발곰발 2024. 4. 19.
728x90

📌 문제

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

📌 풀이

1단계 - new_id의 모든 대문자를 대응되는 소문자로 치환한다.

new_id = new_id.toLowerCase();

 

2단계 - new_id에서 알파벳 소문자, 숫자, 빼기(-), 밑줄(_), 마침표(.)를 제외한 모든 문자를 제거한다.

2단계는 new_id를 문자 하나하나 돌면서 알파벳 소문자, 숫자, 빼기(-), 밑줄(_), 마침표(.)에 해당되지 않으면 지웠다.

 for (Character c : new_id.toCharArray()) {
    if (!(c >= 'a' && c <= 'z') && // 알파벳 소문자가 아니라면
       !(Character.isDigit(c)) && // 숫자가 아니라면
       c != '-' && c != '_' && c != '.') { // 빼기(-), 밑줄(_), 마침표(.)가 아니라면
        new_id = new_id.replace(c.toString(), "");
    }
}

 

다른 코드를 보니 replaceAll과 정규표현식으로 한 줄에 담을 수 있었다.

new_id = new_id.replaceAll("[^a-z0-9._-]", "");

 

정규표현식 [^a-z0-9._-] 에서

  • [ ] 는 문자 선택을 표현하며, 괄호 안에 들어간 것들 중 하나를 의미한다.
  • ^ 는 NOT을 표현하며 뒤에 나온 것들을 제외한 문자를 의미한다.
  • a-z나 0-9에서 - 는 range를 표현하며 a ~ z 사이의 문자, 0 ~ 9 사이의 숫자를 의미한다.
  •  ._- 는 빼기(-), 밑줄(_), 마침표(.)를 의미한다. 앞에 ^를 붙였으므로 이 문자에 포함되지 않는 문자를 의미한다.

 

3단계 - new_id에서 마침표(.)가 2번 이상 연속된 부분을 하나의 마침표(.)로 치환한다.

new_id = new_id.replaceAll("[.]{2,}", ".");

 

정규표현식 [.]{2,} 에서

  • [ ] 는 문자 선택을 표현하며, 괄호 안에 들어간 것들 중 하나를 의미한다. 마침표(.)는 [ ] 로 묶어주어야 한다.
  • [.]{2,} 는 앞에 나온 문자 마침표(.)가 2번 이상 반복됨을 의미한다.

 

4단계 - new_id에서 마침표(.)가 처음이나 끝에 위치한다면 제거한다.

new_id의 처음인 new_id.charAt(0)이 마침표(.) 이거나 new_id의 끝인 new_id.charAt(new_id.length() - 1) 이 마침표(.0라면 substring을 이용해 해당 부분을 잘라낸다. 주의할 점은 substring 사용시 인덱스 범위를 벗어나면 에러가 나므로 문자열이 비어있지 않은지 미리 체크해줘야 한다는 점이다.

if (!new_id.isEmpty() && new_id.charAt(0) == '.') {
    new_id = new_id.substring(1);
}

if (!new_id.isEmpty() && new_id.charAt(new_id.length() - 1) == '.') {
    new_id = new_id.substring(0, new_id.length() - 1);
}

 

이 부분 역시 정규표현식으로 표현할 수 있었다.

new_id = new_id.replaceAll("^[.][.]$", "");

 

정규표현식 ^[.][.]$ 에서

  • ^[.] 는 문자열의 시작을 말하며, 마침표(.)로 시작됨을 의미한다.
  • [.]$ 는 문자열의 종료를 말하며, 마침표(.)로 종료됨을 의미한다.

5단계 - new_id가 빈 문자열이라면, new_id에 "a"를 대입한다.

if (new_id.equals("")) new_id = "a";

 

혹은 삼항연산자로도 표현할 수 있다.

new_id = new_id.isEmpty() ? "a" : new_id;

 

6단계 - new_id의 길이가 16자 이상이면, new_id의 첫 15개의 문자를 제외한 나머지 문자들을 모두 제거한다. 만약 제거 후 마침표(.)가 new_id의 끝에 위치한다면 끝에 위치한 마침표(.) 문자를 제거한다.

 

7단계  - new_id의 길이가 2자 이하라면, new_id의 마지막 문자를 new_id의 길이가 3이 될 때까지 반복해서 끝에 붙입니다.

 

6, 7단계를 합쳐 작성한 코드이다. 지금 보니 굳이 else-if 다음에 while문이 위치하지 않고 while문만 사용해도 될 것 같다. 또 문자열 연산 시 StringBuilder를 사용하면 더 빨라질 것이다. 또한 마침표(.)가 new_id의 끝에 위치할 경우에 마침요(.)를 제거하는 코드도 정규표현식을 사용했다면 더 깔끔하고 가독성 있는 코드가 될 수 있을 것이다.

if (new_id.length() >= 16) {
    new_id = new_id.substring(0, 15);
} else if (new_id.length() <= 2) {
    while (new_id.length() < 3) {
        new_id += new_id.charAt(new_id.length() - 1);
    }
}

// 마침표(.)가 new_id의 끝에 위치할 경우
if (new_id.charAt(new_id.length() - 1) == '.') {
    new_id = new_id.substring(0, new_id.length() - 1);
}

📌 전체 코드

나의 코드

나의 코드는 정답이긴 하지만 아주 순차지향적인 코드이다. 다른 코드를 보고 나니 더 깔끔하고 가독성있게 작성할 수 있다는 사실을 알게되었다.

class Solution {
    public String solution(String new_id) {
        // 1단계
        new_id = new_id.toLowerCase();
        
        // 2단계
        for (Character c : new_id.toCharArray()) {
            if (!(c >= 'a' && c <= 'z') &&
               !(Character.isDigit(c)) &&
               c != '-' && c != '_' && c != '.') {
                new_id = new_id.replace(c.toString(), "");
            }
        }
        // 3단계
        new_id = new_id.replaceAll("[.]{2,}", "."); // []로 .을 감싸주어야 함
        
        // 4단계
        if (!new_id.isEmpty() && new_id.charAt(0) == '.') {
            new_id = new_id.substring(1);
        }
        if (!new_id.isEmpty() && new_id.charAt(new_id.length() - 1) == '.') {
            new_id = new_id.substring(0, new_id.length() - 1);
        }
        
        // 5단계
        if (new_id.equals("")) new_id = "a";
        
        // 6, 7단계
        if (new_id.length() >= 16) {
            new_id = new_id.substring(0, 15);
        } else if (new_id.length() <= 2) {
            while (new_id.length() < 3) {
                new_id += new_id.charAt(new_id.length() - 1);
            }
        }
         
        if (new_id.charAt(new_id.length() - 1) == '.') {
            new_id = new_id.substring(0, new_id.length() - 1);
        }
            
        return new_id;
    }
}

다른 사람의 코드

내가 아주 깔끔하다고 생각한 코드이다. 찾아보니 빌더 패턴(Builder Pattern)이라고 하는데, 객체의 생성 과정과 표현 방법을 분리해 인스턴스를 만드는 생성 패턴이다. 확실히 이렇게 작성하면 보는 입장에서 훨씬 이해가 잘 되고 나중에 유지보수하기도 쉬울 것 같다.

class Solution {
    public String solution(String new_id) {

        String s = new KAKAOID(new_id)
                .replaceToLowerCase()
                .filter()
                .toSingleDot()
                .noStartEndDot()
                .noBlank()
                .noGreaterThan16()
                .noLessThan2()
                .getResult();


        return s;
    }

    private static class KAKAOID {
        private String s;

        KAKAOID(String s) {
            this.s = s;
        }

        private KAKAOID replaceToLowerCase() {
            s = s.toLowerCase();
            return this;
        }

        private KAKAOID filter() {
            s = s.replaceAll("[^a-z0-9._-]", "");
            return this;
        }

        private KAKAOID toSingleDot() {
            s = s.replaceAll("[.]{2,}", ".");
            return this;
        }

        private KAKAOID noStartEndDot() {
            s = s.replaceAll("^[.]|[.]$", "");
            return this;
        }

        private KAKAOID noBlank() {
            s = s.isEmpty() ? "a" : s;
            return this;
        }

        private KAKAOID noGreaterThan16() {
            if (s.length() >= 16) {
                s = s.substring(0, 15);
            }
            s = s.replaceAll("[.]$", "");
            return this;
        }

        private KAKAOID noLessThan2() {
            StringBuilder sBuilder = new StringBuilder(s);
            while (sBuilder.length() <= 2) {
                sBuilder.append(sBuilder.charAt(sBuilder.length() - 1));
            }
            s = sBuilder.toString();
            return this;
        }

        private String getResult() {
            return s;
        }
    }
}

 

나의 코드와 다른 사람의 코드를 비교해보니 다른 사람의 코드가 더 빨랐다. 좀 더 효율적인 코드를 많이 보고 배워야겠다.

나의 코드(왼) / 다른 사람의 코드(오)