配列 7. 取り除いた値を追加する

world.allPossibleCoordinates(ステージの全てのマスの座標)の代わりに、指定した行の全マスを取得するメソッドが紹介されます。それが3行目です。inRows:パラメーターで整数を指定するとその行の全マスの座標が返るので、それを配列に入れて使用します。

出題文には手順しか書かれておらず、結局ステージがどうなればクリアなのかがわからないままですが、とりあえず//で始まるコメント文に指示してある内容を埋めていくとこんな風になります。

先に実行結果を見てみましょう。シュールな光景ですね(笑)。

まず9行目のfor〜in文では、2列目の全てのマスの座標が配列に入っているので、それをtmpcdnという名前で1つずつ取り出してループ内を実行します。ちなみにtmpというのは「一時的な」という意味のtemporaryを略したもの、cdnはcoodinateを略したもので、あわせてtmpcdnです。深い意味はありませんので、自分で憶えやすい名前にしてください。

で、それらの全マスにword.place()メソッドが実行されるので、端から端までブロックが1段積み上がります。

次に14行目の

ですが、まず()内のallCoordinates.removeFirst()で、全ての2列目の座標の配列から、最初の1つを削除します。つまり(0,2)〜(19,2)の20個の配列から先頭の(0,2)が抜かれ、(1,2)〜(19,2)の19個が残ります。配列内での背番号は詰められていきますが、実際の座標自体が詰まるわけではないので注意してください。allCoordinates[0]が(0,2)から(1,2)になります。抜いた(0,2)の座標が()の外側にあるcdns(最初に用意しておいた空の配列)に.append(追加)されます。ちょっと特殊な書き方ですが、removeFirst()などのメソッドを使うと、そこに削除したオブジェクトがその行の中に一時的に残ると考えてください。なので、.append()のパラメータとして引き継がれます。
これが8行目のfor i in 1…12によって12回繰り返されるわけですが、2回目はallCordinatesの中が(1,2)〜(19,2)になっているので、ひとつ奥の(1,2)から(19,2)の19座標に対してworld.place()が実行されます。結果としてさっきよりも1列短い形で2段目が形成されます。そのあとまた.removeされるので、3周目は(2,2)〜(19,2)となり、、結果として1段上がるごとに1列少ないブロックが12段積み上がっていきます。
8〜15行目のループが終わった後は、19行目からのループです。これには1行上がるごとに削られた左端の座標が1つずつ入っているので、それらすべてにキャラクターを置いていくと、写真のようになるわけです。
ややこしいですが順を追ってじっくり考えてみましょう。

配列 6. 島を作る

前ステージの応用編です。allCoordinatesに全マスの座標を入れておき、ある条件に沿うものを配列Aに.append()、そうでないものを配列Bに.append()して、最後に配列Aと配列Bそれぞれについて異なる処理をします。
異なる処理というのは、「陸地ブロックを積む」と「海にする」なので、それぞれ、islandArrayとseaArrayという名前にしてみます。いいかげんcoordinate、coordinates、Coordinateなど似たようなのが入り乱れててわかりづいらいですよね。ので、配列には「Array」とつけてみました。配列を空の状態でつくる時は、将来的にどんな型のオブジェクトが入るかあらかじめ宣言しておく必要があります。今回はどちらもCoordinate型です(4,5行目)。

7行目からのfor〜inループは基本的に前ステージと同じです。Coordinate型オブジェクトcoordinateのメンバー変数であるcolumnとrowをチェックしてある範囲のものだったらislandArrayに追加。そして今回elseブロックでそれ以外の時はseaArrayに入れて行きます。これで全てのコマはislandArrayかseaArrayのどちらかに別れてコピーされたことになります。ちなみに、islandとは島(アイランド)の意味であって、isで始まるからといってBool型を意味してるわけではないです。

このステージでも実行結果を写真で載せておきます。真ん中に4×4マスの島ができ、後の陸地は全て海に変化してれば正常です。

coordinate、coordinates、Coordinateの区別

上の方で書いた似たような名前の見分け方について触れておきます。これはコードを書く人の好き好きで決められる部分でもあるので必ずではないのですが、一般的にこういうルールで使い分けてる人が多いですよ、という話です。逆にいえばこういうルールで書いておけば誰か他の人がみても理解しやすいでしょう、ということでもあります。

まず最初が大文字になっているものは、一般に型(クラス)を意味しています。実際のプログラミングでは型自体を自分で用意することもありますが、当面Swift Playgroundsの中では最初から用意されているなんらかの型を指していると考えて良いでしょう。

次に、同じ名前でcoordinateと小文字で書いたものは、大文字の型から作られたインスタンスであることが多いです。letやvarでTaiyaki型から作った1つのたい焼きがtaiyakiです。ひとつだけ作る場合はそれでいいですが、複数個の場合はtaiyaki1とtaiyaki2としたりもするかも知れません。

そして複数形coordinatesとなっていたら、Coodinate型のオブジェクトが複数入った配列であると考えられます。配列は中身が複数入るので複数形なのです。わかりやすいでしょ?たくさんあったらtaiyaki
[0]、taiyaki[1]というように[]内の通し番号で呼び分けます。

そしてfor〜in文でループする時は、

のように、「for 単数形 in 複数形 {}」とし、ループの中では単数形を使う形にするのが直観的でしょう。
例えばwatersみたいに「実際の英語では単複同形なのでsつけるのおかしくね?」というような場合もありますが、まぁあんまり英語としての正しさは二の次で、「配列ならs」をつけるというルールにしてしまった方がわかりやすかったりします。どうしても気持ち悪ければ上の例のように「Array(=配列)」をつけるとか自分で独自のルールを決めても良いと思います。

配列 5. 配列に追加する

8×8の64マスの座標をallCoordinates配列に入れて、for〜in文で1つ1つについてチェックをして、条件に当てはまるものを別のblockSetという配列に追加し、最後にblockSetの中身1つ1つについてまたfor〜in文でループしてブロックを積み上げていきます。

このステージでの目新しいテクニックは、2つ。
1つは、coodinate.columnとかcoodinate.rowでCoodinate型オブジェクトがもっているcolumnやrowといった値(メンバー変数と呼んだりします)を取り出せる点です。ドット記法は色々な意味があってややこしいですね。一般に、

のように最後に()がついている場合は、そのオブジェクトになんらかの処理(メソッド)を実行させる意味で、

のように()がついていないものは、オブジェクトが中にもっている変数(メンバー変数)を指していると思えばよいでしょう。英語なのが難点ですがその意味からなんとなくイメージすることもできることが多いです。

2つめは、配列に対して要素を追加する.append()メソッドです。以前、.insert()というのも出てきましたが、それと違い、単純に最後に付け足すというものです。

今配列内に要素がいくつあるか気にしなくて良いので、うっかり範囲外エラーを出してしまう心配もなくお手軽ですね。配列の中での順番が重要ではない場合は、.insert()より.append()がオススメです。

その2点を踏まえて正解例をご覧ください。

いまいち何をしているのかわかりづらいので、正解例コードを実行した時の結果も画像で載せておきます。

配列 4. 順番に並べる

配列に後から要素を追加したり削除したりする練習です。
7行目までの部分でcharactersという配列が用意され、Blu型キャラクター、ワープポータル、Hopper型キャラクター、宝石が入れられます。キャラクター以外のものが混じってたりするので修正したいのですが、直接4行目や6行目を削除することはできません。ここでは一旦作って中身を入れてしまった後の配列に対し、.removeメソッドで要素を消したり.insertメソッドで追加したりする練習をするのが目的だからです。

まず正解例はこんな感じになります。

■配列から要素を削除したり追加するには

配列名に対してドット記法で.removeや.insertメソッドを指定します。この際、.removeなら何番目の要素を削除するかをパラメーターとして指定してやります。

ここで非常に重要な注意点があります。3〜6行目をみると、余分な要素であるポータルと宝石は2番目と4番目にあります。配列の中身は0から数えるルールなので、

としてみたのではないでしょうか?そうすると宝石を削除する方が配列の範囲外エラーになります。どうしてでしょう?この理由をイメージできる感覚はプログラミングにとても重要な気がしますので、じっくり考えてみてください。正解は最後に。

でとりあえず次は挿入。.insertの場合は、なにを足すかと、どこに割り込ませるかを指定します。

■16行目以降について

今回はループ毎に配列の中身を順に読み出しても変化するのはキャラクターの種類だけです(Blu->Expert->Hopper)。それとは別に配置する位置は自分でズラしていかなければなりません。そこで、rowの値が繰り返し毎に1ずつズレていくよう別の変数rowPlacementを用意して工夫します。最初に16行目で0をセットしておくことで、初回のループではrow:パラメーターが0になり、(1,0)の位置にBluが出現します。そして繰り返しで戻る前の20行目で1を足しておくことで、2回目は(1,1)にExpertが、3回目は(1,2)にHopperが出てくるわけです。

■なぜ12行目は範囲外エラーになるのか?

さて、あえて正解例コードにバグを入れておいた「範囲外エラー」の答え合わせについてです。範囲外エラーなので試しに数字を小さくしてみれば上手くいっちゃったりするかも知れません。そう、正解は、3ではなく2ですね。大事なのは何故かです。これは直前の行でワープポータルを削除したことに関係します。
最初はこうでした。

その後で、

したらどうなるでしょう?

こうですね。つまり、charactersの中のオブジェクト数は4から3に減りました。そして、Gem()の順番も4番目でなく3番目に繰り上げになっています。配列は0番スタートなので、3番目ということは0,1,2で2を指定する必要があるわけです。3を指定するということは存在しない4番目を削除しようとすることになるので、範囲外エラーというわけですね。
もちろん、.insertする場合も影響しますので、常にその時点で配列の中がどうなっているかを意識しましょう。

配列 3. ブロックを積む

配列の各部屋には

のように整数を入れるだけでなく、様々な型のオブジェクトを入れることができます。

このステージでは列番号と行番号をひとまとまりにしたCoordinate(座標)型というオブジェクトを配列に格納します。ちょっと入れ子のようでややこしいですが、「カンマで区切られた数だけ、同じ型のものが順番に入れられる」という意味では整数の時と全く同じなので気を楽にしてください。

まずは正解例はこんな感じになります。

3行目から8行目は長いので改行していますが、[]でひとまとまりで、4つのCoodinate型オブジェクトをblockLocationsという名前の配列に入れていることになります。

もしピンとこなければこういうコードで考えてみてください。

正解例コードの14行目でもやっていますが、「後で名前で参照できなくて良いオブジェクトはいきなり型名を指定してもOK」でしたよね。「ステージを作る」章の「階段を置く」で出てきた省略記法です。これと同じことをすると、上のコードは、正解例コードの3〜8行目のように書けるわけです。

ということで、blockLocationには4箇所の座標(ステージの4隅)を示す4つのCoordinate型オブジェクトが入りました。これをfor〜in文を使って繰り返します。blockLocation配列から1つずつ取り出されてcoodinateという名前でループ内で利用可能になるので、13行目のworld.place文でat:パラメーターの中身が置き換えられます。最初のループでは、

としたのと同じことになります。2ループ目は

ですね。