見出し画像

iPhone アプリを自分でつくる 5.


今回の内容: コレクション型(配列、辞書、Set)、 条件式、 ループ について

まずコレクション型の配列についてみていきます。
なおコレクション型というのは複数の要素をひとまとめにしたもので、Collection型の機能を持つものです。

配列について

配列はArray アレイとも言い、数字や文字をひとまとまりにしたものです。

・作り方は、内容を[ ] 四角カッコで囲んで、, 内容をカンマ記号で区切ります。
・内容の一つ一つをElement エレメント(要素)と呼びます。
・Element は、それぞれの順番が保存され内部で Indexがつけられます。
Index は、0から始まる整数です。
・Index を使用して要素を取り出すことができます。
・要素は追加、削除ができます
空(カラ)の配列を作ることができます。

配列の作成

let flowers: [String] = ["Sakura", "Ume", "Kiku"]
let points: [Int] = [100, 92, 63]
var time: [Double] = [ 10.3, 9.1, 9.5]
var name = ["田中", "鈴木"]               // 型を明示せずに型推論[String]をシステムにまかせるシンプルな書き方

// 定義で書かれる記法
var stations3: Array<String> = ["Tokyo", "Shinagawa", "Yokohama"// 型を明記した書き方 公式な内容でよく使われる形
var num: Array<Int> = Array([1, 3, 5])  // 定義通りに書くとこうなると思いますが、あまり見たことありません。
print(num)

空の配列をつくることもできます。要素がある場合と同じように扱うことができるのでよく使われます。


var playerPoints: [Int] = []     // 型を[Int]でしておき、空の配列は[]で表す方法
var systemPoints = [Double]()       // 変数では型を明示せずに [型]() で空の配列を表す方法

// 定義で書かれる記法
let testEmpty: Array<Double> = []
var emptyStrings: Array<String> = Array()

上記の例で[Double]() やArray()の最後にある()はイニシャライズ(初期化:使えるようにする)ためのものです。

配列はいろいろな書き方ができるので混乱してしまいがちですが、自分の書き方を決めておいて、他のコードを見たときに配列だと理解できれば良いです。

なお定義として載せた書き方は、一般に書くことはあまりないですが、公式ドキュメントなどで見たときに配列なんだな、とわかれば十分だと思います。
ちなみに、各型の書き方のことをliteral リテラルと呼び、定義とは違うやさしい書き方をプログラミングではシンタックスシュガー(systax sugar): 糖衣構文 と呼びます。糖衣というのは、昔よく使った「オブラート」ですね。
今でもオブラートは使われているのでしょうか?

配列のエレメントを繰り返してつくる際には便利な方法があります。
配列の要素を繰り返しできたら良いのにな〜と思ったときには、この機能があったことを忘れている、という経験は私だけではないはずです。

var digitCounts = Array(repeating: 0, count: 10)
print(digitCounts)
//  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

いろいろな要素を持つ配列
IntやDoubleなど基本的な型はもちろんですが、自分で作った構造体を型として扱うことも通常に行います。

let myNumbers: [Int] = [3, 5, 9, 1, 6]
let myDoubles: Array<Double> = [2.3, 9.6, 100.9, 1.5]

var myFriends: [MyFriend] = [friend1, friend2, friend3 ]  
   // 自分でMyFriend型の構造体をつくって、データを配列に入れておくイメージ  
   // friend1, friend2 … にそれぞれ名前、住所が入っていて取り出すことができるようなイメージ。 
   // 今後アプリを作る際にこのような使い方をしていきます。

配列の要素の取り出し
配列のIndexを[ ]で指定するとその要素を返すことができる。

var stations = ["Tokyo", "Shinagawa", "Yokohama"] 
print(stations[0])                        // Tokyo

print(["Tokyo", "Shinagawa", "Yokohama"][1])      // Shinagawa
  // アプリでこのような使い方はあまりしませんが、配列に直接Index指定しても要素が返ります。

配列の要素を最後に追加
.append(要素)で要素を配列の最後に追加できる。

stations.append("Odawara")
print(stations)            // ["Tokyo", "Shinagawa", "Yokohama", "Odawara"]

配列の要素を挿入
.insert(at: 要素)で要素を指定場所に挿入できる。

stations.insert("Ekiben", at: 3)
print(stations)            // ["Tokyo", "Shinagawa", "Yokohama", "Ekiben", "Odawara"]
                           // at: 3 で Index3 の前に入る

配列の要素を削除
.remove(at: 要素)で指定場所の要素を削除できる。
.removeLast() で最後の要素を削除できる。
.removeFirst() で最初の要素を削除できる。
.removeAll() で全て削除

stations.remove(at: 1)   // index 1番(2つ目)の要素”Shinagawa”を削除した
print(stations)           // ["Tokyo", "Yokohama", "Ekiben", "Odawara"]

stations.removeLast()
print(stations)           // ["Tokyo", "Yokohama", "Ekiben"]


stations.removeFirst()
print(stations)           // ["Yokohama", "Ekiben"]


stations.removeAll()
print(stations)           // []   空の配列

Tips
配列の値を取り出すときは、[a, b, c, d] [1] のように Indexを [ ] にいれますが、配列を追加するのは、[1, 2, 3, 4].append(5) 、削除するのは .remove(at: 1)
のように()を使用します。
私は学習開始当初、四角カッコや丸カッコ、どっちを使うのか混乱していましたが、[ ] は配列構造体そのものの機能(サブスクリプト)で、.append()や.remove() などはArray型の持つメソッド( struct Array は func append() などを持っているということ)のため ()になるということを理解してから混乱しなくなりました。
なお struct や func は今後取り扱う内容です。

配列に配列を追加する
配列に要素そのものを加えるときとは違うので注意
.append(contentsOf: 配列) で配列を加えることができる。
配列 + 配列 のように + 記号で配列同士をひとつにまとめることもできる。
ここらあたりの話はご自分でも何かの配列を作ってみると理解しやすいかと思います。

stations = []
let stationsA = ["Tokyo", "Shinagawa"]
let stationsB = ["Yokohama", "Odawara"]
stations.append(contentsOf: stationsA)
stations.append(contentsOf: stationsB)
print(stations)                // ["Tokyo", "Shinagawa", "Yokohama", "Odawara"]

stations = stations + ["Atami", "Mishima"]
print(stations)                // ["Tokyo", "Shinagawa", "Yokohama", "Odawara", "Atami", "Mishima"]

要素が配列型の配列(2次元配列)
配列の要素は、Int型、String型などと同様Array<String>型など配列を持つことができる。
型を明記するときは、[[String]] のようになる。
このように要素が配列の場合を特に2次元配列と呼ぶ。(要素が2次元配列であれば3次元配列となる)

var row1 = ["A1", "B1", "C1"]
var row2 = ["A2", "B2", "C2"]
var row3 = ["A3", "B3", "C3"]

var board: [[String]] = []    // 空の1次元配列をつくっておく
board.append(row1)         // 配列ごとappend(配列)している。
board.append(row2)
board.append(row3)
print(board)
// ----------------------------
//  [["A1", "B1", "C1"], ["A2", "B2", "C2"], ["A3", "B3", "C3"]]


// 2次元配列を理解しやすいように、このような記入をする場合も多い
var myGameBoard = [
     ["A1", "B1", "C1"],
     ["A2", "B2", "C2"],
     ["A3", "B3", "C3"]
    ]

上記では、空の一次元配列[ ]をつくったあとで、 board.append(row1) のように配列に直接配列をappend して2次元配列をつくっています。


配列の要素を数える

配列の要素数を調べるには .count を使用する。
.count は配列構造体のプロパティ(struct Arrayの持つ変数)です。 

var totalStations = stations.count
print(totalStations)          // 6

ある要素を含んでいるかどうか確認する
配列.contains(調べる要素) とするとBool型が返ります。

var isTokaidoLine = stations.contains("Hakodate")
print(isTokaidoLine)            // false

配列のソート
ソートする
には .sorted() で、ソートされた配列を変数に返します。
ソートを逆にする
には .reversed() を使用して変数に返します。

let names = ["Suzuki", "Tanaka", "Sato", "Hayashi", "Honda"]
var sortedNames = names.sorted()
print(sortedNames)     // ["Hayashi", "Honda", "Sato", "Suzuki", "Tanaka"]

var reversedNames = sortedNames.reversed()
print(reversedNames) 
// ReversedCollection<Array<String>>(_base: ["Hayashi", "Honda", "Sato", "Suzuki", "Tanaka"])

配列自体をソートするには .sort() を使用します。

var myFavorite = ["Nikuman", "Amman", "Gyoza", "Ramen", "Gyudon"]
myFavorite.sort()
print(myFavorite)      // ["Amman", "Gyoza", "Gyudon", "Nikuman", "Ramen"]

Tips
sorted() と sort() は似ていますが、sorted() はソートした内容を返すので、
 取得するための変数を用意して、その変数に返すようにします。
 いっぽう sort() は配列自体をソートするので、何も返しません。
 この動きは理解しようとすると混乱しますが、実際に自分で試してエラーを出し
 てみると理解できます。
reversed() は逆ソートを返す働きをしますが、print()した内容をみると
   ReversedCollection にラップされた配列を返しています。
 ReversedCollectionは、実際には配列をひっくり返さず、自分が呼ばれた際に
 答えを見せれるように準備しています。
 この方法によってコスト(コンピュータの仕事の負担)を抑えています。
・配列には randomElement() というメソッドがあり、ランダムにした配列を返
 します。ただし返すのは Optional型です。もし配列が空配列だった場合にnil   
    を返すので、このメソッドを使うときにはOptional への対応をします。

配列型に内容が入っているか確認する
.isEmpty は配列に内容がないかあるかを Bool型で返します。なければ trueを返します。

let testEmpty1: [Int] = []
let isEmptyCheck = testEmpty1.isEmpty
print(isEmptyCheck)        // true

Tips
var fruits = [""]
多くのプログラミング言語で、カラかどうかを確認する際に、配列に入っている
要素数が0 かどうかを確認します。
もしSwift で同様の方法で確認する際には次のいずれかになります。
let hasNoContents = (fruits.count == 0)
let hasNoItems = (fruits.isEmpty)
いずれの方法でも結果は正しいですが Swiftのコメントには、「カラかどうかの判断はisEmptyプロパティを使うようにして countは使用しない」旨の記載があります。
理由はcountを使用すると、内容がいくつあるか答えを出してから0と比較するためコストが高くなる(プログラミングの世界ではシステムが余計に仕事をして結果を出すまでの時間や負担が余分にかかることをこう言います)からです。人間であればひと目で判断するところをcountはわざわざ働いてしまうんですね。
これはSwiftの言語仕様であって、他の言語ではそれぞれの仕様に合ったやりかたがあります。


辞書型について

辞書型は下記のようにつくります。

let itemPrices: [String: Int] = ["Apples": 250, "Oranges": 150, "Bananas": 200]

var bananaPrice = itemPrices["Bananas"]

print(bananaPrice)

// コンソール表示
Optional(200)

[ ]四角カッコの中にKey キーと Value バリューを : コロンで一対にしたものを配置していきます。
左側がキーで右側がバリューとなります。
辞書内でのペアが崩れることはありませんが、ペアの順番は保持されません
キーとバリューの型は違っていても問題ありませんが、キーとして型は揃えて、またバリューでも型を揃えておきます。
キーの重複はエラーとなります。バリューは重複しても問題ありません
itemPrices["Bananas"] のように変数辞書[キー] とすることでオプショナル型のバリューが返ってきます。もしキーの値が見つからなかった場合にクラッシュしないように(Swift制作の方がたが)オプショナル型で返す設定にしていると思われます。(配列についてはオプショナル型ではありません。配列で設定ミスするとクラッシュしてしまいます。)
下のように探索時にキーの答えが見つからなかった場合のDefault を設定しておく方法もあります。(defaultバリューは、Value と型を揃える必要はあります。)

var mangoPrice = itemPrices["Mangoes", default: 500]
print(mangoPrice)

// コンソール表示
500

空の辞書を作っておいて、追加していくことも可能です。
追加するには辞書[キー] = バリューのようにします。

var pets = [String: String]()       // 空の辞書型を作成

pets["Tama"] = "cat"                // キー Tama の辞書を作成
pets["Taro"] = "dog"                // キー Taro の辞書を作成

print(pets)

// コンソール表示
["Tama": "cat", "Taro": "dog"]

上記では入力した順番に反映されていますが、この順番は変わる可能性があります。

辞書を書き換えるにはキーの内容を上書きします。

pets["Tama"] = "rabbit"

print(pets)

// コンソール表示
["Tama": "rabbit", "Taro": "dog"]   // "Tama"が ラビットになってしまった!

順番は変わる可能性がありますが、ループ処理を行い内容を確認することができます。(ループ処理の方法はこの後でご紹介します。)

let imageItems = ["star": "star.jpg",
                  "mountain": "mountain.jpg",
                  "sea": "sea.jpg"]


for (name, item) in imageItems {
    print("The Image of '\(name)' is '\(item)'.")
}

Set型について

Set型 セット型は配列とよく似ていますが、順番が保持されません
Set型は配列とよく似ていますが、重複しません。重複を見つけると自動的に排除します。
作り方は下記のようになります。

let stations: Set = ["Tokyo", "Shinagawa", "Shin Yokohama"]
print(stations)

var morningSet = Set(["Coffee", "Toast", "Egg sand", "Salad", "Miso-soup"])
print(morningSet)

// コンソール表示 順番が変わって表示されます。 
["Shinagawa", "Shin Yokohama", "Tokyo"]
["Salad", "Coffee", "Miso-soup", "Toast", "Egg sand"]

Setも記入方法がいくつかあるので、自分の方法を決めておくと良いかと思います。

順番は変わる可能性がありますが、ループ処理で内容を確認することができます。
(ループ処理の方法はこの後でご紹介します。)

var primes: Set = [2, 3, 5, 7]

for number in primes {
    print(number)
}

// コンソール表示  順番が変わった
2
7
5
3


空のSet は下記のようになります。

var testSet1: Set<Int> = []
print(testSet1)

var testSet2 = Set<String>()
print(testSet2)
                                                // 私はいまだに空セットが覚えられません  でもあなたなら大丈夫です!

Set には順番がないのでArray の .append() はありません.insert() を使用して追加します。

var lunchSet: Set<String> = []
lunchSet.insert("Ramen")
lunchSet.insert("Chahan")
lunchSet.insert("Gyoza")

print(lunchSet)

// コンソール表示
["Chahan", "Gyoza", "Ramen"]

配列に重複の可能性がある場合、Setをはさんで排除することができます

var myDinner: Array = ["egg", "soup", "fish", "egg", "hijiki", "fish"]

var checkDinner = Set(myDinner)       // Set で重複を排除

var myCheckedDinner = Array(checkDinner)    // 配列での作業をしたいのでArrayに戻した 

print(myCheckedDinner)

// コンソール表示
["egg", "fish", "soup", "hijiki"]

Tips 
配列とSet は似ていますが、例で取り上げたようにSet は重複を持たない、また順番を持たないことが違いのひとつです。
また、順番を持たない辞書型とSet型は、システムが記憶するときにHash value という覚えの番号を自分でつけています。そのため検索するときにどんなにデータが多くても一発で取り出せる準備ができています。
いっぽう配列は通常ひとつひとつ順番に検索して確かめていくためデータが多ければ多いほど平均探索時間は長くなります。
それでもループ処理などを考えたときには配列の使い勝手はとても良いので、データ数量や容量などをチェックしながらどの型を使用するのか、またはデータベースシステムを使うのかなどを検討します。


条件分岐について

プログラムの途中で何らかの変数が変わったとき、その変わった状態で次に進む内容を変化させることを条件分岐と言います。

条件分岐の種類
if 〜 :  〜であれば・・・する。(else  その他の場合・・・する。)
guard 〜: 〜でなければ、else・・・する。〜であれば・・・する。
     (falseの場合に先にelseで対応を除外しておく)
switch  : 条件が細かく分かれるときに、ケース毎に返す値をセットする。
     ケースはすべての場合を網羅する必要がある。
三項演算子:if文と同じ内容を1行にシンプルにまとめられる。
      しかし複雑な条件の場合は読み難くなる。


いろいろな条件分岐方法

ケース
本のページ数:bookPages を読書日数:readDays で割って平均読書ページを出しています。これはまだ条件分岐はしていません。

var bookPages = 350
var readDays = 12
var avgPerDay = 0

avgPerDay = bookPages / readDays

print(avgPerDay)
//---------------------------------
29                                  Int での計算のため回答もIntとなる

しかし読書日数を0日とすると 0で割り算をするためエラーとなる。

var bookPages = 350
var readDays = 0
var avgPerDay = 0

avgPerDay = bookPages / readDays   
//----------------------------------
x error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

・読書日数が0日のときもエラーが立たないように条件分岐をおこないます。 
if で条件分岐
を行なってみます。

var bookPages = 350
var readDays = 0
var avgPerDay = 0

if readDays > 0 {
    avgPerDay = bookPages / readDays      // readDays が0より大きい時計算する
} else {
    avgPerDay = 0                             //  readDays が0以下の場合は0を代入する
}
print(avgPerDay)

// -------------------------------------
0

次も if での条件分岐ですが、最初にエラー発生を排除する順番にした条件分岐です

var bookPages = 350
var readDays = 0
var avgPerDay = 0

if readDays <= 0 {
    avgPerDay = 0                    // readDays が0以下の場合は 0を代入する           
} else {
    avgPerDay = bookPages / readDays     // readDays が0より大きい場合に計算する
}
print(avgPerDay)

// -------------------------------------
0

次は switch を使ってみます。
0より大きいか、0 以下であるか の2択の問題なのでこの場合switch を使うメリットは感じにくいです。
switch の条件分岐を 1以上とdefaultの2種類としました。(すべてのケースを網羅する形にする必要があるため、残りのケースをdefaultとしています。)

var bookPages = 350
var readDays = 0
var avgPerDay = 0

switch readDays {
case 1...Int.max :              // 1以上Intの最大値までをひとつの条件区分にしました
    avgPerDay = bookPages / readDays
default :
    avgPerDay = 0               // 最初のcase に当てはまらない場合は 0を代入します。
}
print(avgPerDay)
// -------------------------------------
0


次は三項演算子を使ってみます。
三項演算子1行で条件分岐ができる便利なものです。
ただし複雑な条件分岐になると読みにくさの問題が発生します。

三項演算子の書き方は
・ 変数  =  条件  ?  真の場合に返す内容 : 偽の場合に返す内容
となります。
今回は単純な条件で Yes / No の2択、 回答もシンプルで読みにくさの問題はありません。
・条件は     (readDays > 0)
・真の時は (bookPages / readDays)
・偽の時は 0
で分岐させています。

var bookPages = 350
var readDays = 0
var avgPerDay = 0

avgPerDay = (readDays > 0) ? (bookPages / readDays) : 0

print(avgPerDay)
// -------------------------------------
0

// 条件は(readDays > 0)   
// 真のときに返す内容は (bookPages / readDays) 
// 偽のときに返す内容は 0


次は guard 文で書いて見ます。
guard 文の書き方は
guard  条件式  else {  条件にあてはまらない時に実行する内容 }
  条件式に適合した場合に実行する内容

関数func()を使用していますが内側のguard に注目してください。
guard days > 0 の条件が必要だけれども成り立たない場合は0を返す、成り立つのであれば次の式を計算する、という条件分岐です。
guard文は嫌な条件から早めに退出する方法です。

var bookPages = 350
var readDays = 0
var avgPerDay = 0

func readResult(pages: Int, days: Int, perDay: Int) -> Int {
    guard days > 0 else {                       // days > 0 が成り立たない時
        return 0                             // 0を返す
    }
    return pages / days                         // days > 0 が成り立つときに計算する
}

avgPerDay = readResult(pages: bookPages, days: readDays, perDay: avgPerDay)

print(avgPerDay)

上の例では readResult が 関数名となり、3つの変数Intを受け取りIntを返す設定にしています。
最後にreadResult(pages: bookPages, days: readDays, perDay: avgPerDay) を
実行して、変数 avgPerDay に代入しています。
関数については後ほど行います。

条件式による違い
・if 文は思考の流れにそって文を書きやすく、読みやすい。
・switch 文は、ケース分けを見やすく表現できるが、今回のようなYes / No 区分
 ではメリットがあまりない。
・三項演算子は、1行だけで条件と返す内容が書けるので見通しが良い。
 ただし少し複雑になると読み難くなるため注意が必要。
・guard 文は、条件に適合しない場合にすぐに退出する挙動となるため、実行時
 の安心感がある。(Playground では少しわかりにくかったかもしれません。)
 場合によってif 文とguard 文のどちらを使うか検討してみてください。


ループ文(for in文、 while文)

ループは繰り返し処理のことで、コンピュータが得意とする作業です。
普通のパソコンでも単純な計算式であれば1秒間に何億回もの計算を行なってくれます。
プログラムの論理の流れをつくっているアルゴリズムは①順次②選択③繰り返し、の3つの制御構造の組み合わせでできていると言われますが、ループ文は繰り返しを担う大事な役割をもっています。

forループ

forループは配列 やDictionary型、または 0…10などの設定範囲を最初から順番に取り出しては処理することを繰り返します。

let stations = ["Tokyo", "Shinagawa", "Yokohama", "Odawara", "Atami"]

for station in stations {
    print("Next stop is \(station)")
}
//----------------------------------------
//Next stop is Tokyo
//Next stop is Shinagawa
//Next stop is Yokohama
//Next stop is Odawara
//Next stop is Atami

上の例で for station とありますが、この名前はなんでもよく、このforループの中でだけ使える一時的な変数です。
つまり for s in stations { でもよいですが、変数をstation にすることで、
print("Next stop is \(station)") のように使うことができ
読みやすいコードとなります。
上の例で in stations とありますが、これが配列を持つ定数 stations で、
print("Next stop is \(station)") がループ本体(loop body)で繰り返し実行されるものです。
配列を使用した forループでは、配列の最初(Index: 0)から順にElement を変数に代入してループ本体の処理をしたら次のIndex を取り出します。
そして最後の値を取り出して処理したらループを終了します。
変数stationは作られては実行後に捨てられることを繰り返します


上の例と同じ内容を0から始まる数値を利用して書きます。

let stations = ["Tokyo", "Shinagawa", "Yokohama", "Odawara", "Atami"]

for i in 0..<stations.count {
    print("Next stop is \(stations[i])")
}
//----------------------------------------
//Next stop is Tokyo
//Next stop is Shinagawa
//Next stop is Yokohama
//Next stop is Odawara
//Next stop is Atami

繰り返しの範囲を0からにすると、都合よく配列のIndex に合致します。
ループ変数をi として繰り返し範囲を0から4までの5回とします。
最後のIndexである4は Array.count - 1 なので、範囲は 0..<Array.count  と表現できます。

ループ変数としては、一般に i が使われることが多いです。
(index とか item とか integer とか iterate でしょうか?)
ループ変数を入れ子にした場合など、次のループ変数には、j, k … と順にあてていくことが多いです。

上の例での for ループの動きのイメージ
(1) for に続くi に0..<stations.count が順に代入されます。
  最初は i に0が代入される。
  ループ本体では stations[0] として配列stationsのIndex 0番が使われる。
 print("Next stop is \(stations[0])")の実行が終わると 変数i はクリアされ
 る。
(2) i に1が代入され print("Next stop is \(stations[1])")が実行され終わるとクリ
 アされる。
 続いてi = 2,  i = 3, i =4 が代入され、それぞれループ本体が実行されてはクリア
 される。
(3) i に5が代入されると 0..<stations.count の条件を満たさないのでforループの
  実行をストップ
して、そのあとのコードに移動する。

Tips
例で使われた一時的な変数station や i は forループの中でだけ有効です。
変数の有効範囲スコープと呼びます。(scope とは範囲、領域という意味でrangeと同義語です)
今後、structや class 、そこで使用するプロパティ、メソッドを自分でつくっていくと、このスコープの問題に苦労することもあるかと思います。
最初のうちは、有効範囲は広い方が便利だと感じるかもしれませんが、慣れるにつれて範囲を意識しながら進められるようになります。
また誤って変数を変更しないようにするためや、Appでバグが発生した場合に影響を小さく抑えるためにも、スコープを小さくしておく(アクセスコントロールする)ことが重要とされます。

今までの例だと、出力される文字が同じでしたので、条件分岐を入れて最初の駅と最後の駅は違うアナウンスにしてみましょう。

let stations = ["Tokyo", "Shinagawa", "Yokohama", "Odawara", "Atami"]

for i in 0..<stations.count {
    if i == 0 {
        print("This train is bound for \(stations[stations.count - 1]) from \(stations[0])")
    } else if i == (stations.count - 1) {
        print("We will arrive at \(stations[stations.count - 1]) in a few minutes")
    } else {
        print("Next stop is \(stations[i])")
    }
}

//This train is bound for Atami from Tokyo
//Next stop is Shinagawa
//Next stop is Yokohama
//Next stop is Odawara
//We will arrive at Atami in a few minutes

最初の駅は Index: 0、最後の駅は Index: (stations.count - 1 ) なので、i がこの値になったときだけアナウンスを変えています。
新幹線の自動アナウンスもこれと同じような条件分岐のプログラムが使われていると思います。知りませんけど。。。


九九の掛け算の一の段を出力する
ループ変数 i に範囲1…9が順番に入る

for i in 1...9 {
    print("1 x \(i) = \(1 * i)")
}

// 1 x 1 = 1
// 1 x 2 = 2
// 1 x 3 = 3
// 1 x 4 = 4
// 1 x 5 = 5
// 1 x 6 = 6
// 1 x 7 = 7
// 1 x 8 = 8
// 1 x 9 = 9

九九の掛け算の一の段から九の段までを出力する
2重ループを使用します

ループの動きのイメージ
(1)  ループ変数 i に 1 が入る。
(2) ループ変数 j に 範囲1…9が順に入る。
(3) ループ変数 i に 2 が入る。
(4) ループ変数 j に 範囲1…9が順に入る。
(5) ループ変数 i に 3が入る。
      ~ ~ ~  省略 ~ ~ ~
(6) ループ変数 i に 9 が入る。
(7) ループ変数 j に 範囲1…9が順に入る。 
つまり内側のj のループが1セット終わると次の外側のi のループがひとつ進む、というような動きをします。 

for i in 1...9 {
    for j in 1...9 {
        print("\(i) x \(j) = \(i * j)")
    }
    print(" -*-*-")
}

// 1 x 1 = 1
// 1 x 2 = 2
// 1 x 3 = 3
// 1 x 4 = 4
// 1 x 5 = 5
// 1 x 6 = 6
// 1 x 7 = 7
// 1 x 8 = 8
// 1 x 9 = 9
//  -*-*-
// 2 x 1 = 2
//      〜〜〜省略〜〜〜
// 8 x 9 = 72
//  -*-*-
// 9 x 1 = 9
// 9 x 2 = 18
// 9 x 3 = 27
// 9 x 4 = 36
// 9 x 5 = 45
// 9 x 6 = 54
// 9 x 7 = 63
// 9 x 8 = 72
// 9 x 9 = 81
//  -*-*-

盤面のマスの名前のように出力を調整する
print()の使い方で出力が盤面状になるようにしています。
print("内容", terminator: " ")   の  terminator: " " で " "の中にある文字(スペース)を区切り文字として横並びに出力しています。
内側のj が1セット終わるとprint("")が実行されてから 外側 iがひとつ進みます。
print("") ではterminatorは使用していないので、縦に移動します。 

let boardRow = ["A", "B", "C", "D", "E", "F", "G", "H"]
let boardColumn = ["1", "2", "3", "4", "5", "6", "7", "8"]

for i in 0..<8 {
    for j in 0..<8 {
        print("\(boardRow[j])\(boardColumn[i])", terminator: " ")
    }
    print("")
}

// A1 B1 C1 D1 E1 F1 G1 H1
// A2 B2 C2 D2 E2 F2 G2 H2
// A3 B3 C3 D3 E3 F3 G3 H3
// A4 B4 C4 D4 E4 F4 G4 H4
// A5 B5 C5 D5 E5 F5 G5 H5
// A6 B6 C6 D6 E6 F6 G6 H6
// A7 B7 C7 D7 E7 F7 G7 H7
// A8 B8 C8 D8 E8 F8 G8 H8

ループ内で変数を必要としない場合はアンダーバーを使用します。

// 変数を必要としないループ (変数は使わず回数を重ねたい)

var shake = "Shake, shake"

for _ in 1...3 {        // i などの代わりに _ アンダーバーをセット 
    shake += ", shake"
}

print(shake)

// コンソール表示
Shake, shake, shake, shake, shake

// for in 文に類似したものに forEach メソッドがあります。
var arrayItems = ["りんご", "みかん", "バナナ"]
arrayItems.forEach { item in
print(item)
}
のように使いますが、for in との違いとして、forEach は、continue, breakなどskipはできない(開始したら最後まで回す)、などあるので注意です。

なお、SwiftUI では ForEach()  { ~  in   }  というものがアイテムを画面に表示させる際によく使う手段のひとつです。(forEach と似た動きですが ForEach()はSwiftUI で使うもので view(画面に表す部品)を扱います。)



2次元配列を出力する
for文を入れ子にすると2次元配列の内容を取得するのに都合がよいです。
下の例では、2次元配列の変数をmyBoardとしています。
(1) for row in myBoard  の row に myBoard の1つ目の要素(配列)が入ります
(2) for col in row のcol に (1)で取り出したrow(配列)の1つ目の要素
   (String: "A1")が入ります。 
(3) for col in row のcol に (1)で取り出したrow(配列)の2つ目の要素
   (String: "B1")が入ります。
       〜〜〜 同じように続く〜〜〜
(4)  for col in row のcol に (1)で取り出したrow(配列)の8つ目の要素
   (String: "H1")が入ります。
(5) for row in myBoard  の row に myBoard の2つ目要素(配列)が入ります。
(6) for col in row のcol に (5)で取り出したrow(配列)の1つ目の要素
   (String: "A2")が入ります。 
(7) 同じように最後まで続きます。

最初にfor row に 配列["A1",  〜 "H1"] が入るところに注意してください。

var myBoard = [
     ["A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1"],
     ["A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2"],
     ["A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3"],
     ["A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4"],
     ["A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5"],
     ["A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6"],
     ["A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7"],
     ["A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8"]
]

for row in myBoard {
    for col in row {
        print(col, terminator: " ")
    }
    print("")
}

// A1 B1 C1 D1 E1 F1 G1 H1
// A2 B2 C2 D2 E2 F2 G2 H2 
// A3 B3 C3 D3 E3 F3 G3 H3 
// A4 B4 C4 D4 E4 F4 G4 H4
// A5 B5 C5 D5 E5 F5 G5 H5
// A6 B6 C6 D6 E6 F6 G6 H6
// A7 B7 C7 D7 E7 F7 G7 H7
// A8 B8 C8 D8 E8 F8 G8 H8

条件に当てはまる要素だけ処理、条件に当てはまらない場合は無視してループの最初に戻る(continue コンティニュー)
ループ文の中でcontinue を読むと、ループの先頭に戻るという動きができます。
下の例では、
(1) let prefectures として都道府県の配列を持つ定数がある。
(2) var ken として文字列が入れられる空の配列をセットする。
(3) for pref で in prefectures の配列要素を一つづつ取り込む。
(4) pref.hasSuffix("県") で最後に県がつくかどうかを Bool型で返して false かど
   うかをチェックする。
       ※hasSuffix()は配列の持つメソッドで、最後の文字(今回は最後の一文字)が
          合致するかどうか、Bool型を返します。
(5) 上の(4)でfalseとなった場合("県"が最後の文字でない)continue が働く。
      continueが働くと for pref に戻りpref に次のprefectures要素が入り
   
ます。
    上の(4)でtrue となった場合("県"が最後の文字である)if 文の内容が
  スキップ
されるためcontinue は働かず、ken.append(pref) により
  変数kenにそのprefが追加される。

let prefectures = ["北海道", "青森県", "栃木県", "埼玉県", "東京都", "静岡県", "滋賀県", "大阪府", "京都府"]

var ken = [String]()

for pref in prefectures {
    if pref.hasSuffix("県") == false {
        continue
    }
    ken.append(pref)
}

print(ken)

//  ["青森県", "栃木県", "埼玉県", "静岡県", "滋賀県"]

上記をcontinue を使わずに書いてみます。
最初にtrueの場合に処理をして falseの場合は何もせずにループに戻ります。
こちらのほうがわかりやすいですね。

var ken = [String]()

for pref in prefectures {
    
    if pref.hasSuffix("県") == true {
        ken.append(pref)
    }
}

print(ken)

//  ["青森県", "栃木県", "埼玉県", "静岡県", "滋賀県"]

ある条件になったらループを抜け出す(break ブレーク) 
ループ文の中でbreak を読むと、ループを抜け出して次の作業に移ります。
下の例では、
(1) var  myLikeNumbers として空の配列を用意しておく。
(2) forループで変数 i が1から100,000までの繰り返し処理を開始する。
(3) 条件式として i.isMultiple(of: 3) && i.isMultiple(of: 7) を設定。
   isMultiple(of: 数値) はIntの持つメソッドでその数値の倍数かどうかをBool型
  で返す。(%を使った余りを使う方法でもできますね!)
     この例では条件式を&&でつないでいる。&& で条件式をつなぐと、両方とも
      trueを返す場合にだけ最終的にtrueを返す式となる。
      今回の場合、3の倍数でありかつ7の倍数の場合にtrueを返す。
(4) 条件が適合すれば myLikeNumbers に加える。
(5) myLikeNumbers の要素数をカウントして10になっていれば、breakでループ処理を終了し、次の処理 print(myLikeNumbers) に移る。

var myLikeNumbers = [Int]()

for i in 1...100_000 {
    if i.isMultiple(of: 3) && i.isMultiple(of: 7) {
        myLikeNumbers.append(i)
        
        if myLikeNumbers.count == 10 {
            break
        }
    }
}

print(myLikeNumbers)

// [21, 42, 63, 84, 105, 126, 147, 168, 189, 210]

while 文

while文はwhileに続く条件文がtrueの間、処理を繰り返します。
上記のコードをwhileを使って書いて見ます。
下の例では、
(1) var  myLikeNumbers として空の配列を置く。
(2) チェックする数値 i = 1として1から順に条件に適合するか調べていく。
(3) while文に続く条件式は、myLikeNumbers.count < 10 として
  10個の答えが見つかるまでは続ける。
(4) 条件式として i.isMultiple(of: 3) && i.isMultiple(of: 7) を設定。
    ・条件に適合すればmyLikeNumbers  に 数値 i を追加する。
(5) i に1を足して、while文に戻る。 
(6) while文を開始する際にmyLikeNumbers.countが10以内か確認して
      オーバーしていなければ継続、していれば中止する。

var myLikeNumbers = [Int]()
var i = 1

while myLikeNumbers.count < 10 {
    if i.isMultiple(of: 3) && i.isMultiple(of: 7) {
        myLikeNumbers.append(i)
    }
    i += 1
}


print(myLikeNumbers)


// [21, 42, 63, 84, 105, 126, 147, 168, 189, 210]

ループ内でcontinue が読まれると後の実行文は無視してループの続きをおこないます。
ループ内でbreakが読まれると、ループ処理は終了して、次のコードに移動します。
while文は繰り返し回数が分らない時に使われることが多いです。
while文は永遠に終わらないコード(無限ループ)を簡単にできてしまうので注意しましょう。
playgroundで無限ループを走らせてしまったら左下の🔲ボタンで止めてください。

まとめ

配列
コレクション型はプログラミングで重要な役割を果たしますが、特に配列は基本となります。
調べてみるといろいろな発見もあると思いますので、少しづつ深掘りしてみてください。

条件分岐
条件文は、いろいろな書き方がありますが、書きやすさや読みやすさを考えて、使い分けをしていきたいですね。

ループ
ループ文はアルゴリズムの基本の一つです。
間違えても良いので、自分でいろいろと試してみると発見があって習熟も早くなると思います。

次回第6回内容   関数、Type(構造体、クラス、enum)です。
         よろしくお願いします。
           

この記事が気に入ったらサポートをしてみませんか?