読者です 読者をやめる 読者になる 読者になる

小学校の先生のためのSwift Playgrounds講座 コードを学ぼう1<whileループ>【解答】

f:id:PSYuki:20170403225928p:plain こんばんは。ツムさんです。
プログラミングなんてやったことないぜキリッ
という先生のための Swift Playgrounds を使ったセミナーです。

 

 

 前回は、論理演算子を使ってifの条件をより細かく制御できるようにしました。前回までの記事はこちら。

今回は、whileループです。

 

 

whileループって何?

前々前回で「forループ」を学びました。whileループも同じ「ループ」という言葉が付いています。何が違うのでしょうか。

forループは、コマンドを繰り返すために下のような構文を使いました。繰り返したい回数をプログラムの中で指定して、その回数の数だけ{}で囲まれたコマンドが実行されるのでした。

 

たとえば、10歩進むために forループを使うと、

 

for i in 1 ... 10 {

    moveForward()

}

 

と書けました。

 

じゃぁ、whileループとは何か?ということですが、whileは日本語に直すと「~の間」という意味です。whileループは~の間「繰り返す」ことが出来ます。forループが回数だったのに対して、whileループは、ifと同じ「条件」を判別して繰り返します。

 

おっと、Swift Playgroundにいいスライドがあるので引用します。これは釘打ちの例です。何回打てばいいのかは分からないので、forループではプログラムすることができません。代わりに「釘がまだ飛び出している間」を条件にしてwhileループでプログラムすることが出来ます。条件は「釘がまだ飛び出していること」になるわけです。

f:id:PSYuki:20170508104259p:plain

 

下のスライドで nailIsStickingOut が「釘が飛び出している」条件を示しています。whileはまず、この条件が成立するかどうかをチェックして、成立していれば{}の中のコマンドを実行します。釘が飛び出していればhammerNail()(ハンマーで打つ)を実行します。そして、再び条件が成立するかを確認してまだ釘が飛び出しているなら、再びhammerNail()(ハンマーで打つ)コマンドを実行します。釘が飛び出さなくなるまで繰り返します。

f:id:PSYuki:20170508104644p:plain

 

 

条件を決めてコードをループする

whileループ最初のステージは真っすぐに並べられたスイッチを全部押します。スイッチの数は実行する度に増えたり減ったりします

つまり、forループで、6回繰り返すと指定しても、実行してみるとスイッチの数が4個しかなかったり、8個に増えてしまったりするわけです。

このような場合、forループでプログラムすることはとても難しいです。

f:id:PSYuki:20170508105709p:plain

何度も実行してみると分かりますが、スイッチの数は増えたり減ったりしますが、必ず「連続して」置かれています。そして最後のマスは必ずONになっているスイッチが置かれていることが分かります。

 

ということは、whileを使って、「OFFスイッチの上に居る間」を条件として繰り返してあげればよいことになります。

 

1. OFFスイッチの上に居るか確認(whileのisOnClosedSwitch)

2. OFFスイッチならスイッチを押す(toggleSwitch())

3. 1歩進む(moveForward())

4. 1に戻って繰り返し

 

1~4の処理をプログラムに直したのが上のコードになります。

ちなみに、スタート位置のマスに最初からONスイッチが置いてあったら(要は一度もスイッチを押す必要が無い場合)このプログラムはどう動くのでしょうか?

 

1の条件がいきなり不成立となるため、2,3,4は一度も実行されることなく、4より後ろの処理へ移っていきます。

 

何度も実行すると下のようにスイッチの数が増えたりしますが、whileループを使えば大丈夫。100マス先だろうが1000マス先だろが、ONスイッチが現れるまで健気に何度スイッチを押してくれます。

f:id:PSYuki:20170508110248p:plain

クリアしました!!

f:id:PSYuki:20170508110435p:plain

 

whileループをじょうずに使う

1つ前のステージから少し複雑になりました。ワープマスが2か所(黄色と紫)が登場していますが真っすぐ進めばいいことは変わらないので、先ほどのステージと同様に一歩進んではスイッチを押すという処理を繰り返せばよさそうです。

 

途中にはONとOFFの2つの状態のスイッチが置いてあります。このステージも実行する度に、ONとOFFが変化します。ifを使ってスイッチの状態を調べる必要がありますね。

 

1つ前のステージは、whileの繰り返し条件を「OFFスイッチの上に居る間」としました。ここではどのような条件にしたらいいのでしょう。

OFFスイッチはどこに現れるか分かりません。たまたま最後のスイッチだけONで、残りが全てOFFのスイッチになれば、先ほどのプログラムでクリアすることができますが、このステージはOFFスイッチとONスイッチが混ざって配置されるため、同じプログラムではクリアできないケースが出てくるのです。

f:id:PSYuki:20170508110617p:plain

ステージをよぉーく見ると、最後のマスにはスイッチがなく行き止まりになっています。つまり、1歩1歩進めていって、行き止まりでは無い間、スイッチを押すという処理を繰り返せばよさそうです。行き止まりでは「ない」ことを条件とするので、前回学んだ論理演算子のNOT(否定)を使います。

 

行き止まりは isBlocked です。これを否定するのですから、

!isBlocked と書けますね。

 

プログラムの大枠は、

 

while !isBlocked {

    ...

}

 

になります。{}の中は、1歩進んで、OFFスイッチのマスに止まったらスイッチを押す。という処理になります。ifを使って書くことが出来ます。

 

moveForward()

if isOnClosedSwitch {

    toggleSwitch()

}

 

ですね。

 

f:id:PSYuki:20170508115448p:plain

クリアできました!!

 

正しいループを使う

ステージのタイトルが少し意味ありげですね。このステージは、whileループでもforループでもどちらでもクリアすることができます。

宝石は全部で6個です。何度実行しても宝石の数や位置は変わりません。

つまり、forループを使って回数を指定してもクリアできてしまうのです。宝石も斜めに並んでいますね。Byteくんをあるパターンに従って動かしてあげることでプログラムをカンタンに書けそうです。

f:id:PSYuki:20170508115553p:plain

Byteくんの動きのパターンが見えてきましたか??

スタート位置から見て、最初の宝石を取るまでは、

  1. 1歩進む
  2. 左を向く
  3. 1歩進む
  4. 宝石を取る
  5. 右を向く

のコマンドで問題無さそうです。5で右を向いているのがポイントです。5で右を向いておくことで、1に戻って繰り返してみると、2個目の宝石もGetできてしまいます!!

(頭の中でByteくんを動かしてみてください。2個目の宝石を取れましたか?)

 

ということは、、1~5のコマンドを関数にしておいて、for(もしくはwhile)ループの中で呼び出してあげれば6個の宝石を取ることができるのではないでしょうか。

上のプログラムでは、turnAndCollectGemという名前の関数を作って、この中に1~5のコマンドを書きました。関数を繰り返す部分をwhileループで書いていますが、forループで書くとどうなるのでしょうか。宝石は全部で6個あるので、6回繰り返せばよさそうですね。

 

for i in 1 ... 6 {

    turnAndCollectGem()

}

 

となります。このステージはwhileで書いてもforで書いても記述量はほとんど変わりませんが、ある処理を実現するときに何通りもの書き方(プログラム)がある場合は、なるべく簡潔に書けるように意識すべきです。

 

f:id:PSYuki:20170508121029p:plain

クリアです!!

 

2つのループ

前のステージと同様、ここでも whileとforのどちらを使ってもクリアすることができます。今度は宝石ではなくスイッチになっています。ONとOFFが実行するたびに変わるため、ifを使ってスイッチの状態を確認しなければなりません。

ただ、スイッチの数は変わりません。

ということは、処理のパターンさえ見つけてしまえば、そのパターンを繰り返す回数を指定してforを使ってプログラムすることもできる。ということになります。

f:id:PSYuki:20170508121053p:plain

ワープマスが入っていて、最後は少し分かりにくのですが、処理パターンはスタート位置から見てみてください。

  1. 3歩進む
  2. OFFスイッチか確認する
  3. OFFスイッチならスイッチを押す
  4. 右を向く

どうでしょうか?1~4を何度も繰り返すことでOFFスイッチを押しながら最後のスイッチまでたどり着くことが出来ましたか?ワープマスは勝手にワープするので1歩とカウントされません。

 

whileループの条件は前のステージと同様に「行き止まりかどうか」を使えそうですね。最後のスイッチの先は行き止まりになっています。

 

上のプログラムはwhileで書きました。これをforループに直してみましょう。

 

for i in 1 ... 4 {

    moveAndToggle()

}

 

1~4の手順は、moveAndToggleという名前の関数を作ってその中に書きました。forの中で繰り返すのは、moveAndToggle関数を書くだけにしています。

f:id:PSYuki:20170508122023p:plain

クリアできました!!

 

回る

Byteくんの回りを宝石がグルーっと取り囲んでいます。このステージは宝石の数も位置も変わりませんので、forループを使うこともできます。

 

まずは、繰り返すべき処理のパターンを見つけましょう。

f:id:PSYuki:20170508122106p:plain

私は moveAndGetGemという名前の関数の中で、1辺に置かれている2つの宝石を取る処理を書いてみました。1辺の2つの宝石を取るためには、スタートのマスからみて、

  1. 1歩進む
  2. 左へ向く
  3. 宝石を取る
  4. 1歩進む
  5. 宝石を取る
  6. 左を向く
  7. 1歩進む
  8. 右を向く

となります。最後の8(左を向く)がポイントです。8があることで、2つの宝石とスタートマスの位置関係が、丁度隣の辺に移動したことになるからです。

 

このステージは4辺を順番に処理すればいいことが明確なので、whileではなくforループで作ってみました。

f:id:PSYuki:20170508122743p:plain

クリアできました!!

 

方法はたくさん

最初のステージに似てますが、列が3列に増えています。最初の列はスイッチ、真ん中の列は宝石です。最後の列は再びスイッチが置いてあります。

宝石の数とスイッチの長さが変わります(ステージが伸び縮みします)。このためforループで繰り返すことは非常に難しいです。

f:id:PSYuki:20170508122858p:plain

このステージの繰り返しパターンはどう考えますか?

 

宝石の列とスイッチの列があるので、まずはこの2種類に分けて考えてみるのがよさそうです。

最初と3列目はスイッチの列、真ん中は宝石の列です。

つまり、

 

1. スイッチ列の処理

2. 宝石列の処理

3. スイッチ列の処理

 

の順番で実行することができるわけです。このとき、1と3は同じ関数を使って簡潔にプログラムを書けるようにします。

 

私は、スイッチ列の処理をtoggleAndTurn関数、宝石列の処理をgetGemAndTurn関数という名前で作りました。toggleAndTurnもgetGemAndTurnもどちらも、数が変わるスイッチや宝石を相手に処理しますから、forループではなくwhileループを使います。繰り返しの条件は何度も使ってきた「行き止まりでない」ことにしました。toggleAndTrun()は下のように書けますね。

 

func toggleAndTurn() {

    while !isBlocked {

        moveAndToggle()

    }

    turnRight()

}

 

{}の中の最後に turnRightコマンドを書いています。これは一体どういう意味があるのでしょうか。turnRightコマンドはwhileループが終わった後に実行されます。whileループが終わるのは「行き止まりではない」という条件が成立しなくなったときなので、要は「行き止まりになったら」whileループは繰り返しを停止します。そしてこの後、turnRightするのです。

 

そうですね。隣の列へ向きを変えたんですね。

moveAndToggle関数は、1歩すすんでOFFスイッチならスイッチを押すといういつもの処理です。

 

一方、宝石の列はどう書けるでしょうか。

 

func getGemAndTurn() {

    while !isBlocked {

         moveAndGetGem()

    }

    turnLeft()

}

 

宝石の列もスイッチの列と同様に「行き止まりでない間」処理を繰り返すことになります。何度も言いますが、これは宝石の数が実行の度に異なるためです。もし宝石の数が変わらないならforループを使って書くこともできます。

 

そして whileループの後の turnLeftコマンドも先ほどと同様に隣の列へByteくんの向きを変えるためのコマンドです。

 

moveAndGetGem関数は、1歩進んで宝石マスの上だったら宝石を取るといういつもの処理をプログラムしています。

 

関数は全部で4つになりました。スイッチの列を処理する関数が2つ(toggleAndTurnとmoveAndToggle)と、宝石の列を処理する関数が2つ(getGemAndTurnとmoveAndGetGem)です。

f:id:PSYuki:20170508125011p:plain

宝石の列を処理する関数(getGemAndTurnとmoveAndGetGem

 

4つの関数を使って、3つの列を順番にクリアしていきます。最初にtoggleAndTurnを使って最初の列のスイッチを押します。最後は右を向いて終わりますから、1マス進んで右を向いてから(隣の列へ移ってから)宝石を取り始めます。宝石の列が終わったら最後は再びスイッチの列です。隣の列へ移るためのコマンドをサンドイッチしていることに注意してください。

f:id:PSYuki:20170508125122p:plain

クリアできました!!

 

ループをネストする

ネストというのは、ループの中でループさせるという構造のことを表します。プログラムっぽく書くと下のようなコードのことです。

 

while cond1 {

    while cond2 {

        ...

    }

}

 

このステージでは、ループをネストさせて使ってみます。

 

えっ、頭が混乱するって??

 

慣れです。最初からうまく書ける人はいません。私のプログラムに慣れるまでは、「書いては間違って直して」を繰り返していました。

 

f:id:PSYuki:20170508125455p:plain

ネストを考えるときは、外側のループの条件を先に考えてください。このステージは、最後はらせん状の階段の先に置いてある宝石を取ってクリアします。最後の宝石はらせん状の道の行き止まりの手前に置いてあります。

つまり、ネストするwhileループの外側の条件は、「行き止まりでない間」ということになります。では内側のwhileループの条件はどうやって考えればいいのでしょうか。

 

f:id:PSYuki:20170508130756p:plain

 らせん状の道は各辺に1つ宝石が置いてあり、宝石が置かれたマスを起点にして向きを変えてグルグル回っています。宝石が置いてあるマスまで1歩ずつ進んでいくことで、向きを変えるべき位置までByteくんを誘導することが出来そうですね。

 

宝石のマスに止まったか

宝石のマスでなければ1歩進む

 

ことを繰り返し、

宝石のマスに止まったときは whileループを抜けて、宝石を取り、左を向くことで内側のwhileループが完成します。

f:id:PSYuki:20170508130830p:plain

クリアしました!!

 

ランダムな四角形

このステージもネストしたループを使います。ステージは非常にシンプルでゴールのマスにOFFスイッチが一個だけ置いてあるというものです。ただし、実行する度にステージの大きさが変わるためforループで繰り返すのは現実的ではありません。whileループを使いましょう。

 

f:id:PSYuki:20170508130959p:plain

外側のwhileループから考えてみます。最後のOFFスイッチにたどり着かねばなりませんから、外側のwhileループの繰り返し条件は、「OFFスイッチの上に居ない間」ということになります。プログラムで書くと、 !isOnCLosedSwitchですね。

内側のwhileループは4辺を1辺ずつ移動するために必要なループです。ここでは行き止まりになるまで1歩ずつ進むようにプログラムしました。もし行き止まりになれば、隣の辺へ移るため右へ向きを変えます。

外側のwhileが終わった後、最後にOFFスイッチを押すための toggleSwitchを忘れないようにしましょう。

 

ステージが先ほどの写真よりも大きくなっていることが分かりますか?

f:id:PSYuki:20170508131526p:plain

このような場合、whileループを上手に使いましょう。

 

f:id:PSYuki:20170508131623p:plain

クリアです!!

 

どんな方法でも

whileループ最後の課題はOFFスイッチを押しながら、宝石のマスまで移動して宝石を取ってクリアです。途中ワープマスがあるので分かりにくいですが、落ち着て繰り返し処理する部分を見つけてあげることで比較的シンプルにプログラムすることが出来ます。

 

このステージは基本的に一本道です。何度も向きを変えなければなりませんが、向きを変えるのは当然のことながら「行き止まりになったら」ですので、裏を返すと「行き止まりでない間」は1歩ずつ前へ進んでいけばいいのです。

 

そうすると、内側のwhileループが思ったよりもシンプルに書けることが分かります。行き止まりでない間、一歩進んではOFFスイッチかどうかを調べスイッチが切れていたら押す。という処理を繰り返します。

行き止まりになって内側のwhileループを抜けたら、宝石のマスかもしれないので一度ifで確認します。その後、隣の辺へ移るためにByteくんを右へ向きを変えます。

 

あとは、このwhileループ自体を外側のwhileでネストしてあげれば完成です!

 

f:id:PSYuki:20170508131739p:plain

f:id:PSYuki:20170508132603p:plain

クリアしました~。

 

どうですか?whileループとforループを使い分けることでより柔軟なプログラムを書けるようになります。繰り返す回数が場合によって変わってしまう場合は、forループよりもwhileループのほうが適しています。forループが繰り返しの回数を指定するのに対してwhileループは繰り返すための条件を指定する必要があります。その条件には、行き止まりやOFFスイッチの上に居る間などを指定することが出来ました。

 

次回は「アルゴリズム」を説明します。