集合类型
Swift 提供了三种主要的集合类型,所谓的数组、合集还有字典,用来储存值的集合。数组是有序的值的集合。合集是唯一值的无序集合。字典是无序的键值对集合。
Swift 中的数组、合集和字典总是明确能储存的值的类型以及它们能储存的键。就是说你不会意外地插入一个错误类型的值到集合中去。它同样意味着你可以从集合当中取回确定类型的值。
注意
Swift 的数组、合集和字典是以泛型集合实现的。要了解更多关于泛型类型和集合,参见泛型。
集合的可变性
如果你创建一个数组、合集或者一个字典,并且赋值给一个变量,那么创建的集合就是可变的。这意味着你随后可以通过添加、移除、或者改变集合中的元素来改变(或者说异变)集合。如果你把数组、合集或者字典赋值给一个常量,则集合就成了不可变的,它的大小和内容都不能被改变。
注意
在集合不需要改变的情况下创建不可变集合是个不错的选择。这样做可以允许 Swift 编译器优化你创建的集合的性能。
数组
数组以有序的方式来储存相同类型的值。相同类型的值可以在数组的不同地方多次出现。
注意
Swift 的 Array类型被桥接到了基础框架的 NSArray类上。
更多关于与基础框架和 Cocoa 一同使用 Array的信息,参考与 Cocoa 和 Objective-C 一起使用 Swift(Swift 3)(官方链接) 。
数组类型简写语法
Swift 数组的类型完整写法是 Array<Element>, Element是数组允许存入的值的类型。你同样可以简写数组的类型为 [Element]。尽管两种格式功能上相同,我们更推荐简写并且全书涉及到数组类型的时候都会使用简写。
创建一个空数组
你可以使用确定类型通过初始化器语法来创建一个空数组:
var someInts = [Int]() print("someInts is of type [Int] with \(someInts.count) items.") // prints "someInts is of type [Int] with 0 items."
注意 someInts变量的类型通过初始化器的类型推断为 [Int]。
相反,如果内容已经提供了类型信息,比如说作为函数的实际参数或者已经分类了的变量或常量,你可以通过空数组字面量来创建一个空数组,它写作[ ](一对空方括号):
someInts.append(3) // someInts now contains 1 value of type Int someInts = [] // someInts is now an empty array, but is still of type [Int]
使用默认值创建数组
Swift 的 Array类型提供了初始化器来创建确定大小且元素都设定为相同默认值的数组。你可以传给初始化器对应类型的默认值(叫做 repeating)和新数组元素的数量(叫做 count):
var threeDoubles = Array(repeating: 0.0, count: 3) // threeDoubles is of type [Double], and equals [0.0, 0.0, 0.0]
通过连接两个数组来创建数组
你可以通过把两个兼容类型的现存数组用加运算符( +)加在一起来创建一个新数组。新数组的类型将从你相加的数组里推断出来:
var anotherThreeDoubles = Array(repeating: 2.5, count: 3) // anotherThreeDoubles is of type [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]
使用数组字面量创建数组
你同样可以使用数组字面量来初始化一个数组,它是一种以数组集合来写一个或者多个值的简写方式。数组字面量写做一系列的值,用逗号分隔,用方括号括起来:
[value 1, value 2, value 3]
下边的示例创建了一个叫做 shoppingList的数组来储存 String值:
var shoppingList: [String] = ["Eggs", "Milk"] // shoppingList has been initialized with two initial items
shoppingList变量被声明为“字符串值的数组”写做 [String] 。由于这个特定的数组拥有特定的String值类型,它就只能储存 String值。这里, shoppingList被两个 String值( "Eggs"和 "Milk")初始化,写在字符串字面量里。
注意
数组 shoppingList被声明为变量(用 var提示符)而不是常量(用 let提示符)因为更多的元素会在下边的栗子中添加到数组当中。
在这种情况下,数组的字面量只包含两个 String值。这与 shoppingList变量声明类型一致(一个只能储存 String值的数组),因此用数组字面量作为 shoppingList以两个初始元素初始化的方式是被允许的。
依托于 Swift 的类型推断,如果你用包含相同类型值的数组字面量初始化数组,就不需要写明数组的类型。 shoppingList的初始化可以写得更短:
var shoppingList = ["Eggs", "Milk"]
因为数组字面量中的值都是相同的类型,Swift 就能够推断 [String]是 shoppingList变量最合适的类型。
访问和修改数组
你可以通过数组的方法和属性来修改数组,或者使用下标脚本语法。
要得出数组中元素的数量,检查只读的 count属性:
print("The shopping list contains \(shoppingList.count) items.") // prints "The shopping list contains 2 items."
使用布尔量 isEmpty属性来作为检查 count属性是否等于 0的快捷方式:
if shoppingList.isEmpty { print("The shopping list is empty.") } else { print("The shopping list is not empty.") } // prints "The shopping list is not empty."
你可以通过 append(_:)方法给数组末尾添加新的元素:
shoppingList.append("Flour") // shoppingList now contains 3 items, and someone is making pancakes
另外,可以使用加赋值运算符 ( +=)来在数组末尾添加一个或者多个同类型元素:
shoppingList += ["Baking Powder"] // shoppingList now contains 4 items shoppingList += ["Chocolate Spread", "Cheese", "Butter"] // shoppingList now contains 7 items
通过下标脚本语法来从数组当中取回一个值,在紧跟数组名后的方括号内传入你想要取回的值的索引:
var firstItem = shoppingList[0] // firstItem is equal to "Eggs"
注意
数组中的第一个元素的索引为 0,不是 1 .Swift 中的数组都是零开头的。
你可以使用下标脚本语法来改变给定索引中已经存在的值:
shoppingList[0] = "Six eggs" // the first item in the list is now equal to "Six eggs" rather than "Eggs"
你同样可以使用下标脚本语法来一次改变一个范围的值,就算替换与范围长度不同的值的合集也行。下面的栗子替换用 "Bananas"和 "Apples"替换 "Chocolate Spread", "Cheese", and "Butter":
shoppingList[4...6] = ["Bananas", "Apples"] // shoppingList now contains 6 items
注意
你不能用下标脚本语法来追加一个新元素到数组的末尾。
要把元素插入到特定的索引位置,调用数组的 insert(_:at:)方法:
shoppingList.insert("Maple Syrup", at: 0) // shoppingList now contains 7 items // "Maple Syrup" is now the first item in the list
调用 insert(_:at:)方法插入了一个新元素值为 "Maple Syrup"到 shopping list 的最前面,通过明确索引位置为 0 .
类似地,你可以使用 remove(at:)方法来移除一个元素。这个方法移除特定索引的元素并且返回它(尽管你不需要的话可以无视返回的值):
let mapleSyrup = shoppingList.remove(at: 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
注意
如果你访问或者修改一个超出数组边界索引的值,你将会触发运行时错误。你可以在使用索引前通过对比数组的 count属性来检查它。除非当 count为 0(就是说数组为空),否则最大的合法索引永远都是 count - 1,因为数组的索引从零开始。
当数组中元素被移除,任何留下的空白都会被封闭,所以索引 0 的值再一次等于 "Six eggs":
firstItem = shoppingList[0] // firstItem is now equal to "Six eggs"
如果你想要移除数组最后一个元素,使用 removeLast()方法而不是 remove(at:)方法以避免查询数组的 count属性。与 remove(at:)方法相同, removeLast()返回删除了的元素:
let apples = shoppingList.removeLast() // the last item in the array has just been removed // shoppingList now contains 5 items, and no apples // the apples constant is now equal to the removed "Apples" string
遍历一个数组
循环来遍历整个数组中值的合集:
for item in shoppingList { print(item) } // Six eggs // Milk // Flour // Baking Powder // Bananas
如果你需要每个元素以及值的整数索引,使用 enumerated()方法来遍历数组。 enumerated()方法返回数组中每一个元素的元组,包含了这个元素的索引和值。你可以分解元组为临时的常量或者变量作为遍历的一部分:
for (index, value) in shoppingList.enumerated() { print("Item \(index + 1): \(value)") } // Item 1: Six eggs // Item 2: Milk // Item 3: Flour // Item 4: Baking Powder // Item 5: Bananas
关于 for-in循环的更多内容,见For-in循环。
合集[1]
合集将同一类型且不重复的值无序地储存在一个集合当中。当元素的顺序不那么重要的时候你就可以使用合集来代替数组,或者你需要确保元素不会重复的时候。
注意
Swift 的 Set类型桥接到了基础框架的 NSSet类上。
更多关于与基础框架和 Cocoa 一起使用 Set的信息,见与 Cocoa 和 Objective-C 一起使用 Swift(Swift 3)。
Set 类型的哈希值
为了能让类型储存在合集当中,它必须是可哈希的——就是说类型必须提供计算它自身哈希值的方法。哈希值是Int值且所有的对比起来相等的对象都相同,比如 a == b,
它遵循 a.hashValue == b.hashValue。
所有 Swift 的基础类型(比如 String, Int, Double, 和 Bool)默认都是可哈希的,并且可以用于合集或者字典的键。没有关联值的枚举成员值(如同枚举当中描述的那样)同样默认可哈希。
注意
你可以使用你自己自定义的类型作为合集的值类型或者字典的键类型,只要让它们遵循 Swift 基础库的 Hashable协议即可。遵循 Hashable协议的类型必须提供可获取的叫做 hashValue的 Int属性。通过 hashValue属性返回的值不需要在同一个程序的不同的执行当中都相同,或者不同程序。
因为 Hashable协议遵循 Equatable,遵循的类型必须同时一个“等于”运算符 ( ==)的实现。 Equatable协议需要任何遵循 ==的实现都具有等价关系。就是说, ==的实现必须满足以下三个条件,其中 a, b, 和 c是任意值:
- a == a (自反性)
- a == b 意味着 b == a (对称性)
- a == b && b == c 意味着 a == c (传递性)
更多对协议的遵循信息,见协议。
合集类型语法
Swift 的合集类型写做 Set<Element>,
这里的Element
是合集要储存的类型。不同与数组,合集没有等价的简写。
创建并初始化一个空合集
你可以使用初始化器语法来创建一个确定类型的空合集:
var letters = Set<Character>() print("letters is of type Set<Character> with \(letters.count) items.") // prints "letters is of type Set<Character> with 0 items."
注意
letters变量的类型被推断为 Set<Character>,基于初始化器的类型。
另外,如果内容已经提供了类型信息,比如函数的实际参数或者已经分类的变量常量,你就可以用空的数组字面量来创建一个空合集:
letters.insert("a") // letters now contains 1 value of type Character letters = [] // letters is now an empty set, but is still of type Set<Character>
使用数组字面量创建合集
你同样可以使用数组字面量来初始化一个合集,算是一种写一个或者多个合集值的快捷方式。
下边的栗子创建了一个叫做 favoriteGenres的合集来储存 String值:
var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"] // favoriteGenres has been initialized with three initial items
favoriteGenres变量被声明为“ String值的合集”,写做 Set<String>。由于这个合集已经被明确值类型为 String,它只允许储存 String值。这时,合集 favoriteGenres用三个写在数组字面量中的 String值 ( "Rock", "Classical", 和 "Hip hop")初始化。
注意
合集 favoriteGenres作为变量(用 var标记)而不是常量(用 let标记)是因为元素会在下边的栗子中添加和移除。
合集类型不能从数组字面量推断出来,所以 Set类型必须被显式地声明。总之,由于 Swift 的类型推断,你不需要在使用包含相同类型值的数组字面量初始化合集的时候写合集的类型。 favoriteGenres 的初始化可以写的更短一些:
var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]
由于数组字面量中所有的值都是相同类型的,Swift 就可以推断 Set<String>是 favoriteGenres变量的正确类型。
访问和修改合集
你可以通过合集的方法和属性来访问和修改合集。
要得出合集当中元素的数量,检查它的只读 count属性:
print("I have \(favoriteGenres.count) favorite music genres.") // prints "I have 3 favorite music genres."
使用布尔量 isEmpty属性作为检查 count属性是否等于 0的快捷方式:
if favoriteGenres.isEmpty { print("As far as music goes, I'm not picky.") } else { print("I have particular music preferences.") } // prints "I have particular music preferences."
你可通过调用 insert(_:)方法来添加一个新的元素到合集:
favoriteGenres.insert("Jazz") // favoriteGenres now contains 4 items
你可以通过调用合集的 remove(_:)方法来从合集当中移除一个元素,如果元素是合集的成员就移除它,并且返回移除的值,如果合集没有这个成员就返回 nil。另外,合集当中所有的元素可以用 removeAll()一次移除。
if let removedGenre = favoriteGenres.remove("Rock") { print("\(removedGenre)? I'm over it.") } else { print("I never much cared for that.") } // prints "Rock? I'm over it."
要检查合集是否包含了特定的元素,使用 contains(_:)方法。
if favoriteGenres.contains("Funk") { print("I get up on the good foot.") } else { print("It's too funky in here.") } // prints "It's too funky in here."
遍历合集
你可以在 for-in循环里遍历合集的值。
for genre in favoriteGenres { print("\(genre)") } // Classical // Jazz // Hip hop
更多关于 for-in循环,见 For-in 循环。
Swift 的 Set类型是无序的。要以特定的顺序遍历合集的值,使用 sorted()方法,它把合集的元素作为使用 < 运算符排序了的数组返回。
for genre in favoriteGenres.sorted() { print("\(genre)") } // Classical // Hip hop // Jazz
执行合集操作
你可以高效地执行基本地合集操作,比如合并两个合集,确定两个合集共有哪个值,或者确定两个合集是否包含所有、某些或没有相同的值。
基本合集操作
下边的示例描述了两个合集—— a和 b——在各种合集操作下的结果,用阴影部分表示。
- 使用 intersection(_:)方法来创建一个只包含两个合集共有值的新合集;
- 使用 symmetricDifference(_:)方法来创建一个只包含两个合集各自有的非共有值的新合集;
- 使用 union(_:)方法来创建一个包含两个合集所有值的新合集;
- 使用 subtracting(_:)方法来创建一个两个合集当中不包含某个合集值的新合集。
let oddDigits: Set = [1, 3, 5, 7, 9] let evenDigits: Set = [0, 2, 4, 6, 8] let singleDigitPrimeNumbers: Set = [2, 3, 5, 7] oddDigits.union(evenDigits).sorted() // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] oddDigits.intersection(evenDigits).sorted() // [] oddDigits.subtracting(singleDigitPrimeNumbers).sorted() // [1, 9] oddDigits.symmetricDifference(singleDigitPrimeNumbers).sorted() // [1, 2, 9]
合集成员关系和相等性
下面的示例描述了三个合集—— a, b和 c——用重叠区域代表合集之间值共享。合集 a是合集 b的超集,因为 a包含 b的所有元素。相反地,合集 b是合集 a的子集,因为 b的所有元素被 a包含。合集 b和合集 c是不相交的,因为他们的元素没有相同的。
- 使用“相等”运算符 ( == )来判断两个合集是否包含有相同的值;
- 使用 isSubset(of:) 方法来确定一个合集的所有值是被某合集包含;
- 使用 isSuperset(of:)方法来确定一个合集是否包含某个合集的所有值;
- 使用 isStrictSubset(of:) 或者 isStrictSuperset(of:)方法来确定是个合集是否为某一个合集的子集或者超集,但并不相等;
- 使用 isDisjoint(with:)方法来判断两个合集是否拥有完全不同的值。