Programmierkurs
für Naturwissenschaftler/innen

繰り返し (ループ)

ここでは繰り返しについて説明する。

for ループ

何かを繰り返し実行したいことがよくある。

例えば、1 から 10 までの整数の三乗の和を求めたいとしよう。

let sum;
sum = 1 ** 3 + 2 ** 3 + 3 ** 3 + 4 ** 3 + 5 ** 3 + 6 ** 3 + 7 ** 3 + 8 ** 3 + 9 ** 3 + 10 ** 3;
console.log(sum);

と書けるが、間違いやすいし、1 から 100 までの場合を計算したい場合はさらに長くなり、間違いが混入する可能性が高くなる。 このような場合は for ループを使う (詳細は後で説明するので、今はざっと目を通してもらえれば良い)。

let sum = 0;	// 初期化を忘れないこと!

let i;
for (i = 1; i <= 10; i++) {
  sum += i ** 3; // sum = sum + i ** 3; でもよい
}
console.log(sum);

これをフローチャートとして書くと次のようになる。

/images/loop-for-sum.png
for-loop

上のプログラムとフローチャートを見比べて、どことどこが対応しているか確認せよ。

「上から下へ」の流れ以外に、前に戻る流れがあるのが分かる。 すなわちこれも「制御構造」の一つである。 また、菱形が出てきていることからも分かるように、本質的には条件分岐であることも分かるだろう。

ひとまず、簡単な例で体感してみよう。

次のプログラムを入力して実行してみよ。 詳細はすぐに説明するが、何をやっているか想像してみよ。 (先に説明を読んで後で試してみてもよい)

let i;
for (i = 0; i < 3; i++) {
  console.log(i);
}

実行結果

次のプログラムを入力して実行してみよ。 詳細はすぐに説明するが、何をやっているか想像してみよ。 (先に説明を読んで後で試してみてもよい)

let sum = 0;
let i;
for (i = 1; i <= 5; i++) {
  sum += i;
  console.log("  i =", i, ", sum =", sum);
}
console.log("Sum is", sum);

実行結果

for 文の構造

簡単な例で for 文の構造をきちんと見てみよう。

let i;
for (i = 0; i < 3; i++) {
  console.log(i);
}

for 文は

  1. for (という文字列)

  2. () で囲まれた部分

  3. ブロック ({} で囲まれた部分)

という構造になっている。

ブロックには実際の処理を書く。この部分が繰り返し実行される。

for 文で重要なのは () で囲まれた部分である。 () の中は前から順に、

  1. 初期化

  2. ループ条件

  3. 更新

という要素から構成されており、それぞれ ; で区切られている。 この ; は省略できない。 これらはそれぞれ以下のような役割を持っている。 それぞれが実行されるタイミングに注意 しよう。

初期化

  • for (i = 0; i < 3; i++) {

for 文を実行しはじめる前に行われる処理である。 まず最初に 一度だけ 実行される。 ループ変数 (ループの回数を数えるための一時的な変数。ループカウンターとも。ここの例では i) を初期化するのに使われる場合がほとんどである。

ループ条件

  • for (i = 0; i < 3; i++) {

ループする条件。これを満たしている間、ブロックを繰り返し実行する。 より正確に言うと、ブロックを実行する に評価し、その結果が真ならブロックを実行する。

更新

  • for (i = 0; i < 3; i++) {

ブロック実行 に実行される文である。 ループ変数を更新するのに使われる。

[補足]

なお、初期化、ループ条件、更新は不要なときは省略できる。 良くない書き方 だが、例えば以下のように書くこともできる。

let i = 1
for ( ; i < 3; i++) {	// ! 可能だが避けるべき書き方 !
  console.log("Hello, World");
}

この場合でも ; は省略できないので注意

小まとめ

まとめると、 for 文は、

for ([初期化]; [ループ条件]; [更新]) {
  処理
}

という構造になっており、このフローチャートは次のようになる。

/images/loop-for.png
for-loop

各ループは、

  1. ループ条件の評価

  2. ブロック実行

  3. 更新

が、この順番で繰り返されていることに注意しよう。

以下で、実際に確認してみよう。

下の Run をクリックして、次のプログラムを実行してみよ。 実行されている部分がマークされるので、どこがいつ実行されているか確認せよ。

実行結果

次の二問で、処理のタイミングをもう一度確認しよう。

次のプログラムを実行すると Hello, World は何回表示されるか? それはなぜか? 答を検討したら、実際に実行して確認してみよ。

let i;
for (i = 0; i < -1; i++) {
  console.log("Hello, World\n");
}

実行結果

次のプログラムを実行すると何が表示されるか? それはなぜか? 答を検討したら、実際に実行して確認してみよ。

let i;
for (i = 1; i <= 10; i++) {
  // 何もしない
}
console.log(i);

実行結果

ループ変数の宣言の場所

ここまでの例では

let i;
for (i = 0; i < 3; i++) {
  ...
}

// i はここ以降でも有効

のように書いてきた。

実際には以下のように書くことが多い。

for (let i = 0; i < 3; i++) {
  ...
}

// ここではもう i は使えない

このように書いたときは、 ifor ループ内でのみ使用できる 。 これについては スコープ で詳しく説明する。 一般的には変数の有効範囲は狭い方が良いので、原則として後者の方が望ましい書き方である。

ループ変数の初期値

10 回 Hello, World と出力するには、例えば、

for (let i = 0; i < 10; i++) {
  console.log("Hello, World");
}

と書ける。 これは

for (let i = 1; i <= 10; i++) {
  console.log("Hello, World");
}

とも書けるが、このような書きかたは (JavaScript や C 言語では) あまりしない。 慣例のようなものであるが、できればどちらの書き方にも慣れておきたい。

一方、和を求める計算では、

let sum = 0;
for (let i = 1; i <= 10; i++) {
  sum += i;
}
console.log(sum);

のように、 i は 1 から始まっている。

これらの大きな違いは、最初の例では i の値はブロック内では使われていない (ループの回数を数えるためのみに使われる) のに対して、 後者では i の値が実際に演算で使われているという点である。

for 文を使って、1 から 100 までの整数の和を求めるプログラムを書け。

実行結果

while ループ

for は繰り返し回数が重要であったが、繰り返し回数が事前に分からず、ある条件を満たしている間は繰り返したい、という場合がある。

例えば、1 からの整数の三乗を足していったとき、最初に 1000 を越えるのはいつかを知りたいとしよう。 この場合、事前に繰り返し回数は分からないので、for では書きにくい。 その場合、 while を使って次のように書くことができる。

let n = 0;
let sum = 0;
while (sum < 1000) {
  n++;
  sum += n ** 3;
  console.log(n, sum);
}
console.log("Result: ", n, sum);

上のプログラムを入力し、実行してみよ。 その際、それぞれの部分でなにをやっているか、想像してみよ。

実行結果

while 文の構造は次のようになっている。

while (ループ条件) {
  処理
}
/images/loop-while.png
while-loop

これは、まずループ条件を評価し、これが真だったらブロックが実行される。 for と同様に、まず条件が判定されるので、ブロックが一度も実行されない場合がある。

次のプログラムは 1 からの整数の二乗を足していったとき、最初に 10 を越えるのはいつかを求めるものである。

  1. 下の Run をクリックして、次のプログラムを実行してみよ。 実行されている部分がマークされるので、どこがいつ実行されているか確認せよ。

  2. このプログラムのフローチャートを描いてみよ

実行結果

上で「for では書きにくい」と書いたが、実は for ループでも書ける。

// ! 良くない書き方 !
let sum = 0;
for (let i = 1; sum < 1000; i++) {	// i と sum がまざっている
  sum += i ** 3;
}
console.log(sum);

しかし、これは良くない書き方である。 for の () の中が複数の変数で構成されていて、分かりにくい (後で見たとき勘違いしやすい)。 そもそも for と while の (英単語の) 意味を考えると、この場合は while の方が適切である。

「他の人が見て分かりやすい」プログラムを書くことは重要である。 「他の人が見て分かりやすい」ということは、自分が後で見たときに分かりやすいということでもあり、 そのようなプログラムはバグが少なくなるか、あっても発見しやすくなる。 「動けば良い」という方針で書かれたプログラムは、バグが入りやすい。

たとえその予定が無くても、他人に見られることを前提として分かりやすいプログラムを書こう。

ありがちな間違いについても見ておこう。

  1. 次のプログラムを入力し、実行せよ。 何が起きたか、何故そうなったか考えてみよ。

    let n = 50;
    
    while (n < 0) {
        n -= 10;
        console.log("in while:", n);
    }
    
    console.log("result:", n);
  2. もしこのプログラムの意図が「 n から 10 を引いていって、 n の値が負になったら終了する」だった場合、 どこをどのように修正すれば良いか検討せよ。 実際に修正して、期待通りに動作するか確認せよ。 (この場合、最後に result: -10 と表示されるのが、期待される結果である。)

実行結果

do-while ループ

do-while ループは while に非常に似ているが、少なくとも一回は実行される点が異なる。 使用頻度は比較的低い。

do {
  // 処理
} while ([ループ条件]);

これは、まずブロックが実行される。 ブロック実行後にループ条件が評価され、真だったらループを繰り返す。

/images/loop-do-while.png
do-while-loop

次のプログラムは $x$ の絶対値が 1 より小さくなるまで $x$ を 2 で割っていくプログラムである。

  1. 下の Run をクリックして、次のプログラムを実行してみよ。 実行されている部分がマークされるので、どこがいつ実行されているか確認せよ

  2. このプログラムのフローチャートを描いてみよ

実行結果

break

ループを途中で抜けたいときには、 break を使うことができる。 ただし break の使用は可能なら避けるのが望ましい。 使う前に、本当に必要であるか、より良い書き方が無いか良く考えてから使用するようにしよう。 break は for でも while でも do-while でも使用できる。

比較的良く使うパターンは次のものである。

ある繰り返し計算 (例えば ニュートン法 を用いた計算) をしていて、収束したら終了させたい場合、理論的には、

do {
  // 計算
} while (! converged);	// converged には具体的な条件を書く

と書ける。

しかし、収束するとは限らない場合 (大抵そうである)、最大の繰り返し回数を定めておいて、 その回数以内に収束しなかったら諦めるようにしたいということが良くある。 そのような場合、例えば、

let MAXLOOP = 100;

for (let i = 0; i < MAXLOOP; i++) {
  // 計算

  if (converged) {
    break;
  }
}

あるいは、ほぼ同じであるが、

let MAXLOOP = 100;

let i = 0;
do {
  // 計算

  if (converged) {
    break;
  }
  i++;
} while (i < MAXLOOP);

などと書ける。 これは、回数制限付き (ここでは 100 回) の do-while と見ることができる。

なお、入れ子の for/while/do-while (ループの中のループ) で break を使用した場合は、その break から見て 最も内側の ループを抜ける。

ループでの注意

ループを書く際には、終了条件に十分注意しよう。 ループが一回多かったり、あるいは一回少なかったりするのはよくあるミスである。 このようなミスもかなり見つけにくいことが多い。

また、常に成立する (絶対に不成立にならない) 条件を書くとループが終わらない無限ループとなる。

まとめ

ここでは、繰り返しの方法の基礎について学んだ。 繰り返しの為には

  • for

  • while

  • do-while

が使える。 また、これらで使える break について説明した。