2022年6月19日

ループ: whileとfor

アクションを繰り返す必要がよくあります。

例えば、リストから商品を順番に出力したり、1から10までの各数字に対して同じコードを実行したりします。

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

for…ofループとfor…inループ

上級者向けの小さなお知らせです。

この記事では、基本的なループである`while`、`do..while`、`for(..;..;..)`のみを扱います。

この記事で他の種類のループを探している場合は、以下のポインタを参照してください。

  • for…inを参照して、オブジェクトのプロパティをループ処理します。
  • for…ofイテラブルを参照して、配列とイテラブルオブジェクトをループ処理します。

それ以外の場合は、読み進めてください。

「while」ループ

`while`ループの構文は以下のとおりです。

while (condition) {
  // code
  // so-called "loop body"
}

`condition`が真である間、ループ本体の`code`が実行されます。

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

let i = 0;
while (i < 3) { // shows 0, then 1, then 2
  alert( i );
  i++;
}

ループ本体の単一の実行を反復と呼びます。上記の例では、ループは3回反復します。

上記の例で`i++`がなかった場合、ループは(理論上は)永遠に繰り返されます。実際には、ブラウザはこうしたループを停止する方法を提供しており、サーバーサイドのJavaScriptではプロセスを強制終了できます。

比較演算だけでなく、任意の式や変数をループ条件にすることができます。条件は`while`によって評価され、ブール値に変換されます。

例えば、`while (i != 0)`をより短く書く方法は`while (i)`です。

let i = 3;
while (i) { // when i becomes 0, the condition becomes falsy, and the loop stops
  alert( i );
  i--;
}
単一行の本体には中括弧は必要ありません。

ループ本体に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`ループはより複雑ですが、最も一般的に使用されるループでもあります。

これは次のようになります。

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

例を通してこれらの部分の意味を学びましょう。以下のループは、`i`が`0`から`3`未満になるまで`alert(i)`を実行します。

for (let i = 0; i < 3; i++) { // shows 0, then 1, then 2
  alert(i);
}

`for`ステートメントを部分ごとに調べましょう。

部分
開始 let i = 0 ループに入るときに1回実行されます。
条件 i < 3 各ループ反復の前にチェックされます。偽の場合、ループは停止します。
本体 alert(i) 条件が真である間、繰り返し実行されます。
ステップ i++ 各反復で本体の後に実行されます。

一般的なループアルゴリズムは次のようになります。

Run begin
→ (if condition → run body and run step)
→ (if condition → run body and run step)
→ (if condition → run body and run step)
→ ...

つまり、`begin`は1回実行され、その後反復します。各`condition`テストの後、`body`と`step`が実行されます。

ループが初めての場合は、例に戻って、紙の上でステップバイステップでどのように実行されるかを再現すると役立つかもしれません。

これが私たちのケースで実際に起こることです。

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

// run begin
let i = 0
// if condition → run body and run step
if (i < 3) { alert(i); i++ }
// if condition → run body and run step
if (i < 3) { alert(i); i++ }
// if condition → run body and run step
if (i < 3) { alert(i); i++ }
// ...finish, because now i == 3
インライン変数宣言

ここでは、「カウンター」変数`i`がループ内で直接宣言されています。これは「インライン」変数宣言と呼ばれます。このような変数はループ内でのみ表示されます。

for (let i = 0; i < 3; i++) {
  alert(i); // 0, 1, 2
}
alert(i); // error, no such variable

変数を定義する代わりに、既存の変数を使用できます。

let i = 0;

for (i = 0; i < 3; i++) { // use an existing variable
  alert(i); // 0, 1, 2
}

alert(i); // 3, visible, because declared outside of the loop

部分のスキップ

`for`のどの部分もスキップできます。

例えば、ループの開始時に何もする必要がない場合は、`begin`を省略できます。

ここに例を示します。

let i = 0; // we have i already declared and assigned

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

`step`部分も削除できます。

let i = 0;

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

これにより、ループは`while (i < 3)`と同じになります。

実際にはすべてを削除して、無限ループを作成できます。

for (;;) {
  // repeats without limits
}

2つの`for`のセミコロン`;`は必ず存在する必要があることに注意してください。そうでなければ、構文エラーになります。

ループの中断

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

しかし、特別な`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++) {

  // if true, skip the remaining part of the body
  if (i % 2 == 0) continue;

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

`i`が偶数の値の場合、`continue`ディレクティブは本体の実行を停止し、`for`の次の反復(次の番号付き)に制御を渡します。そのため、`alert`は奇数の値に対してのみ呼び出されます。

`continue`ディレクティブはネストを減らすのに役立ちます。

奇数値を表示するループは次のようになります。

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

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

}

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

しかし、副作用として、ネストレベルが1つ増えました(中括弧内の`alert`呼び出し)。`if`内のコードが数行より長い場合、全体的な可読性が低下する可能性があります。

`?`の右側には`break/continue`がありません。

式ではない構文構造は、三項演算子`?`と一緒に使用できません。特に、`break/continue`などのディレクティブは使用できません。

例えば、このコードを取り上げると

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

…疑問符を使用して書き直すと

(i > 5) ? alert(i) : continue; // continue isn't allowed here

…機能しなくなります。構文エラーが発生します。

これは、`if`の代わりに疑問符演算子`?`を使用しないもう一つの理由です。

break/continueのラベル

複数入れ子のループから一度に抜け出す必要がある場合があります。

例えば、以下のコードでは`i`と`j`をループ処理し、`(0,0)`から`(2,2)`までの座標`(i, j)`をプロンプト表示します。

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

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

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

    // what if we want to exit from here to Done (below)?
  }
}

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 an empty string or canceled, then break out of both loops
    if (!input) break outer; // (*)

    // do something with the value...
  }
}

alert('Done!');

上記のコードでは、`break outer`は`outer`という名前のラベルを上向きに探し、そのループから抜け出します。

そのため、制御は`(*)`から`alert('Done!')`に直接移動します。

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

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

`continue`ディレクティブもラベルで使用できます。この場合、コードの実行はラベル付きループの次の反復にジャンプします。

ラベルは「ジャンプ」を許可しません。

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

例えば、これは不可能です。

break label; // jump to the label below (doesn't work)

label: for (...)

`break`ディレクティブはコードブロックの中に存在する必要があります。技術的には、ラベル付きのコードブロックであれば何でも構いません。

label: {
  // ...
  break label; // works
  // ...
}

ただし、上記の例で見たように、`break`は99.9%の確率でループ内で使用されます。

`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-- );
}

各ループ反復で`i`は`1`ずつ減少します。`while(i)`チェックは`i = 0`になるとループを停止します。

したがって、ループのステップは次のシーケンスを形成します(「ループ展開」)。

let i = 3;

alert(i--); // shows 3, decreases i to 2

alert(i--) // shows 2, decreases i to 1

alert(i--) // shows 1, decreases i to 0

// done, while(i) check stops the loop
重要度: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となり、アラートは1を表示します。

    その後、2, 3, 4…と続きます。値は順番に表示されます。比較では常に増分された値が使用されます。なぜなら、++は変数の前にあるからです。

    最後に、i = 45に増分され、比較while(5 < 5)は失敗し、ループは停止します。そのため、5は表示されません。

  2. 1から5まで

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

    最初の値は再びi = 1です。i++の後置形式は、iを増分してから古い値を返すため、比較i++ < 5ではi = 0が使用されます(++i < 5とは対照的です)。

    しかし、alert呼び出しは別個です。それは増分と比較の後で実行される別の文です。そのため、現在のi = 1を取得します。

    その後、2, 3, 4…と続きます。

    i = 4で停止してみましょう。前置形式の++iはそれを増分し、比較で5を使用します。しかし、ここでは後置形式のi++を使用しています。そのため、i5に増分されますが、古い値を返します。したがって、比較は実際にはwhile(4 < 5)となり、真であるため、制御はalertに進みます。

    i = 5は最後の値です。次のステップではwhile(5 < 5)が偽になるためです。

重要度:4

各ループについて、表示される値を書き出してください。その後、解答と比較してください。

両方のループは同じ値をアラートしますか、それともしませんか?

  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. 真の場合、ループ本体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または空文字列の場合、偽になります。その後、whileループも停止します。

補足:numnullの場合、num <= 100は真であるため、2番目のチェックがないと、ユーザーがキャンセルをクリックした場合、ループは停止しません。両方のチェックが必要です。

重要度:3

1より大きい整数は、1と自分自身を除いて何にも余りなく割り切れない場合、素数と呼ばれます。

言い換えると、n > 1は、1n以外では割り切れない場合、素数です。

例えば、5は素数です。なぜなら、234では余りなく割り切れないからです。

2からnまでの範囲の素数を表示するコードを作成してください。

n = 10の場合、結果は2,3,5,7になります。

補足:コードは任意の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の平方根までの除数を探すことができます。しかし、いずれにせよ、大きな区間に対して本当に効率的にしたい場合は、アプローチを変更し、二次ふるい一般数体ふるいなどの高度な数学と複雑なアルゴリズムに頼る必要があります。

チュートリアルマップ

コメント

コメントする前にこれを読んでください…
  • 改善点の提案がある場合は、コメントする代わりにGitHub issueまたはプルリクエストを送信してください。
  • 記事の内容が理解できない場合は、詳しく説明してください。
  • コードを数語挿入するには<code>タグを使用し、数行の場合は<pre>タグで囲み、10行以上の場合にはサンドボックス(plnkrjsbincodepen…)を使用してください。