科目Bの関数・手続問題の解き方|引数と戻り値を表で追う
関数の中で別の関数が呼ばれた瞬間、どこまで読んだのか分からなくなる。
科目Bの問題で、呼び出し先の関数を読みに行ったまま、元の場所へ戻れなくなったことはありませんか?
1つの関数だけなら追えるのに、関数が2つ、3つと増えると急に混乱する。そんな悩みを持つ人は少なくないのではないでしょうか。
大丈夫です。関数の問題で必要なのは、呼び出すたびに新しいメモ用紙を1枚用意する、という考え方だけです。
私はエンジニアとして10年以上、Webシステムの開発や運用に携わってきました。実務のコードは、ほとんどが関数の呼び出しの積み重ねでできています。
不具合を調べるときも、どの関数にどんな値が渡され、何が返ってきたのかを順番に書き出していくのが基本です。この地道な作業は、科目Bの関数の問題を解く手順とまったく同じです。
この記事では、関数・手続の問題を、科目Bの解き方に合わせて5つの段階で紹介します。科目B全体の中で、こうした問題がどこに位置するのかを先に確かめたい人は、親記事から読んでおくと流れがつかみやすくなります。
【関連記事】基本情報技術者試験の科目Bとは?擬似言語・アルゴリズム対策を初心者向けに解説
関数・手続の問題は何を聞いているのか¶
最初に、問題文が何を求めているのかを整理しておきましょう。関数の問題で問われる内容は、大きく3つの型に分かれます。
その3つを表にまとめました。
| 問われ方 | 具体的な聞かれ方 | 主に見る場所 |
|---|---|---|
| 戻り値を答える | ある引数で呼び出したとき、返る値はどれか | return の行 |
| 空欄を埋める | 目的の値が返るように、空欄に入る式はどれか | 関数の目的と呼び出し側 |
| 呼び出し回数を答える | ある関数が何回呼ばれるか、何行目が何回実行されるか | 呼び出しの位置とループ |
どの型でも、最終的に知りたいのは、呼び出し側に何が戻ってくるかです。問題文を読んだら、まずどの型なのかを決めてからコードに入ると、読むべき場所が自然に絞れます。
関数と手続の違いを押さえる¶
擬似言語では、値を返すものを関数、値を返さないものを手続と呼び分けます。宣言の行で、○のあとに型が書いてあれば関数、型が無ければ手続です。
関数は、呼び出した場所に値を持ち帰ります。手続は値を持ち帰らない代わりに、配列の中身を書き換えたり、値を出力したりと、何かの作業をして終わります。
宣言の読み方や、引数と戻り値の基本をもう一度確かめたい場合は、こちらの記事で整理しておくと安心です。
【関連記事】擬似言語の手続と関数の違いとは?引数・戻り値と変数の範囲を初心者向けに解説
まず目を付ける場所¶
問われ方が分かったら、次はコードのどこから読むかを決めます。関数の問題では、目を付ける場所が3つあります。
ひとつ目は宣言の行、ふたつ目は return の行、みっつ目は呼び出している行です。この順番で見ていくと、関数の役割がはっきりします。
宣言の行で入口と出口を確かめる¶
最初に見るのは、関数の宣言の行です。ここには、何を受け取って何を返すのかが、すべて書かれています。
たとえば ○整数型: square(整数型: n) なら、整数を1つ受け取り、整数を1つ返す関数だと分かります。中身を読む前に入口と出口が分かるので、迷子になりにくくなります。
次に return の行を探しましょう。return が複数ある関数は、条件によって返す値が変わる、という合図です。
局所変数は呼び出しごとに新しく作られる¶
関数の中で宣言された変数は、局所変数と呼ばれます。局所変数は、その関数が呼ばれるたびに新しく作られ、関数が終わると消えます。
ここが、関数の問題でいちばん大事なポイントです。同じ関数を3回呼べば、局所変数も3回、初期値から作り直されます。
前の呼び出しで計算した値が、次の呼び出しに残っていると考えてしまうと、答えがずれます。呼び出しごとに新しいメモ用紙を使う、とイメージしておきましょう。
例題:関数の中で関数を呼ぶ¶
ここからは、実際の例題で追い方を確かめます。この記事の例題は説明のために作成したもので、IPA の公開問題そのものではありません。
次のコードには、2つの関数があります。square は受け取った値の2乗を返し、sumOfSquares は 1 から k までの2乗の合計を返します。
○整数型: square(整数型: n)
return n × n
○整数型: sumOfSquares(整数型: k)
整数型: total ← 0
整数型: i
for (i を 1 から k まで 1 ずつ増やす)
total ← total + square(i)
endfor
return total
sumOfSquares の for の中で、square が呼び出されています。では、sumOfSquares(3) を呼び出すと、戻り値はいくつになるでしょうか?
呼び出しの行は2段階で読む¶
total ← total + square(i) の行は、一度に読もうとすると混乱します。そこで、2段階に分けて読みましょう。
まず square(i) を呼び出して、戻ってきた値を手元に置きます。次に、その値を total に足して、total を書き換えます。
右辺の計算は、呼び出した関数が値を返してくるまで止まっている、と考えると分かりやすくなります。呼び出し先から戻ってきて初めて、足し算が続きから実行されるのです。
トレース表で呼び出しを追う¶
頭の中だけで関数を行き来すると、どの関数のどの行にいるのかを見失いがちです。そこで、呼び出し1回ごとに1行を使うトレース表を書きます。
ポイントは、呼び出した関数と引数、戻り値を、それぞれ別の列に書くことです。呼び出し側の変数と、呼び出された側の変数を混ぜないようにします。
| 順番 | 呼び出し側の i | 呼び出し | 引数 n | 戻り値 | 足したあとの total |
|---|---|---|---|---|---|
| 1 | 1 | square(1) | 1 | 1 | 1 |
| 2 | 2 | square(2) | 2 | 4 | 5 |
| 3 | 3 | square(3) | 3 | 9 | 14 |
3回目の呼び出しが終わると、i が k を超えるので for を抜けます。sumOfSquares(3) の戻り値は 14 です。
1、4、9 を足すと確かに14になります。表の結果と手計算が一致すれば、追い方は合っています。
呼び出しの深さを列で表す¶
関数が関数を呼び、その関数がさらに別の関数を呼ぶ問題もあります。そんなときは、表に深さの列を1つ足しておくと便利です。
深さ1が最初に呼ばれた関数、深さ2がその中から呼ばれた関数、という具合に番号を付けます。戻り値を書くときに深さを1つ戻せば、どこへ値を持ち帰るのかを間違えません。
関数が自分自身を呼び出す再帰も、この深さの考え方で追えます。再帰でつまずいている場合は、こちらの記事でじっくり確認してみてください。
【関連記事】基本情報科目Bの再帰呼び出しがわからない人への対策方法を解説
return は関数をその場で終わらせる¶
もう一つ、トレースで見落としやすいのが、ループの途中にある return です。return が実行されると、残りの行は一切実行されず、関数はその場で終わります。
次の関数は、配列 data の中から target を探し、見つかった位置を返します。見つからなければ -1 を返します。
○整数型: findIndex(整数型の配列: data, 整数型: target)
整数型: i
for (i を 1 から dataの要素数 まで 1 ずつ増やす)
if (data[i] が target と等しい)
return i
endif
endfor
return -1
data が {4, 7, 7, 2} で target が 7 なら、戻り値は 2 です。3番目にも 7 がありますが、2番目で return した時点でループは終わっているので、3番目は調べられません。
最初に見つかった位置を返すのか、最後に見つかった位置を返すのか。こうした違いは選択肢で狙われやすいので、return の位置には必ず印を付けておきましょう。
間違いやすい選択肢の見分け方¶
関数の問題では、読み方の小さな取り違えを狙った選択肢が並びます。どんな誤りが用意されやすいのかを知っておけば、選択肢を見た瞬間に候補を絞れます。
よくある誤りを表にまとめました。
| 誤りの種類 | 起きること | 見分け方 |
|---|---|---|
| 局所変数が前回の値を引き継ぐと考える | total が初期化されず、合計が大きくなりすぎる | 呼び出しごとに初期化の行を通るか確かめる |
| 引数と戻り値を取り違える | square(3) の答えを 3 と書いてしまう | 宣言の行で何を返す関数かを先に読む |
| return のあとも処理が続くと考える | 最後に見つかった位置を答えてしまう | return の行に印を付け、そこで表を止める |
| 内側の呼び出しを後回しにする | f(g(2)) で f を先に計算してしまう | 括弧の内側から順に値を確定させる |
| 同じ名前の変数を同じものと考える | 呼び出し側の n まで書き換わったと考える | 表の列を関数ごとに分けて書く |
この中でも、特によく狙われるのが局所変数の扱いと、入れ子になった呼び出しの順番です。順番に見ていきましょう。
同じ名前でも別の変数¶
呼び出し側にも呼び出された側にも、同じ n という名前の変数が出てくることがあります。名前が同じでも、それぞれの関数の中だけで使われる、別の変数です。
呼び出された側で n を書き換えても、呼び出し側の n は変わりません。表の列を関数ごとに分けて書いておけば、この取り違えは起こりにくくなります。
入れ子の呼び出しは内側から¶
square(square(2)) のような書き方では、まず内側の square(2) を計算して 4 を得ます。次に、その 4 を引数にして外側の square(4) を計算し、16 になります。
数学で括弧の内側から計算するのと同じ順番です。内側の値が確定するまで、外側の関数は呼び出されない、と覚えておきましょう。
引数に渡したのは値か、配列か¶
手続の問題では、配列を引数として渡し、手続の中でその要素を書き換える形がよく出てきます。呼び出したあとに配列がどう変わっているかが問われるので、手続の中で書き換えている行を必ず確認しましょう。
ただし、引数の受け渡しの細かい規則は、問題文の中で説明されることがほとんどです。思い込みで判断せず、問題文に書かれている約束に従って読むことが大切です。
トレースの途中で値が合わなくなったときの探し方は、こちらの記事でまとめています。
【関連記事】擬似言語の答えが合わないときの直し方|値がずれた場所の探し方
Giji Academy のシミュレーターで動かす¶
紙のトレース表で動きをつかんだら、最後は実際に動かして確かめましょう。関数の呼び出しのように、読む場所があちこちへ飛ぶ処理ほど、動かして見る効果は大きくなります。
Giji Academy の擬似言語シミュレーターでは、擬似言語のコードを1行ずつ実行し、変数の値が変わる様子を目で確かめられます。自分で書いたトレース表と見比べながら進めると、どの呼び出しで予想とずれたのかがすぐに分かります。
試すときは、この記事の sumOfSquares を少しずつ変えてみるのがおすすめです。k を 4 や 5 に変えたり、square の中身を n × n × n に変えたりすると、戻り値がどう変わるかを予想してから動かしてみてください。
予想と結果が一致すれば、その読み方は身についています。ずれた場合は、トレース表のどの行で違いが出たのかを探すことが、いちばんの練習になります。
まずはGiji Academy の擬似言語シミュレーターで講座を選び、関数を呼び出したときに値が受け渡される様子を確かめてみてください。
まとめ¶
科目Bの関数・手続の問題は、複雑な仕組みを問うものではありません。呼び出しごとに新しいメモ用紙を用意し、引数と戻り値を表に書いていけば必ず解けます。
この記事でお伝えした5つの段階を振り返っておきましょう。
| 段階 | やること |
|---|---|
| 1 設問を読む | 戻り値・空欄・呼び出し回数のどれが問われているかを決める |
| 2 目を付ける | 宣言の行、return の行、呼び出している行を順に見る |
| 3 トレースする | 呼び出し1回を1行にして、引数と戻り値を別の列に書く |
| 4 選択肢を見分ける | 局所変数の初期化、return の位置、入れ子の順番を確かめる |
| 5 動かす | 引数や中身を変えたコードをシミュレーターで動かす |
最初は、この記事の例題のように、関数が2つだけの短いコードで十分です。呼び出しの行で一度止まり、戻り値を書いてから続きを読む練習を繰り返すうちに、関数が増えても落ち着いて追えるようになります。
関数の読み方は、科目Bだけでなく、実務でコードを読むときにもそのまま役立つ力です。次に関数がいくつも出てくる問題に出会ったら、まずは宣言の行を指でなぞるところから始めてみてください。