基本情報技術者試験の良問に学ぶ、コードの裏側を読む「3つの思考法」
「基本情報技術者試験」と聞くと、資格取得のための知識を問うテスト、というイメージが強いかもしれません。しかし、その中には時として、単なる正解探し以上に、プログラミングの本質的な考え方を教えてくれる、驚くほど深い「パズル」が隠されています。
たった一つの試験問題が、実はエキスパートなプログラマが日常的に実践している思考のプロセスを解き明かす鍵になることがあります。この記事では、そんな良問を一つ取り上げ、皆さんと一緒に謎を解きながら、そこから学べる「コードの裏側を読むための3つの思考法」を丁寧に解説していきます。この思考法を身につければ、あなたがコードを読んだり書いたりするときの視点が、きっと変わるはずです。
思考法1:ゴールから逆算する ― 仕様としての単純なコード
まず、今回の挑戦状を見てみましょう。ここには2つの関数 function1 と function2 があります。問題のルールは、「これら2つの関数は、同じ引数を与えられたときに、必ず同じ結果を返さなければならない」というものです。
最初に注目すべきは、非常に素直に書かれた function1 のコードです。
* 整数型: function1(整数型: n, 整数型: m)
整数型: count ← 0
整数型: i
for (iをnからmまで1ずつ増やす)
if ((i mod 4)が0と等しい)
count ← count + 1
endif
endfor
return count
このコードの目的は非常に明快です。一言で言えば、**「nからmまでの範囲に含まれる4の倍数の個数を数える」**という処理をしています。
ここでの重要なポイントは、この単純な function1 が、単なるコードではなく、この問題における**「仕様」そのもの**であるということです。つまり、私たちのゴールは「4の倍数を数えること」だと、このコードが定義してくれているのです。
さて、ここからが本番です。function1 よりも賢く、効率的に同じ結果を出す function2 が以下のように与えられています。ただし、コードの一部が空欄 [ a ] と [ b ] になっています。
【問題】 function2 が function1 と全く同じ結果を返すように、空欄 [ a ] と [ b ] に入る正しい処理の組み合わせはどれでしょう?(なお、引数 m は引数 n よりも10以上大きい数とします。)
* 整数型: function2(整数型: n, 整数型: m)
整数型: count ← 0
整数型: tempN ← n
整数型: i, j
for ( [ a ] )
if ((tempN mod 4)が0と等しい)
繰返し処理を終了する
endif
tempN ← tempN + 1
endfor
for ( [ b ] )
count ← count + 1
endfor
return count
優れた問題解決者は、複雑な「どうやるか(how)」に飛び込む前に、まず「何をすべきか(what)」を正確に理解します。function1 が教えてくれた「what」を頼りに、このパズルを解き明かしていきましょう。
思考法2:準備と本番を分ける ―「最初の1つ」を見つけるためのループ
function2 の構造を見ると、2つの for ループに分かれていることがわかります。これは、処理を2つのステップに分けていることを示唆しています。最初の for ループの目的は何でしょうか?
このループは、本格的なカウント処理に入る前の**「準備」または「セットアップ」のステップです。ループの中では tempN の値を1ずつ増やし、tempN が4の倍数になったらループを抜けています。つまり、与えられた n 以上の数の中で「最初に見つかる4の倍数」**を探し出し、その値を tempN に保存しているのです。
では、この目的を達成するために、空欄 [ a ] にはどのようなループ条件を書けばよいでしょうか?
ここで重要な数学的な事実があります。それは**「連続する4つの整数の中には、必ず1つだけ4の倍数が存在する」**ということです。例えば、5, 6, 7, 8 の中には 8 が、10, 11, 12, 13 の中には 12 があります。
この性質を利用すると、n から始めて最初の4の倍数を見つけるには、最大でも n, n+1, n+2, n+3 の4つの数をチェックすれば十分だとわかります。 function2 のコードでは、まず tempN (初期値は n) をチェックし、それが4の倍数でなければループに入ります。ループの中では tempN を +1 していくので、あと最大3回チェックすれば必ず4の倍数にたどり着くはずです。
したがって、空欄 [ a ] に入るべきなのは、ループを最大3回繰り返す処理、すなわち iを1から3まで1ずつ増やす となります。
思考法3:1ずつ進むのではなく、4ずつ跳ぶ ― 問題の本質を見抜いた最適化
さて、最初のループで無事にスタート地点 (tempN、つまり n 以上の最初の4の倍数) を見つけることができました。いよいよ本番のカウント処理、空欄 [ b ] を考える番です。
function1 は、n から m までのすべての数を「1ずつ」進みながら、一つひとつ4の倍数かどうかをチェックする、いわば「総当たり(Brute Force)」のアプローチでした。しかし、function2 はもっと賢い方法を取ることができます。
私たちはすでに「最初の4の倍数」を知っています。では、その「次の」4の倍数はどこにあるでしょうか?当然、4つ先にあります。そのまた次も4つ先です。
この問題の本質は「4の倍数を数えること」です。であるならば、4の倍数でない数を一つひとつチェックするのは無駄な作業です。最初の4の倍数さえ見つけてしまえば、あとは4ずつジャンプしていくだけで、次の4の倍数にたどり着けるのです。
この洞察から、空欄 [ b ] に入るべき処理は jをtempNから始めてmを超えない範囲で4ずつ増やす であることがわかります。
この違いは、効率に大きな差を生みます。例えば n=1, m=100 の場合、function1 は100回のループとチェックを行います。一方、function2 は最初の4を見つけるために数回のチェックを行い、その後は 4, 8, 12, ... と約25回ジャンプするだけで済みます。これこそが「最適化」です。
以上の考察から、完成した function2 のコードは以下のようになります。
* 整数型: function2(整数型: n, 整数型: m)
整数型: count ← 0
整数型: tempN ← n
整数型: i, j
for ( iを1から3まで1ずつ増やす )
if ((tempN mod 4)が0と等しい)
繰返し処理を終了する
endif
tempN ← tempN + 1
endfor
for ( jをtempNから始めてmを超えない範囲で4ずつ増やす )
count ← count + 1
endfor
return count
結論
基本情報技術者試験の一つの設問を解き明かす旅から、私たちは3つの強力な思考法を学びました。
- ゴールから逆算する: まず単純なコードを「仕様」と捉え、達成すべき目的を明確にする。
- 準備と本番を分ける: 本格的な処理の前に、クリーンな開始点を見つけるための準備処理を独立させる。
- 根底のパターンを見抜く: 問題の本質を理解し、「1ずつ進む」のではなく「4ずつ跳ぶ」ような最適化を行う。
これらの思考法は、試験問題を解くだけでなく、日々のコーディングやシステム設計、さらにはプログラミング以外の問題解決においても非常に役立ちます。
最後に一つ、問いかけてみたいと思います。あなたの身の回りにある課題で、この「4ずつ跳ぶ」ような発想を使ってもっと賢く解決できそうなことはありませんか?