第29問の答え

答え

pがループ変数になっているので、こう書くのが正解!
int SumListValues(const ListNode *pNode) {
  int sum = 0;
  for (const ListNode *p = pNode; p; p = p->pNext) {
    sum += p->value;
  }

  return sum;
}

解説

今回は、「ループ変数」を見極める練習問題でした。ループ変数とは、ループが継続する条件となる変数のことです。その値が一定の条件を満たす間だけ、値を更新しながら処理を繰り返します。

もっとも分かりやすいループ変数は、次のようなものでしょう。

  for (int i=0; i<10; i++) {
    ……
  }

ここでのループ変数はiです。条件(10より小さい)を満たす間だけ、値を更新(1ずつ増加)しながら処理を繰り返しています。

では、これから書き直そうとしているwhileループを確認してみましょう。

int SumListValues(const ListNode *pNode) {
  const ListNode *p = pNode;
  int sum = 0;

  while (p) {
    sum += p->value;
    p = p->pNext;
  }

  return sum;
}
どれがループ変数なのか、分かるかしら?
whileの括弧のところを見れば分かりそうだなー。
  while (p) {
    ……
  }
ズバリ、pじゃないですか?
そのとおり!ここまでは難しくないわね。
はい。大丈夫でした。
それじゃあ、これをどうやってforに書き換えるかってことなのだけど……
わくわく。
それにはwhileforの関係性を理解するのがいいわね。

forは、whileでループする際のよくあるパターンを書きやすく拡張したようなものです。多くの場合、whileループは次のような形をしています。

  ループ変数を初期化;
  while (ループ条件をチェック) {
    ……
    ループ変数を更新;
  }

これをforループで表現すると、次のようになります。

  for (ループ変数を初期化; ループ条件をチェック; ループ変数を更新) {
    ……
  }

このように、ループ変数に関する記述をまとめられるのがforの便利なところです。うまく使えば、プログラムを読みやすくできるでしょう。

ということは、このよくあるforループは……
  for (int i=0; i<10; i++) {
    ……
  }
このwhileループの変形だったのか!
  int i=0;
  while (i < 10) {
    ……
    i++;
  }
分かったみたいね!この2つが完全に同じ処理だとまではいわないけれど、やろうとしていることは一緒なのよ。
なるほど!って感じです。

この考え方を、問題のwhileループにあてはめてみましょう。ループ変数がpだということは、すでに分かっています。したがって、「ループ変数を初期化」「ループ条件をチェック」「ループ変数を更新」という3つの処理は、それぞれ次の部分だといえます。

int SumListValues(const ListNode *pNode) {
  const ListNode *p = pNode; /* ← ループ変数を初期化 */
  int sum = 0;

  while (p) { /* ← ループ条件をチェック */
    sum += p->value;
    p = p->pNext; /* ← ループ変数を更新 */
  }

  return sum;
}
ここまで見極められれば、あとはforに変形できるんじゃないかしら?
そうですね。できそうな気がします。
3つの処理をforの書き方に合わせて……っと。うーん、これでいいのかなぁ……
int SumListValues(const ListNode *pNode) {
  int sum = 0;
  for (const ListNode *p = pNode; p; p = p->pNext) {
    sum += p->value;
  }

  return sum;
}
そう!正解よ。
よかったぁ!あと、やっぱりプログラムが読みやすくなった気がしますね。
ループ変数に関係する部分が1行にまとまったし、行数も減ったものね。
あのー、1つ質問してもいいですか?
もちろん!
じつは、forの「ループ条件をチェック」のところが、これでいいのか自信がなかったんです。
pとだけ書いたところね。これは条件式なのよ。whileifなんかの括弧の中と同じ書き方ね。
じゃあ、ここにはどんな条件でも指定していいってことですか?
いいのよ。もしかして……今までi<10みたいな書き方しかしたことがなかったのかしら?
そうなんです!だって、forは範囲が決まってるときに使うループじゃないですか。
あー、そういうことね。それは逆なのよ。
逆……ですか?
forwhileと同じで、どんなループに使ってもいいの。ただ、あらかじめ範囲が決まっているループはforを使うと書きやすいっていうことね。
なるほどー。逆ってそういうことだったんですね。

さて、ここまではforループのおすすめの書き方を紹介しました。でも、ただwhileforに書き換えるというだけなら、別の方法もあります。

例えば、無限ループとbreakを組み合わせた、次のような書き方が考えられます。

  ループ変数を初期化;
  for (;;) { /* ← 無限ループ */
    if (ループ条件をチェック) {
      break;
    }
    ……
    ループ変数を更新;
  }

これは、「条件を満たす間だけ処理を繰り返す」のではなく、「条件を満たした時点で処理を終える」ということですね。そのため、「ループ条件をチェック」の部分に入る式は、whileの場合の否定形になります。

今回の問題にあてはめると、次のようになるでしょう。

int SumListValues(const ListNode *pNode) {
  const ListNode *p = pNode;
  int sum = 0;

  for (;;) {
    if (p == 0) {
      break;
    }

    sum += p->value;
    p = p->pNext;
  }

  return sum;
}
もともとのwhileより、行数が増えちゃいましたね。
そうね。それにパッと見たときに、どれがループ変数なのか判別しづらいんじゃないかしら。
そうですね。じゃあ、無限ループは使わないほうがいいってことですか?
そんなことはないのよ。breakの前後どちらにも処理が必要なときは、無限ループが便利ね。
つまり、こんな感じ。
  for (;;) {
    前半の処理:

    if (ループ条件をチェック) {
      break;
    }

    後半の処理;
  }
そっか。ループを抜ける条件を、処理の途中で判定したい場合もあるってことですね。
そういうこと。今回の問題では、その必要はなかったわね。

では、とにかく行数を減らすことを優先させたらどうなるでしょうか?例えば、次のように書くことも可能でしょう。

int SumListValues(const ListNode *pNode) {
  int sum = 0;
  for (const ListNode *p = pNode; p; sum += p->value, p = p->pNext);

  return sum;
}
ええっ!これでも同じ結果になるんですか?
そうなのよ。
中括弧がないですけど……
中括弧の中にあった処理は、カンマで区切って括弧の中に入れてあるでしょ。
あ、ほんとだ。それで行数が減ってるんですね。
そうね。でも、その分だけ読みやすくなったといえるかしら?
うーん。どういう処理をしているのかが、ちょっと分かりづらい気がします。
そうよね。頑張って1行にまとめようとしすぎて、かえって読みづらくなってしまった例といえるわね。
そうですねぇ。
ここがポイント!
ループを作るときは、どれがループ変数なのかを見極めよう!

修正後のプログラム

main.c
#include <stdio.h>
#include <stdlib.h>

typedef struct ListNode {
  int value;
  struct ListNode *pNext;
} ListNode;

int SumListValues(const ListNode *pNode) {
  int sum = 0;
  for (const ListNode *p = pNode; p; p = p->pNext) {
    sum += p->value;
  }

  return sum;
}

int main(void) {
  ListNode node4 = { 40, 0 };
  ListNode node3 = { 30, &node4 };
  ListNode node2 = { 20, &node3 };
  ListNode node1 = { 10, &node2 };

  printf("Sum: %d\n", SumListValues(&node1));

  return EXIT_SUCCESS;
}
実行結果
Sum: 100