본문 바로가기

Java

자바(java) 시저암호

어떤 문장의 각 알파벳을 일정한 거리만큼 밀어서 다른 알파벳으로 바꾸는 암호화 방식을 시저 암호라고 합니다.
A를 3만큼 밀면 D가 되고 z를 1만큼 밀면 a가 됩니다. 공백은 수정하지 않습니다.
보낼 문자열 s와 얼마나 밀지 알려주는 n을 입력받아 암호문을 만드는 caesar 함수를 완성해 보세요.
  • “a B z”,4를 입력받았다면 “e F d”를 리턴합니다.


​String형 변수 s를 char로 변환한 후, 아스키코드를 n만큼 밀면 되는 간단한 문제가 아닌가? 
라고 생각했다. 

처음 작성한 코드에서는 a B z, 4가 정상적으로 e F d로 변환되었지만 테스트용으로 나오는 "AsadFw WNezin dIwEndZ IoenNoje nVQa" 같은 형식에서는 작동하지 않았다.
시저암호라는 개념을 모르고 있었고 모든 테스트코드가 a B z처럼 한 글자와 공백 하나로 이루어진 것으로 착각했기 때문이다. 


알파벳은 소문자 26자, 대문자 26자로 이루어져 있다.
z를 1 밀었을때 도로 a가 된다면, (대문자 A가 되는 것은 아니다) 결국 n이 아무리 커도 26으로 나눈 나머지만 필요하다는 생각이 든다. 
a에서 27을 밀면 도로 a가 되어야 하기 때문이다. 

private String caesar(String str, int n) {
StringBuffer sb = new StringBuffer();
           n %= 26 ; 

String str을 char로 변환할 때 .toCharArray()로 배열에 담는 것과 반복문과 .charAt() 으로 일일이 찢는 방법이 있다.
어차피 글자마다 n만큼 밀어줘야 하는 과정을 거쳐야 하고 char[] array에 담아도 빼내는 과정을 거칠 것이므로 .charAt()을 사용했다.
그리고 ch에 n을 더했을 때 'z' 혹은 'Z'의 범위를 넘지 않는지 확인하기 위해 int ascii를 선언했다.

for (int i = 0; i < str.length(); i++) {
                char ch = str.charAt(i);

int ascii = (int)ch+n;

n을 26으로 나눈 값을 사용하고는 있지만 ascii가 'a'~'z'의 범위를 넘는 경우가 발생할 것이다. (n이 0~25이므로)
ascii<='a' && ascii >='z'  라고 조건을 선언해도 되지만, Character.isLowerCase(char) 라는 간편한 방법이 있다. 말 그대로 char가 소문자 알파벳인지 아닌지를 판별해 boolean 타입으로 리턴한다.
false가 나오면 26을 한 번 더 빼도록 하고, 대문자인 경우도 마찬가지로 해주었다.

 if(Character.isLowerCase(ch)) {
                     if(!Character.isLowerCase(ascii)) {
                           ascii-=26;
                     }
                }else if(Character.isUpperCase(ch)) {
                     if(!Character.isUpperCase(ascii)) {
                           ascii -=26;
                     }
                }
  
변경이 완료되었으니 추가만 하면 되는데 공백인 경우를 배제했다. 
공백인 경우는 ch에 n을 더한 ascii가 아닌 ch 그대로를 넣고, 공백이 아니라면 계산이 완료된 ascii를 넣으면 될 것이다. 
  if(ch!=32) {
                     sb.append((char)ascii);
                }else {
                     sb.append((char)ch);
                }



Caesar.java
public class Caesar {
     public static void main(String[] args) {
           Caesar cs = new Caesar();
           System.out.println(cs.caesar("a B z", 4));
           System.out.println(cs.caesar("AsadFw WNezin dIwEndZ IoenNoje nVQa",64));
     }
     private String caesar(String str, int n) {
           n %= 26 ; 

           StringBuffer sb = new StringBuffer();
           
           for (int i = 0; i < str.length(); i++) {
                char ch = str.charAt(i);
                
                int ascii = (int)ch+n;
                if(Character.isLowerCase(ch)) {
                     if(!Character.isLowerCase(ascii)) {
                           ascii-=26;
                     }
                }else if(Character.isUpperCase(ch)) {
                     if(!Character.isUpperCase(ascii)) {
                           ascii -=26;
                     }
                }
                if(ch!=32) {
                     sb.append((char)ascii);
                }else {
                     sb.append((char)ch);
                }
     }//for
           return sb.toString();
     }
}

결과

e F d
MempRi IZqluz pUiQzpL UaqzZavq zHCm


결국 n을 26으로 나눈 나머지로 사용하고 있으므로 중간에 한번 더 조건으로 판별하여 26을 빼는 과정이 낭비같다는 생각도 든다.
이리저리 'a'값과 n 값을 더하고 빼고 볶고 지지면 생략하고 단순 연산으로만 도출할 수 있을 것도 같다. 
아마 그럼 for문과 if 하나만으로 될 듯.