ループ: while と for

私たちはしばしば連続して何度も似たような処理を実行する必要があります。

例えば、1つずつリストから商品を出力する必要がある場合、または単に1から10の数値それぞれに同じコードを実行する場合などです。

ループ は複数回コードの同じ部分を繰り返す方法です。

“while” ループ

while ループは次の構文を持っています:

while (condition) {
  // code
  // いわゆる "ループ本体"
}

conditiontrue の間、ループの本体の code が実行されます。

例えば、下のループは i < 3 の間、i を出力します:

let i = 0;
while (i < 3) { // 0, 次に 1, 次に 2 を表示
  alert( i );
  i++;
}

ループ本体の1回の実行は イテレーション と呼ばれます。上の例のループは3回イテレーションします。

もしも、上の例に i++ がない場合、ループは (理論上は) 永遠に繰り返されます。実際には、ブラウザはこのようなループを止める方法を提供しており、サーバサイドJavaScriptではそのプロセスを殺すことができます。

任意の式または変数は、単に比較ではなくループ条件になります。それらは評価され while によって真偽値に変換されます。

たとえば、while (i != 0) をより短く書く方法としてwhile (i)があります:

let i = 3;
while (i) { // i が 0 になったとき、条件が偽になり、ループが止まります
  alert( i );
  i--;
}
本体が1行の場合、括弧は必須ではありません

ループの本体が1つの文である場合、括弧{…}を省略することができます:

let i = 3;
while (i) alert(i--);

“do…while” ループ

条件チェックは do..while 構文を使うことでループ本体の 下に 移動させることができます。:

do {
  // loop body
} while (condition);

ループは最初に本体を実行します、次に条件をチェックし、真となる間実行を繰り返します。

例:

let i = 0;
do {
  alert( i );
  i++;
} while (i < 3);

この構文の形式は、条件が真になるかどうかに関わらず、少なくとも1度 はループ本体を実行したい場合を除き、ほとんど使われません。通常は他の形式が好まれます: while(…) {…}

“for” ループ

for ループは最も使われるものの1つです。

このようになります:

for (begin; condition; step) {
  // ... loop body ...
}

例でこれらのパーツの意味を学びましょう。下のループは 0 から 3 まで(3 は含みません)、i に対して alert(i) を実行します。:

for (let i = 0; i < 3; i++) { // 0, 次に 1, 次に 2 を表示
  alert(i);
}

for 文を部分的に調べてみましょう:

パート
begin i = 0 ループに入ると1度実行されます。
condition i < 3 すべてのループのイテレーションの前にチェックされます、もしも false の場合ループを停止します。
step i++ 各イテレーションで、条件チェックの前に本体の後に実行されます。
body alert(i) 条件が真の間繰り返し実行されます。

一般的なループアルゴリズムは次のように動作します:

begin を実行
→ (if condition → body を実行し step を実行)
→ (if condition → body を実行し step を実行)
→ (if condition → body を実行し step を実行)
→ ...

もし初めてループを使う場合は、上の例に戻って、紙の上でステップ毎にどのように動作するかを再現してみると理解しやすいでしょう。

これが今のケースで正確に起こっていることです:

// for (let i = 0; i < 3; i++) alert(i)

// begin を実行
let i = 0
// if condition → body を実行し step を実行
if (i < 3) { alert(i); i++ }
// if condition → body を実行し step を実行
if (i < 3) { alert(i); i++ }
// if condition → body を実行し step を実行
if (i < 3) { alert(i); i++ }
// ...終わり, 今 i == 3 なので
インライン変数宣言

ここで “カウンタ” 変数 i はループの中で正しく宣言されます。それは “インライン” 変数宣言と呼ばれます。 このような変数はループの中でだけ見えます。

for (let i = 0; i < 3; i++) {
  alert(i); // 0, 1, 2
}
alert(i); // エラー, そのような変数はありません

変数を宣言する代わりに、存在するもの使うことも出来ます:

let i = 0;

for (i = 0; i < 3; i++) { // 既存の変数を使用
  alert(i); // 0, 1, 2
}

alert(i); // 3, ループの外で宣言されているので見える

一部分の省略

for の任意の一部分を省略することができます。 例えば、ループの最初で何もする必要がなければ、begin を省略することができます。

このように:

let i = 0; // すでに i を宣言し代入済み

for (; i < 3; i++) { // "begin" 不要
  alert( i ); // 0, 1, 2
}

同じように step パートも除去することができます。:

let i = 0;

for (; i < 3;) {
  alert( i++ );
}

ループは while (i < 3) と同じになりました。

実際にはすべてを除くこともできます。それは無限ループになります:

for (;;) {
  // 制限なしで繰り返し
}

for の2つのセミコロン ; は必須であることに注意してください。ない場合は構文エラーになります。

ループの終わり

通常、ループは条件が偽になると終了します。

しかし、いつでもループから出ることを強制することができます。そのための特別な break ディレクティブがあります。

例えば、以下のループはユーザに一連の数字を入力するよう求めますが、文字以外が入力されたとき “中断” します。:

let sum = 0;

while (true) {

  let value = +prompt("Enter a number", '');

  if (!value) break; // (*)

  sum += value;

}
alert( 'Sum: ' + sum );

もしもユーザが空を入力、もしくは入力をキャンセルした場合、break ディレクティブは行 (*) で有効になります。 それはループをすぐに停止し、ループ後の最初の行へ制御を渡します。つまり、alert です。

"必要に応じた無限ループ + break" の組み合わせは、ループの最初/最後ではなく、その間や本体の様々な場所で条件をチェックする必要がある状況で最適です。

次のイテレーションに進む

continue ディレクティブは break の “軽量版” です。ループ全体はストップしません。その代わりに、現在のイテレーションを停止し、新しいイテレーションのスタートを強制します(もしも条件が真であれば)。

現在のイテレーションが完了し、次へ移動したいときに使います。

以下のループは奇数値だけを出力するために continue を使用しています:

for (let i = 0; i < 10; i++) {

  // true の場合、本体の残りのパートをスキップ
  if (i % 2 == 0) continue;

  alert(i); // 1, 次に 3, 5, 7, 9
}

i の値が偶数の場合、continue ディレクティブは本体の実行を停止し、for の次のイテレーションへ制御を渡します(次の番号で)。 したがって、alert は奇数値に対してのみ実行されます。

ディレクティブ continue を使うと入れ子のレベルを減らせます

奇数値を表示するループはこのように書くこともできます:

for (let i = 0; i < 10; i++) {

  if (i % 2) {
    alert( i );
  }

}

技術的な観点からは、これは上の例と同じです。確かに、continue の代わりに if ブロックでコードをラップするだけです。

しかし、副作用として括弧のネストが1段深くなります。もしも if の中のコードが長い場合、全体の可読性が下がる可能性があります。

? の右側には break/continue を入れないでください

式ではない構文構造は、 '?' の中では使うことはできないことに注意してください。特に、ディレクティブ break/continue はそこでは許可されません。

例えば、このコードを考えます

if (i > 5) {
  alert(i);
} else {
  continue;
}

…これを、疑問符を使って書き直します:

(i > 5) ? alert(i) : continue; // continue はここでは使えません

…それは動作を停止します。このようなコードは構文エラーになります:

これは if の代わりに疑問符演算子 '?' を使用しない別の理由です。

break/continue のためのラベル

時々、複数のネストしたループから一度で抜け出す必要があることがあります。

例えば、下のコードでは 座標 (i, j)(0,0) から (3,3) へプロンプトするよう ij をループします:

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

  for (let j = 0; j < 3; j++) {

    let input = prompt(`Value at coords (${i},${j})`, '');

    // 仮にここで終了して下にある Done をしたい場合にはどうすればよいでしょう?

  }
}

alert('Done!');

ユーザが入力をキャンセルした場合、処理をストップする方法が必要です。

input の後の通常の break は内部ループのみの終了です。それだけでは十分ではありません。 ここでラベルが助けにきます。

ラベル は、ループの前のコロンがついた識別子です:

labelName: for (...) {
  ...
}

ループの中の break <labelName> 文はラベルまで終了にします。

次のようになります:

outer: for (let i = 0; i < 3; i++) {

  for (let j = 0; j < 3; j++) {

    let input = prompt(`Value at coords (${i},${j})`, '');

    // 文字から文字またはキャンセルされた場合、両方のループから抜ける
    if (!input) break outer; // (*)

    // 値に何かをする処理...
  }
}
alert('Done!');

上のコードで、break outerouter と名付けされたラベルを上に探し、そのループを抜けます。

そのため、制御は (*) から alert('Done!') にまっすぐに進みます。

ラベルを別の行に移動させることもできます:

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

continue ディレクティブもラベルと一緒に使うことができます。このケースでは、実行はラベル付けされたループの次のイテレーションにジャンプします。

ラベルは “goto” ではありません

ラベルはコードの任意の場所にジャンプすることはできません。

例えば、このようにすることは出来ません:

break label;  // label にジャンプ? いいえ。

label: for (...)

break/continue の呼び出しはループの中からだけ可能です。またラベルはそのディレクティブから上のどこかにある必要があります。

サマリ

3つのループの種類について説明しました:

  • while – 条件は各イテレーションの前にチェックされます。
  • do..while – 条件は各イテレーションの後にチェックされます。
  • for (;;) – 条件は各イテレーションの前にチェックされ、追加の設定ができます。

“無限” ループを作るために、通常は while(true) 構造が使われます。このようなループは他の他のループと同様に break ディレクティブで停止することができます。

もしも現在のイテレーションで何もしたくなく、次のイテレーションに進みたい場合は、continue ディレクティブを使います。

break/continue はループの前のラベルをサポートします。ラベルは、ネストを抜けて外のループに行くための、 break/continue のための方法です。

タスク

重要性: 3

このコードで最後にアラートされる値は何でしょう?それはなぜでしょう?

let i = 3;

while (i) {
  alert( i-- );
}

答え: 1.

let i = 3;

while (i) {
  alert( i-- );
}

各ループイテレーションは i1 減らします。チェック while(i)i = 0 のときにループを停止します。

従って、ループのステップは次のシーケンスを形成します。:

let i = 3;

alert(i--); // 3 を表示, i を 2 に減らす

alert(i--) // 2 を表示, i を 1 に減らす

alert(i--) // 1 を表示, i を 0 に減らす

// 完了。while(i)チェックでループが停止します。
重要性: 4

各ループで、どの値が表示されるか、あなたの意見を書きなさい。また、それと答えを見比べてみてください。

両方のループは同じ数だけ alert されますか?それとも違いますか?

  1. プレフィックス形式 ++i:

    let i = 0;
    while (++i < 5) alert( i );
  2. ポストフィックス形式 i++

    let i = 0;
    while (i++ < 5) alert( i );

このタスクは、ポストフィックス/サフィックス形式を比較で使ったときに、どのように異なる結果に繋がるかを示します。

  1. 1 から 4

    let i = 0;
    while (++i < 5) alert( i );

    最初の値は i=1 です。なぜなら、i++ は最初に i をインクリメントし、新しい値を返します。なので、最初の比較は 1 < 5 で、alert1 を表示します。

    次に、2,3,4… に続きます – 値は次々に表示されます。比較は常にインクリメントされた値を使います。なぜなら ++ は変数の前にあるからです。

    最終的に、i=4 では 5 にインクリメントされ、比較 while(5 < 5) が偽になりループが停止します。なので、5 は表示されません。

  2. 1 から 5

    let i = 0;
    while (i++ < 5) alert( i );

    最初の値は再び i=1 です。i++ のポストフィックス形式は i をインクリメントし、古い 値を返します。なので、比較 i++ < 5i=0 を使います (++i < 5 とは逆です)。

    しかし、alert 呼び出しは別です。インクリメントと比較の後に実行される別の文なので、現在の i=1 を使います。

    そして 2,3,4… に続きます。

    i=4 で止めてみましょう。プレフィックス形式 ++i はインクリメントし、比較では 5 を使います。しかし、ここではポストフィックスです。なので、i5 にインクリメントしますが、古い値を返します。従って、比較は実際 while(4 < 5) – true です。そして制御は alert に行きます。

    i=5 は最後です。なぜなら次のステップ while(5 < 5) は偽になるからです。

重要性: 4

各ループでどの値が表示されるか書き留めてください。そして答えと比較してください。

両ループ同じ値を alert しますか?それとも違いますか?

  1. ポストフィックス形式:

    for (let i = 0; i < 5; i++) alert( i );
  2. プレフィックス形式:

    for (let i = 0; i < 5; ++i) alert( i );

答え: どちらも場合も 0 から 4 です

for (let i = 0; i < 5; ++i) alert( i );

for (let i = 0; i < 5; i++) alert( i );

これは for のアルゴリズムから簡単に差し引くことができます:

  1. すべての前(最初)に i = 0 を一度実行します。
  2. 条件 i < 5 をチェックします。
  3. もし true なら – ループ本体 alert(i) を実行し、i++ します。

インクリメント i++ は条件チェック (2) とは分離されています。それは単に別の文です。

インクリメントによって返された値はここでは使われていません。なので、 i++++i の間に違いはありません。

重要性: 5

for ループを使って 2 から 10 までの偶数を出力してください。

デモを実行

for (let i = 2; i <= 10; i++) {
  if (i % 2 == 0) {
    alert( i );
  }
}

ここでは、残りを取得するための “剰余” 演算子 % を使って偶数のチェックをしています。

重要性: 5

その振る舞いを変えず(出力は同じまま)に、for ループから while にコードを書き換えてください。

for (let i = 0; i < 3; i++) {
  alert( `number ${i}!` );
}
let i = 0;
while (i < 3) {
  alert( `number ${i}!` );
  i++;
}
重要性: 5

100 より大きい数値を入力するプロンプトを書いてください。もし訪問者が別の数値を入力したら – 再び入力を訪ねます。

ループは、訪問者が 100 より大きい値を入力するか、入力をキャンセル/空行の入力をするまで訪ねます。

ここでは、訪問者は数値のみを入力すると仮定します。このタスクでは、非数値に対する特別な処理を実装する必要はありません。

デモを実行

let num;

do {
  num = prompt("Enter a number greater than 100?", 0);
} while (num <= 100 && num);

ループ do..while は両方のチェックが真になるまで繰り返します。:

  1. num <= 100 のチェック – つまり、入力値がまだ 100 よりも大きくない。
  2. && num チェックは、numnull または空文字の場合に false です。そのとき、while ループも停止します。

P.S. numnull の場合、num <= 100true なので、2回目のチェックがなければ、ユーザーがCANCELをクリックするとループは止まらなくなります。 両方のチェックが必要です。

重要性: 3

1 よりも大きい整数で、1 と自身以外では、余りなく割ることができない場合、その数値は 素数(prime) と呼ばれます。

つまり、n > 11n を除いて均等に分割できない場合、素数です。

例えば、5 は素数です。なぜなら、2, 34 ではあまり無く割ることができなからです。

2 から n の範囲で、素数を出力するコードを書きなさい。

n = 10 の場合、結果は 2,3,5,7 です。

P.S. コードは任意の n で動作させてください。固定値でハードコードはしないでください。

このタスクを解決するのに、多くのアルゴリズムがあります。

入れ子ループを使ってみましょう:

For each i in the interval {
  check if i has a divisor from 1..i
  if yes => the value is not a prime
  if no => the value is a prime, show it
}

ラベルを使ったコードです。:

let n = 10;

nextPrime:
for (let i = 2; i <= n; i++) { // for each i...

  for (let j = 2; j < i; j++) { // look for a divisor..
    if (i % j == 0) continue nextPrime; // not a prime, go next i
  }

  alert( i ); // a prime
}

ここには最適化の余地が沢山あります。例えば、2 から i の平方根までの約数を探すことができます。しかし、とにかく、私たちが大きな間隔に対して効率的になりたいなら、アプローチを変更し、高度な数学とQuadratic sieve, General number field sieveなどの複雑なアルゴリズムに頼る必要があります。

チュートリアルマップ

コメント

コメントをする前に読んでください…
  • 自由に記事への追加や質問を投稿をしたり、それらに回答してください。
  • 数語のコードを挿入するには、<code> タグを使ってください。複数行の場合は <pre> を、10行を超える場合にはサンドボックスを使ってください(plnkr, JSBin, codepen…)。
  • 記事の中で理解できないことがあれば、詳しく説明してください。