상세 컨텐츠

본문 제목

[TIL#26] Study] 코딩 테스트 10

개인 공부/코딩 테스트

by DK9 2023. 12. 21. 09:54

본문

1. 시저 암호

 '시저 암호' 풀이와 이를 리팩터링 한 과정을 기술하고자 한다.

 

어떤 문장의 각 알파벳을 일정한 거리만큼 밀어서 다른 알파벳으로 바꾸는 암호화 방식을 시저 암호라고 합니다. 예를 들어 "AB"는 1만큼 밀면 "BC"가 되고, 3만큼 밀면 "DE"가 됩니다. "z"는 1만큼 밀면 "a"가 됩니다. 문자열 s와 거리 n을 입력받아 s를 n만큼 민 암호문을 만드는 함수, solution을 완성해 보세요.

 

해결해야 할 문제는 다음과 같다.

  • 문자열과 문자열의 형변환을 다룸
  • 영어 대소문자를 구별
  • + n 을 했을 때 A~Z, a~z 라는 한정된 범위의 값을 반복
  • 코드의 속도

최초의 풀이

1 class Solution {
2     public String solution(String s, int n) {
3         char[] chars = s.toCharArray();
4         // 문자열 s를 문자 배열로 받음
5         StringBuilder sb = new StringBuilder();
6         // 속도를 위한 StringBuilder
7         
8         for (char A : chars) {		// 빈칸 출력을 포함하기 위한 향상된 for문 
9             if (A >= 65 && A <= 90) {		// 아스키 코드를 이용한 대문자를 구별
10                 A += n;		// n 만큼 밈
11                if (A > 90) {
12                   char B = (char) (65 + (A - 65)%26);
13                     sb.append(B);
14                     continue;
15                 }
16                 // 대문자의 범위를 벗어난 아스키코드 값을 %연산으로 반복되게 처리.
17             }
18             if (A >= 97 && A <= 122) {		// 아스키 코드를 이용한 소문자 구별
19                 A += n;
20                 if (A > 122) {
21                     char B = (char) (97 + (A - 97)%26);
22                     sb.append(B);
23                     continue;
24                 }
25                 // 소문자 범위를 벗어난 아스키코드 값을 %연산으로 반복되게 처리.
26             }
27             sb.append(A);
28         }
29         return sb.toString();
30     }
31 }

 

10 ~ 14번 라인, 19 ~ 23번 라인이 상당히 유사하여 이 부분을 고치기 위해서 함께 스터디하는 동료들과 약 1시간을 고민했다. 하지만 여러 시행착오를 겪었다.

 

class Solution {
    public String solution(String s, int n) {
        char[] chars = s.toCharArray();
        StringBuilder sb = new StringBuilder();

        for (char A : chars) {
            if (A >= 65 && A <= 90 || A >= 97 && A <= 122) {
                A += n;
                if (A > 122) {
                    char B = (char) (97 + (A - 97)%26);
                    sb.append(B);
                    continue;
                }
                if (A > 90) {
                    char B = (char) (65 + (A - 65)%26);
                    sb.append(B);
                    continue;
                }
            }
            sb.append(A);
        }
        return sb.toString();
    }
}

 

 이런 형태로 조건문을 합쳐서 처리하려고 했으나 뭔가 2%가 모자라서 번번이 실패했다. 반례는 소문자이다. 'a'의 ascii 코드는 97인데, 97이 오면 구분하지 못하고 바로 대문자의 범위를 벗어났을 때를 상정한 로직으로 들어가 버린다.

 

 계속 헤매다가 결국 '대사부 준영'님의 도움을 받아 중복된 코드를 처리하는 리팩터링을 했다.

 

    public String solution(String s, int n) {
        char[] chars = s.toCharArray();
        StringBuilder sb = new StringBuilder();

        for (char A : chars) {
            if (A == ' ') {		// 빈칸 처리
                sb.append(" ");
                continue;
            }
            char ch = ' ';		// 문자 ch 선언
            if (A >= 65 && A <= 90) {		// 대문자 경우
                ch = 'A';		// 문자 ch 는 대문자
            }
            if (A >= 97 && A <= 122) {		// 소문자 경우
                ch = 'a';		// 문자 ch 는 소문자
            }

            A += n;		// n 만큼 밈
            char B = (char) (ch + (A - ch) % 26);
            // 모든 경우에서 반복된 범위를 출력하게끔 처리
            sb.append(B);
        }
        return sb.toString();
    }

 

 코드를 보고 느꼈다. 아 멍청이. 애초에 조건은 묶을 수 없는데, 로직을 한 번에 묶어서 처리한다는 것에 매몰되어서 본질을 파악하지 못했다.

 최초 코드에서 리팩터링 하고자 했던 10 ~ 14번, 19 ~ 23번 라인은 딱 하나만 빼고 다 동일하다. 바로 대문자, 소문자가 다르다. 대소문자 부분 외의 나머지를 묶어서 처리할 수 있는 부분이지, 대소문자 부분은 중복된 코드가 아니다!

 

마무리

 이번 문제에서 얻어갈 점은 다음과 같다.

  • 로직을 짤 때, 공통된 것을 먼저 예외처리해라.
  • 반복되는 값을 출력해야하는 경우 %연산을 이용해라
  • 중복된 코드를 정리할 때, '중복된 부분'의 본질에 집중해라.

관련글 더보기