문제 풀러 가기

시저 암호 (Lv. 1)


문제

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

제한 조건

  • 공백은 아무리 밀어도 공백입니다.
  • s는 알파벳 소문자, 대문자, 공백으로만 이루어져 있습니다.
  • s의 길이는 8000이하입니다.
  • n은 1 이상, 25이하인 자연수입니다.

입출력 예

snresult
"AB"1"BC"
"z"1"a"
"a B z"4"e F d"

나의 풀이

문제의 이해

1. 문자열의 각 글자를 알파벳을 다음 n번째 알파벳으로 반환한다

2. n만큼 움직인 값이 Z 또는 z를 넘어가면 다시 A 또는 a부터 셈을 이어간다

3. 공백(space)도 엄연한 문자이며 공백은 데이터 변조 없이 그대로 반환된다

어떻게 알파벳 순서의 배열을 값으로 받아올 수 있을까?
Event Handler에 사용했던 keyCode 값처럼 알파벳의 값이 저장(또는 주소 지정)된 코드가 있을까?

String.prototype.charCodeAt()

charCodeAt 메소드는 문자열 중 특정 인덱스의 값을 UTF-16 코드로 반환해준다. 만약 알파벳의 UTF-16 코드가 순서대로 되어있다면 코드 값에 n을 더해주면 된다. 그렇다면 UTF-16 코드를 다시 문자로 반환하려면 어떻게 해야할까?

UTF-16 테이블은 다음 링크에서 확인할 수 있다
🔗 UTF-16 Table

String.fromCharCode()

UTF-16 코드를 인자로 전달하면 해당 문자를 반환하는 메소드이다.

위 두 메소드를 핵심 기능으로 코드를 만들어본다.

메소드 테스트

const upperCases = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
const lowerCases = 'abcdefghijklmnopqrstuvwxyz';

function solutionTest(phrase) {
  const arr = [];
  const arr2 = [];
  for (let i = 0; i < phrase.length; i++) {
    arr.push(phrase.charCodeAt(i));
    arr2.push(String.fromCharCode(phrase.charCodeAt(i + 1)));
    // let nextWord = phrase.charCodeAt(i+1);
    // arr2.push(nextWord)
  }
  return `${arr}\n${arr2.join('')}`;
}

console.log(solutionTest(upperCases));
// 65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90
// BCDEFGHIJKLMNOPQRSTUVWXYZ
console.log(solutionTest(lowerCases));
// 97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122
// bcdefghijklmnopqrstuvwxyz
  • UTF 코드로 잘 변환되고, UTF 코드에 값을 더해 문자로 반환도 잘 되는 것을 확인할 수 있다.
  • 하지만 대문자 Z와 소문자 z는 값이 제대로 반환되지 않는 것을 볼 수 있다. UTF 테이블을 보면 대문자의 범위는 65부터 90까지 소문자의 범위는 97부터 122까지인 것을 알 수 있다.
  • 따라서 코드값에 n을 더한 값이 대문자의 경우 90, 소문자의 경우 122을 넘어가면 다시 z -> a 코드 만큼 빼주어야 한다

기능 구현

const upperCases = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
const lowerCases = 'abcdefghijklmnopqrstuvwxyz';

function solution(str, num) {
  let temp = [];

  // 각 자리를 순회하며 charCode 알아냄
  for (let i = 0; i < str.length; i++) {
    temp.push(str.charCodeAt(i));
  }

  let newTemp = temp.map(e => {
    // 대문자는 65 ~ 90까지
    // 소문자는 97 ~ 122까지
    // 공백(space)은 32
    if (e === 32) return 32; // Guard Clause
    if (e >= 97 && e + num <= 122) {return e + num} // 소문자 걸러내기
    if (e + num > 90) {
      return e + num - 26;
    // default = charCode + num
    } else {
      return e + num;
    }
  });

  // fromCharCode로 charCode로부터 해당 문자를 받아옴
  return newTemp.map(e => {
    return String.fromCharCode(e)
  }).join('');
}
  • 빈 배열에 문자의 각 문자를 UTF 코드로 변환하여 임시 저장한다
  • 코드 배열에 대한 맵핑
    • 해당 값이 공백(Code: 32)일 경우 그대로 둔다
    • 해당 값이 97 이상, n값을 더한 값이 122 이하일 경우 코드 + n 값을 반환 (대부분의 소문자가 여기에 해당된다)
    • 해당 값에 n을 더한 값이 90을 넘을 경우 합계에서 26을 빼준다 (합계가 Z 또는 z를 넘을 경우 다시 A, a로 돌아가기 위함)
    • 그 외 값에 대해선 default로서 합계를 반환한다
  • 위 과정을 거친 코드 배열을 하나의 문자열로 변환 후 반환한다

완성

const testStr1 = 'LcxcUetkrv ku c Itgcv Ncpiwcig';
const testStr2 = 'Nz obnf jt Xpolppl Mff';

function solution(str, num) {
  let charCodes = [];
  for (let i = 0; i < str.length; i++) {
    charCodes.push(str.charCodeAt(i));
  }
  let result = charCodes.map(e => {
    if (e === 32) return e;
    if (e >= 97 && e + num <= 122) {
      return e + num
    } else if (e + num > 90) {
      return e + num - 26
    } else {
      return e + num
    }
  });
  return result.map(e => {
    return String.fromCharCode(e)
  }).join('');
}

// RESULT
console.log(solution(testStr1, -2));
// "JavaScript is a Great Language"
console.log(solution(testStr2, -1));
// "My name is Wonkook Lee"

대, 소문자, 공백 구분 때문에 조건문이 복잡해진 점이 아쉽다.
위 풀이는 Positive Movement만 해결할 수 있기 때문에 이동 값을 음수로 넣어 A에서 역으로 Z로 가는 Negative Movement는 구현되어있지 않다. (+10점)

profile
© 가치 지향 프론트엔드 개발자

0개의 댓글