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

【Swift】Appleの新言語「Swift」のリファレンスを読む(3) - Collection Types

注意

  • あくまでメモ書きなので細かい部分を端折りますし、色々間違ってるかもしれません。ちゃんとした内容は原文を読んでね。
  • コード例は基本的に原文からそのまま引用していますが、ちょっとした注釈をつけたり、統合したりしています。
  • SyntaxHighlighterが対応してないので微妙に読みにくいです。SyntaxHighlighterはこちらのものを使用させて頂いてます。
  • 他言語にそっくりな部分でも指摘しない。(自戒)

Collection Types(コレクション)

SwiftにはArrayとDictionaryと言うコレクションが標準で用意されているようです。

この二つのコレクションはジェネリクスによって型安全が保証されています。

Arrays

Arrayの宣言方法はArray<SomeType>、もしくはSomeType[]の二つが用意されています。

Arrayに初期値を与える場合は以下のように記述します。

var shoppingList: String[] = ["Eggs", "Milk"]
// shoppingList has been initialized with two initial items

// 明示的に型を宣言する必要はない
var shoppingList = ["Eggs", "Milk"]

また、この注釈は地味にかなり重要な気がします。

The shoppingList array is declared as a variable (with the var introducer) and not a constant (with the let introducer) because more items are added to the shopping list in the examples below.

letではなくvarを指定しているのは下記の例で要素を追加するためです、みたいなことが書かれているんですが、これはつまり「letで宣言したら要素の変更が行えない」ってことなんでしょうか?

そうすることのメリットも意義もわかるけど、そうならそうと明記して欲しいですね。

さて、Arrayが所持している要素へのアクセス方法は以下の通りです。デフォルトでも中々の高機能っぷりです。いいなぁ。

// Array#countで要素数を取得
println("The shopping list contains \(shoppingList.count) items.")
// prints "The shopping list contains 2 items."


// Array#isEmptyで要素の有無をチェック
if shoppingList.isEmpty {
    println("The shopping list is empty.")
} else {
    println("The shopping list is not empty.")
}
// prints "The shopping list is not empty."


// Array#appendで要素の追加
shoppingList.append("Flour")
// shoppingList now contains 3 items, and someone is making pancakes


// 複合代入演算子でも要素を追加できる
shoppingList += "Baking Powder"
// shoppingList now contains 4 items


// 配列をまとめてappend
shoppingList += ["Chocolate Spread", "Cheese", "Butter"]
// shoppingList now contains 7 items


// Array[n]で要素の取得
var firstItem = shoppingList[0]
// firstItem is equal to "Eggs"


// Array[n] = SomeTypeで要素の置き換え
shoppingList[0] = "Six eggs"
// the first item in the list is now equal to "Six eggs" rather than "Eggs"


// 添え字の部分にRange Operatorを使うとその範囲を対象に出来る
// この例だと4, 5, 6を対象にし、なおかつ2つの要素しか代入していないため、1つ削除される形になる
// (それが許されるのは便利なのか危なっかしいのか…。)
shoppingList[4...6] = ["Bananas", "Apples"]
// shoppingList now contains 6 items 


// Array#insert(atIndex:)で要素の挿入
shoppingList.insert("Maple Syrup", atIndex: 0)
// shoppingList now contains 7 items
// "Maple Syrup" is now the first item in the list


// Array#removeAtIndexで要素の削除
let mapleSyrup = shoppingList.removeAtIndex(0)
// the item that was at index 0 has just been removed
// shoppingList now contains 6 items, and no Maple Syrup
// the mapleSyrup constant is now equal to the removed "Maple Syrup" string


// Array#removeLastで最後の要素の削除
let apples = shoppingList.removeLast()
// the last item in the array has just been removed
// shoppingList now contains 5 items, and no cheese
// the apples constant is now equal to the removed "Apples" string

for-in構文を使うことでイテレート出来る。

for item in shoppingList {
    println(item)
}
// Six eggs
// Milk
// Flour
// Baking Powder
// Bananas

enumerate関数にArrayを入れるとindexとそれに対応したSomeTypeのタプルを返してもらえる。

for (index, value) in enumerate(shoppingList) {
    println("Item \(index + 1): \(value)")
}
// Item 1: Six eggs
// Item 2: Milk
// Item 3: Flour
// Item 4: Baking Powder
// Item 5: Bananas

ArrayのコンストラクタSwift的にはinitializerと呼んで欲しいようだ)の色々。

// 空のインスタンスを作成
var someInts = Int[]()
println("someInts is of type Int[] with \(someInts.count) items.")
// prints "someInts is of type Int[] with 0 items."

// appendした後に空っぽにする
someInts.append(3)
// someInts now contains 1 value of type Int
someInts = []
// someInts is now an empty array, but is still of type Int[]

// (これは名前つき引数なのかな?)
// countとrepeatedValueを指定することで同じ要素を複数生成出来る
var threeDoubles = Double[](count: 3, repeatedValue: 0.0)
// threeDoubles is of type Double[], and equals [0.0, 0.0, 0.0]

// 型さえ合っていればArray同士は結合出来るので、こんなことも出来る。
var anotherThreeDoubles = Array(count: 3, repeatedValue: 2.5)
// anotherThreeDoubles is inferred as Double[], and equals [2.5, 2.5, 2.5]

var sixDoubles = threeDoubles + anotherThreeDoubles
// sixDoubles is inferred as Double[], and equals [0.0, 0.0, 0.0, 2.5, 2.5, 2.5]

Dictionaries

DictionaryはDictionary<KeyType, ValueType>と宣言します。KeyTypeはhashableでなければならないそうです。これはつまり、Keyにする値はそれぞれ一意性を保ってくれ、もしくは、他言語におけるequalsに当たるものをちゃんとオーバーライドしてくれ、程度の意味だと思います。(原文:it must provide a way to make itself uniquely representable.)

「Keyの重複は許さないよ」と素直に書かないのは何かしらの事情があるのかもしれませんが、今の段階ではわからないので深く考えないでおきます。

Dictionaryにも初期値を与えることが出来ます。

var airports: Dictionary<String, String> = ["TYO": "Tokyo", "DUB": "Dublin"]

// 当然型の宣言は必要ない
var airports = ["TYO": "Tokyo", "DUB": "Dublin"]

ここの注釈にも「わざわざvarを指定した理由」が書かれています。やきもきしますね。

Dictionaryのアクセス方法は以下の通りです。基本的にはArrayと大体一緒ですが、optionalをフル活用していて中々便利そうです。

// Dictionary#countで要素数の取得
println("The dictionary of airports contains \(airports.count) items.")
// prints "The dictionary of airports contains 2 items."


// Dictionary[KeyType] = valueで要素の追加 / 変更
airports["LHR"] = "London"
// the airports dictionary now contains 3 items

airports["LHR"] = "London Heathrow"
// the value for "LHR" has been changed to "London Heathrow"

// Dictionary#updateValue(forKey:)で要素を変更しつつ変更前のValueType?をもらえる
// forKeyに指定したKeyがDictionaryにない場合はDictionaryにKey / Valueを追加し、oldValueにはnilを返す
if let oldValue = airports.updateValue("Dublin International", forKey: "DUB") {
    println("The old value for DUB was \(oldValue).")
}
// prints "The old value for DUB was Dublin."


// Dictionary[KeyType]で要素の取得
// 要素はoptionalな型で返ってくる
if let airportName = airports["DUB"] {
    println("The name of the airport is \(airportName).")
} else {
    println("That airport is not in the airports dictionary.")
}
// prints "The name of the airport is Dublin International."


// Dictionary[KeyType] = nilで要素の削除
airports["APL"] = nil
// APL has now been removed from the dictionary


// Dictionary#removeValueForKeyで要素の削除 + ValueType?の取得
if let removedValue = airports.removeValueForKey("DUB") {
    println("The removed airport's name is \(removedValue).")
} else {
    println("The airports dictionary does not contain a value for DUB.")
}
// prints "The removed airport's name is Dublin International."

for-in構文によるイテレートも当然サポートされています。タプルの賜物ですね。

for (airportCode, airportName) in airports {
    println("\(airportCode): \(airportName)")
}
// TYO: Tokyo
// LHR: London Heathrow

Dictionary#keysやDictionary#valuesといったプロパティにアクセスすることでKeyもしくはValueのみをイテレートすることも可能です。

for airportCode in airports.keys {
    println("Airport code: \(airportCode)")
}
// Airport code: TYO
// Airport code: LHR

for airportName in airports.values {
    println("Airport name: \(airportName)")
}
// Airport name: Tokyo
// Airport name: London Heathrow

ちなみに「Dictionaryは順序付けされていないコレクション(unordered collection)だから要素の順番は保証しないよ」とのことです。

また、KeyやValueをArrayとして取得したい場合はわざわざArrayのインスタンスを作るよう記述されています。Dictionary#keysやDictionary#valuesの実体はArrayじゃないってことなんですかね?

let airportCodes = Array(airports.keys)
// airportCodes is ["TYO", "LHR"]

let airportNames = Array(airports.values)
// airportNames is ["Tokyo", "London Heathrow"]

空のDictionaryを作成する方法は以下の通りです。

var namesOfIntegers = Dictionary<Int, String>()
// namesOfIntegers is an empty Dictionary<Int, String>

namesOfIntegers[16] = "sixteen"
// namesOfIntegers now contains 1 key-value pair
namesOfIntegers = [:]
// namesOfIntegers is once again an empty dictionary of type Int, String

Mutability of Collections

ArrayとDictionaryのsizeは基本的に可変です。が、定数として宣言したらそれは不変になります。(原文:if you assign an array or a dictionary to a constant, that array or dictionary is immutable, and its size cannot be changed. )

また、不変のコレクションとして宣言するとそれに合わせてパフォーマンスを最適化するようにしているみたいです。

と言うわけで、やっぱりletでArray or Dictionaryを宣言すると要素はいじれなくなるみたいですね。これ一番最初のセクションに持っていったほうが良かったんじゃないですかね?