문제

구름이는 친구에게 전달할 편지의 내용을 숨기기 위해서 새로운 암호 방법을 고안했다. 암호는 문자열과 암호/복호화를 위한 토큰으로 이루어져 있다. 토큰 앞에 붙는 E 혹은 D표시를 통해서 암호화 혹은 복호화를 진행해야 한다.

 

구름이가 만든 암호화 과정은 다음과 같다.

 

●토큰을 문자열과 같은 길이로 맞춘다. 만약에 토큰의 길이가 문자열보다 작다면, 토큰을 반복하여 늘릴 수 있다.

●문자열의 값이 알파벳 대소문자인 경우 토큰의 ASCII 코드 값만큼 shift한다. shift는 알파벳의 다음 글자로 바뀌는 연산을 의미하며 'Z' 혹은 'z'인 경우 'A' 혹은 'a'로 바뀐다.

●ASCII 코드에서 'A'~'Z'는 65~90, 'a'~'z'는 97~122이며 '0'~'9'는 48~57이다. 

 

복호화는 암호화 과정을 반대로 하여 진행할 수 있다.

 

구름이가 편지를 편하게 작성할 수 있도록 여러 개의 문자열과 'E' 혹은 'D' 표시 및 토큰이 주어질 때 각각 조건에 맞추어 암호화/복호화를 진행한 결과를 출력하시오.


입력

첫 줄에 테스트 케이스의 수  T(1<=T<=100,000)이 주어진다.

각 테스트 케이스는 두 줄로 이루어지는데, 첫 줄에 변환할 문자열 S가 주어진다.

두 번째 줄에 표시 값('E' or 'D')와 토큰이 공백을 두고 주어진다.

토큰은 알파벳 대소문자와 숫자로만 이루어진 문자열이다.

모든 S길이의 합과 토큰 길이의 합은 1,000,000을 넘지 않는다.


바로 문제를 풀 순 있었지만 하나의 함수로 최대한 암호화와 복호화를 한꺼번에 처리해 줄 수 있는 로직을 생각하기 위해 시간을 조금 투자했다. 

 

암호화의 경우 아래와 같은 쉬프트 방향성을 가지고

->--------------------------------------------------------------------->

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

 

복호화의 경우 암호화와 반대 방향으로 쉬프트를 진행 해야 한다.

<---------------------------------------------------------------------<-

A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

 

->방향성을 가지는 암호와의 경우 모듈러 연산을 해주면 마지막 문자인 Z를 A로 변환 시켜줄 수 있지만

<-방향성을 가지는 복호와의 경우 조금 복잡해 질 수 있다.

따라서 Z부터 시작하는 reverse 알파벳 문자열을 생성해주어 복호화 또한 ->방향성을 가질 수 있도록 해주었다.

(alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ", rAlphaber = "ZYXWVUTSRQPONMLKJIHGFEDCBA")

 

구현한 전체 코드는 아래와 같다.

#include <iostream>
#include <string>
#include <algorithm>
using namespace std;

int t;
string input, token, alphabet = "abcdefghijklmnopqrstuvwxyz", rAlphabet;
char type;

void convert() {
	for (int i = 0; i < input.length(); i++) {
    		//2-1
		char key = token[i % token.length()];
		int shift = key;
		int idx;
		if ('A' <= input[i] && input[i] <= 'Z') {
        		//2-2
			idx = type == 'E' ? input[i] - 'A' : 25 - (input[i] - 'A');
			idx = (idx + shift) % 26;
            	//2-3
			if (type == 'E') input[i] = alphabet[idx] - 32;
			else input[i] = rAlphabet[idx] - 32;
		}
		else if ('a' <= input[i] && input[i] <= 'z') {
			idx = type == 'E' ? input[i] - 'a' : 25 - (input[i] - 'a');
			idx = (idx + shift) % 26;
			if (type == 'E') input[i] = alphabet[idx];
			else input[i] = rAlphabet[idx];
		}
	}
}

int main() {
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	cin >> t;
	string temp = alphabet;
	reverse(temp.begin(), temp.end());
	rAlphabet = temp;
	while (t--) {
    		//1
		getline(cin, temp); //t를 입력받고 남아있는 엔터 제거
		getline(cin, input);
		cin >> type >> token;
		convert();
		cout << input << "\n";
	}
	return 0;
}

 

1.공백을 포함한 문자열 입력받기

먼저 변환할 문자열 S를 공백과 함께 입력받기 위해 getline함수를 사용했다.

이때, getline함수는 개행문자를 종료조건으로 인식하기 때문에 테스트케이스 개수 t를 입력 받고 남아 있는 개행문자를getline(cin, temp)로 제거해 주어야 sring형 input 변수에 정상적으로 공백을 포함한 입력을 받을 수 있다.

 

2.문자열 변환

2-1)token

문제에서 토큰을 문자열과 같은 길이로 맞춰줘야 한다고 설명하고 있다.

하지만 나는 토큰의 길이로 모듈러연산을 해주어 굳이 토큰 자체를 늘려주지 않도록 해주었다.

 

2-2)암/복호화 구분

먼저, if 조건문을 통해 알파벳 대/소문자에 대한 처리를 구분해 주었다.

이후, 각 문자가 몇번 째 알파벳인지 idx를 구해야 한다.

암호화의 경우 A는 첫 번재 알파벳이지만, 복호화의 경우 마지막 마지막 알파벳이어야 한다.

이 점에 유의하여 삼항 연산자를 통해 암호와/복호화 상황에 맞게 몇 번째 알파벳인지에 대한 idx인지를 구하도록 하였다.

idx찾았다면 암호화 시 alphabet에서 복호화시 rAlphabet에서 변환될 문자를 찾아주면 된다.

 

2-3)대문자 처리

소스 코드에서는 alphabet을 소문자 알파벳으로 구성해 주었기 때문에 대문자의 경우 32를 뺴주어야 한다.

이는 컴퓨터가 실제로는 문자를 정수의 아스키코드 값으로 구분한다는 것을 알고 있다면 쉽게 이해할 수 있을 것이다.

(A~Z : 65~90, a~z : 97~112)