シーザー暗号:文字コードをずらして出力する
TechFULで解いていた問題のメモです。
修正に手間どったので、どのように解決したかの経緯と、新しく学んだ知識を書きます。
問題:
シーザー暗号化という文字列の暗号化方法があります。
これはすべての文字を、ある整数値(key値)だけ文字コードをずらした文字に置き換えるというものです。
標準入力から文字列strと、key値keyが与えられます。
この条件でシーザー暗号化された文字列cipherを出力してください。(TechFULより引用)
条件:
key値は-10^9≦key≦10^9
以下の整数
strは半角a~z
で構成される0~256文字以下の文字列
負の整数を割った余りは負になる
ex)-17%4は1になる
最初に立てた方針:
・取得した文字に key%26 を足す
・'a'の文字コードより小さければ26を足す
・'z'の文字コードより大きければ26を引く
始め、char配列の宣言でコンパイルエラーが出ました。
以下のようなメッセージです。
main.c: In function 'main':
main.c:9:10: error: array size missing in 'str'
9 | char str[];
| ^~~
これは 配列strの要素数がなかった ことが原因でした。
後からscanf関数で文字列の長さが判明していない入力を受け取る場合でも、先に要素数を決めておく必要があります。
char配列の宣言には要素数が必要
char str[300];
(入力よりも大きい要素数で用意)
サンプルケースはこれでうまくいきましたが、全テストケースにはまだ不正解が出ていました。
//不正解になったコード
for (i = 0; strnlen(str, 258) > i; i++) {
str[i] += key; //str[i]の値が128以上になってしまう場合がある!
if (str[i] < 'a') {
str[i] += 26;
}
else if (str[i] > 'z') {
str[i] -= 26;
}
}
原因はchar型に格納できる値の範囲を考慮できていなかったことでした。
たとえば、'z'の文字コードは122であるため、key%26がとりうる最大値25が'z'に加算されると値は148となります。148は2進数8桁(2ビット)では表現しきれません。 符号付きchar型の最大値127を超えてしまい、間違った文字コードが代入されてしまう と思われます。
char型は2ビット(=1バイト)
10進数で0~127まで表現できる
ASCIIコード
ASCIIとは、2進数7桁で表せる整数値に文字や記号などが割り当てられた文字コードです。
0~127までの整数が割り当てられています。
文字 | 10進数 | 16進数 |
---|---|---|
NULL | 0 | 0x00 |
A | 65 | 0x41 |
Z | 90 | 0x5A |
a | 97 | 0x61 |
z | 122 | 0x7A |
大文字と小文字のコード差は必ず32になっているので、±32をすることで大文字と小文字の変換をすることができます。ASCIIコードについては以下のサイトを参照しました。
参照したページ:
http://www3.nit.ac.jp/~tamura/ex2/ascii.html
char 変数 = '文字';
でchar型の変数に文字コードを格納する
printf("%d\n", ch);
で文字コード(整数)を表示する
printf("%c\n", ch);
で文字コードに対応した文字を表示する
char配列の長さを調べる方法
strlen 関数を使います。
使用する場合は先頭に#include <string.h>
を記述する必要があります。
strnlen(char配列, 最大値)
strlen関数よりも安全。
NULLで終わらなくても最大値までしか読み込まないようにできる。
_mbstrlen(char配列)
日本語のかな文字などにも対応。
#include <locale.h>
#include <stdlib.h>
を先頭に追加する。
参照したページ:
https://programming.pc-note.net/c/mojiretsu2.html
全テストケース通ったときのコード:
#include <stdio.h>
#include <string.h>
int main()
{
//input
int key, i;
char str[257];
scanf("%d %s", &key, str);
key = key % 26;
for (i = 0; strnlen(str, 258) > i; i++) { //場合分けした後でstr[i]の値を操作する
if (str[i] + key < 'a') {
str[i] = str[i] + 26 + key;
}
else if (str[i] + key > 'z') {
str[i] = str[i] - 26 + key;
}
else {
str[i] = str[i] + key;
}
}
//output
printf("%s\n", str);
return 0;
}
2022/10/9実施
参照先:TechFUL