Caesar - CS50x 2026

解決すべき問題
伝えられるところによれば、シーザー(あのシーザーです)は、各文字を一定の数だけずらすことで、機密メッセージを「暗号化」(つまり、元に戻せる方法で隠蔽)していたと言われています。例えば、AをB、BをC、CをDと書き、アルファベットを一周させてZをAと書くといった具合です。したがって、誰かに「HELLO」と伝えるために、シーザーは代わりに「IFMMP」と書いたかもしれません。このようなメッセージをシーザーから受け取った受信者は、同じ数だけ文字を逆方向にずらすことで、メッセージを「復号」する必要がありました。
この「暗号系」の機密性は、シーザーと受信者だけが秘密、つまりシーザーが文字をずらした数(例えば1)を知っているということに依存していました。現代の基準からすれば特に安全というわけではありませんが、もしあなたが世界で初めてこれを行った人物であれば、かなり安全だったことでしょう!
暗号化されていないテキストは一般に**平文(plaintext)と呼ばれます。暗号化されたテキストは一般に暗号文(ciphertext)と呼ばれます。そして、使用される秘密は鍵(key)**と呼ばれます。
明確にするために、鍵を \(1\) として HELLO を暗号化し、IFMMP が得られる様子を以下に示します。
| 平文 (plaintext) | H | E | L | L | O |
|---|---|---|---|---|---|
| + 鍵 (key) | \(1\) | \(1\) | \(1\) | \(1\) | \(1\) |
| = 暗号文 (ciphertext) | I | F | M | M | P |
より厳密に言えば、シーザーのアルゴリズム(つまり暗号)は、各文字を \(k\) ポジションだけ「回転」させることでメッセージを暗号化します。より形式的には、\(p\) をある平文(つまり暗号化されていないメッセージ)、\(p_i\) を \(p\) の \(i\) 番目の文字、\(k\) を秘密の鍵(つまり非負の整数)とすると、暗号文 \(c\) の各文字 \(c_i\) は次のように計算されます。
\[c_i = (p_i + k)\space\%\space26\]ここで \(\%\space26\) は「26で割った余り」を意味します。この公式は、暗号を実際よりも複雑に見せるかもしれませんが、アルゴリズムを正確に表現するための簡潔な方法にすぎません。実際、議論のために A(または a)を \(0\)、B(または b)を \(1\)、…、H(または h)を \(7\)、I(または i)を \(8\)、…、Z(または z)を \(25\) と考えてみてください。シーザーが今度は鍵 \(k\) を 3 として、誰かに機密扱いで Hi と伝えたいとします。この場合、彼の平文 \(p\) は Hi であり、平文の最初の文字 \(p_0\) は H(つまり 7)、2番目の文字 \(p_1\) は i(つまり 8)となります。したがって、彼の暗号文の最初の文字 \(c_0\) は K となり、2番目の文字 \(c_1\) は L となります。意味がわかりましたか?
caesar というフォルダ内の caesar.c というファイルに、シーザー暗号を使用してメッセージを暗号化できるプログラムを書いてください。ユーザーがプログラムを実行する際に、実行時に提供する秘密のメッセージの鍵が何であるかをコマンドライン引数で指定できるようにする必要があります。ユーザーの鍵が必ずしも数字であると仮定すべきではありませんが、もし数字であれば、正の整数であると仮定して構いません。
デモ
仕様
シーザー暗号を使用してメッセージを暗号化するプログラム caesar を設計し、実装してください。
caesarディレクトリ内のcaesar.cというファイルにプログラムを実装してください。- プログラムは、1つのコマンドライン引数(非負の整数)を受け取る必要があります。便宜上、これを \(k\) と呼びます。
- プログラムがコマンドライン引数なしで実行された場合、または2つ以上のコマンドライン引数で実行された場合、プログラムは(
printfで)任意のエラーメッセージを表示し、直ちにmainから1という値(通常はエラーを意味します)を返して終了する必要があります。 - コマンドライン引数の文字の中に1つでも10進法の数字でないものがある場合、プログラムは
Usage: ./caesar keyというメッセージを表示し、mainから1を返して終了する必要があります。 - \(k\) が 26 以下であると仮定しないでください。プログラムは \(2^{31} - 26\) 未満のすべての非負の整数値 \(k\) に対して動作する必要があります。言い換えれば、ユーザーが
intに収まらない、あるいは収まりきらないほど大きな値を \(k\) に選んでプログラムが最終的に壊れてしまっても、心配する必要はありません。(intはオーバーフローする可能性があることを思い出してください。)しかし、たとえ \(k\) が \(26\) より大きくても、プログラムの入力にあるアルファベットは、出力でもアルファベットのままである必要があります。例えば \(k\) が \(27\) の場合、asciitable.com によれば\はAから ASCII で 27 ポジション離れていますが、Aは\になるべきではありません。ZからAにラップアラウンドすることを考慮すると、BはAから 27 ポジション離れているため、AはBになるべきです。 - プログラムは
plaintext:(後ろにスペース2つ、改行なし)を出力し、ユーザーに平文のstringを(get_stringを使用して)入力させる必要があります。 - プログラムは
ciphertext:(後ろにスペース1つ、改行なし)を出力し、続いて平文に対応する暗号文を表示する必要があります。平文の各アルファベットは k ポジションだけ「回転」させ、アルファベット以外の文字は変更せずに出力する必要があります。 - プログラムは大文字・小文字を保持する必要があります。大文字は回転しても大文字のままでなければならず、小文字は回転しても小文字のままでなければなりません。
- 暗号文を出力した後、改行を出力する必要があります。その後、プログラムは
mainから0を返して終了する必要があります。
アドバイス
何から始めればよいでしょうか?この問題を一歩ずつ進めていきましょう。
擬似コード
まず、実際にはどう書くかまだ分からなくても、擬似コードだけを使ってプログラムを実装する main 関数を caesar.c に書いてみてください。
ヒント
これを行う方法は複数ありますが、そのうちの1つを以下に示します!
int main(int argc, string argv[])
{
// プログラムが1つのコマンドライン引数だけで実行されたことを確認する
// argv[1] のすべての文字が数字であることを確認する
// argv[1] を `string` から `int` に変換する
// ユーザーに平文の入力を求める
// 平文の各文字について:
// 文字がアルファベットであれば回転させる
}
こちらの擬似コードを見た後に自分の擬似コードを編集しても構いませんが、こちらのものを自分のものに単にコピー&ペーストしないでください!
コマンドライン引数のカウント
擬似コードがどうであれ、まずは追加の機能を加える前に、プログラムが単一のコマンドライン引数で実行されたかどうかをチェックする C コードだけを書いてみましょう。
具体的には、ユーザーがコマンドライン引数を提供しなかった場合、または2つ以上提供した場合、関数が "Usage: ./caesar key\n" を表示して 1 を返し、事実上プログラムを終了するように caesar.c の main を修正してください。ユーザーがちょうど1つのコマンドライン引数を提供した場合、プログラムは何も表示せずに単に 0 を返します。プログラムは以下のように動作するはずです。
$ ./caesar
Usage: ./caesar key
$ ./caesar 1 2 3
Usage: ./caesar key
$ ./caesar 1
ヒント
printfで出力できることを思い出してください。- 関数は
returnで値を返せることを思い出してください。 argcには、プログラム自身の名前に加えて、プログラムに渡されたコマンドライン引数の数が入っていることを思い出してください。
鍵のチェック
プログラムが(願わくば!)規定通りに入力を受け付けるようになったので、次のステップに進みます。
caesar.c の main の下に、例えば only_digits という名前の関数を追加してください。この関数は引数として string を取り、その string に 0 から 9 までの数字だけが含まれている場合は true を返し、それ以外の場合は false を返します。また、関数のプロトタイプを main の上に追加することも忘れないでください。
ヒント
おそらく、次のようなプロトタイプが必要になるでしょう:
bool only_digits(string s);コンパイラが
string(およびbool)を認識できるように、ファイルの冒頭でcs50.hをインクルードしてください。stringは単なるcharの配列であることを思い出してください。string.hで宣言されているstrlenは、stringの長さを計算することを思い出してください。manual.cs50.io にある、
ctype.hで宣言されているisdigitが役立つかもしれません。ただし、これは一度に1つのcharしかチェックしないことに注意してください!
次に、argv[1] に対して only_digits を呼び出すように main を修正してください。その関数が false を返した場合は、main は "Usage: ./caesar key\n" を表示して 1 を返すべきです。そうでない場合は、main は単に 0 を返すべきです。プログラムは以下のように動作するはずです。
$ ./caesar 42
$ ./caesar banana
Usage: ./caesar key
鍵の使用
次に、argv[1] を int に変換するように main を修正してください。manual.cs50.io にある、stdlib.h で宣言されている atoi が役立つかもしれません。そして、get_string を使用して、"plaintext: " というメッセージでユーザーに平文の入力を促します。
次に、例えば rotate という名前の関数を実装してください。この関数は char と int を入力として取り、その char が文字(つまりアルファベット)であれば、必要に応じて Z から A へ(および z から a へ)ラップアラウンドさせながら、その数だけ位置を回転させます。char が文字でない場合、関数は同じ char を変更せずに返す必要があります。
ヒント
おそらく、次のようなプロトタイプが必要になるでしょう:
char rotate(char c, int n);次のような関数呼び出し
rotate('A', 1)あるいは
rotate('A', 27)は、
'B'を返すべきです。また、次のような関数呼び出しrotate('!', 13)は、
'!'を返すべきです。(int)を使ってcharを明示的にintにキャストしたり、(char)を使ってintをcharにキャストしたりできることを思い出してください。あるいは、一方を他方として扱うことで、暗黙的にキャストすることもできます。計算を行う際、大文字から
'A'の ASCII 値を引いて、'A'を0、'B'を1というように扱うのがよいでしょう。そして、計算が終わったらそれを足し戻します。同様に、小文字から
'a'の ASCII 値を引いて、'a'を0、'b'を1というように扱うのがよいでしょう。そして、計算が終わったらそれを足し戻します。manual.cs50.io にある、
ctype.hで宣言されている他の関数が役立つかもしれません。25のような値から0へ算術的に「ラップアラウンド」させる際には、%が役立つでしょう。
次に、main を修正して "ciphertext: " を出力し、ユーザーの平文の各 char をループで処理し、それぞれに対して rotate を呼び出して、その戻り値を出力するようにします。
ヒント
printfは%cを使用してcharを出力できることを思い出してください。printfを呼び出しても出力が全く表示されない場合は、0 から 127 の有効な ASCII 範囲外の文字を出力している可能性があります。どのような値を出力しているかを確認するために、(%cの代わりに%iを使用して)文字を一時的に数値として出力してみてください!
ウォークスルー
テスト方法
正確性
check50 cs50/problems/2026/x/caesar
debug50 の使い方
debug50 を実行したいですか? make を使ってコードのコンパイルに成功した後、次のように実行できます。
debug50 ./caesar KEY
ここで KEY は、プログラムにコマンドライン引数として渡す鍵です。なお、次のように実行すると、
debug50 ./caesar
(理想的には!)プログラムはユーザーに鍵を要求して終了します。
スタイル
style50 caesar.c
提出方法
ターミナルで以下を実行し、表示されるプロンプトに従って回答して、作品を提出してください。
submit50 cs50/problems/2026/x/caesar