集合类型(Collection Types)

213 阅读20分钟

swift提供了三个主要的集合类型,是arrays,sets,和dictionaries,来存储值的集合。Arrays是有序的值的集合。Sets是无序的不重复的值的集合。Dictionaries是无序的key-value联系的集合。


swift中Arrays,sets,和dictionaries通常对于他们存储的值和键的类型是明确的。这意味着你不能错误的将错的类型的值插入到集合中。也意味着你可以确定你从集合中获取的值的类型。

swift的array,set,和字典类型实现的是泛型集合。更多关于泛型和集合的信息,查看Generics

可变集合(Mutability of Collections)

如果你创建了一个array,一个set或者一个dictionary,并且把它分配给了一个变量,创建的集合就是可变的。这意味着,你可以在创建完之后进行添加,移除或者修改集合中的对象来改变或者修改集合。如果你把array,set,或dictionary分配给了常量,那么集合是不可修改的,它的尺寸大小和内容是不可变的。

当集合不需要修改时都创建不可变集合是一个好的习惯。这样做让你推理自己的代码更简单并且可以使swift编译器优化你创建的集合的执行。

Arrays

一个array在一个有序的列表里存储了相同类型的值。在不同的位置相同的值可以出现多次。

swift的array类型和Foundation的NSArray类是桥接的。

更多关于使用Foundation和Cocoa的Array的信息,查看Bridging Between Array and NSArray

数组类型简写语法(Array Type Shorthand Syntax)

swift的array类型的全写是Array<Element>,Element是array允许存储的值的类型。你也可以用简写的形式像[Element]来写array的类型。虽然两中方式功能是一样的,当引用array类型时简写形式更倾向并且参考中也用的。

创建一个空数组(Creating an Empty Array)

你可以使用初始化语法创建一个确切类型的空数组:

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]

使用默认值创建一个数组(Creating an Array with a Default Value)

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]

通过同时添加两个数组创建一个数组(Creating an Array By Adding Two Arrays Together)

你可以通过使用加号(+)将两个存在的类型兼容的数组添加到一起来创建一个新的数组。新数组的类型由你家到一起的两个数组的类型推导出来:

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]

使用数组字面量创建一个数组(Creating  an Array with an Array Literal)

也可以使用数组字面量老初始化一个数组,是把一个或多个值写成数组集合的简便方法。数组字面量写成一个列表的值,用逗号分隔,两边有一对方括号:

[value 1, value 2, value 3]

下面的例子创建了一个存储string值名为shoppingList的数组:

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

shoppingList变量声明为“一个string值的数组”,写作[String]。因为这个特殊的数组有一个指定的String的值的类型,它只能用来存储String值。这里,shoppingList数组用两个String值(“Eggs”和“Milk”)初始化,使用数组字面量写的。

shoppingList数组声明成了变量(使用var)而不是不是常量(使用let)因为在下面的李忠有更多对象加到shoppinglist中。

这种情况,数组字面量含有两个String值没有别的。这跟变量shoppinglist的声明类型匹配(一个数组只能有String值),所以数组字面量分配可以作为使用两个初始对象初始化shoppingList的方式。

因为swift类型推导,如果你使用包含相同类型的数组字面量初始化它就不用写出数组的类型。shoppingList的初始化已经写成了简便的形式:

var shoppingList = ["Eggs", "Milk"]

因为数组中全部的值是相同类型的,swift可以推导出[String]是shoppingList变量使用的正确的类型。

访问和修改数组(Accessing and Modifying an Array)

通过它的方法和属性可以访问和修改数组,或者通过使用下标语法。

查看它的只读属性cout来获取数组中对象的数目:

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中的数组总是0开始的下标。

你可以使用下标语法改变给定索引位置已存在的值:

shoppingList[0] = "Six eggs"
// the first item in the list is now equal to "Six eggs" rather than "Eggs"

当你使用下标语法时,你指定的下标应该是有效的。例如,写shoppingList[shoppingList.cout] = “Salt”尝试在数组最后添加一个对象导致运行时错误。

你也可以使用下表语法一次性来修改一个区间的值,即使替换的设置的值和要替换的区间的长度不同。下面的例子用”Bananas”和“Apples”踢馆了“Chocolate Spread“,”Cheese“,和”Butter“:

shoppingList[4...6] = ["Bananas", "Apples"]
// shoppingList now contains 6 items

要在指定的索引插入对象,调用array的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:)方法在shopping列表的开头插入新的值”Maple Syrup“,使用下标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-1,因为数组时用0开始索引的--不过,当count是0时(代表数组是空的),没有有效的下标。

当对象被删除时数组中任何的间隔都被关掉,所以索引0的值又变成等于”Six eggs“:

firstItem = shoppingList[0]
// firstItem is now equal to "Six eggs"

如果你想删除数组中最后的对象,为了避免需要查询数组的count属性使用removeLast方法而不用remove(at:)方法。和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

遍历数组(Iterating Over an Array)

可以使用for-in循环遍历数组中整个值的集合:

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 Loops

Sets

set在集合中无序的存储了相同类型的不同的值。当对象的序列不重要时,可以使用set代替数组使用,或者你需要确定一个对象值出现了一次。

swift的set类型桥接Foundation的NSSet类。更多关于使用Foundation和Cocoa的Set的信息,查看Bridging Between Set and NSSet.

Set类型的Hash值(Hash Values For Set Types)

为了存储在set中类型必需是可以hash的--也就是,类型需要自己提供计算hash值的方法。hash值是一个相等的值都一样的Int值,例如如果a==b,那么a.hashValue==b.hashValue。

swift基础类型(像String,Int,Double,和Bool)默认都是可以Hash的,可以作为set值类型或者dictionary的key类型。没有关联值的枚举case(像Enumerations中介绍的)值默认也是可以hash的。

你可以使用自己定义的类型遵守swift标准库中的Hashable协议作为值类型或者字典key的类型。遵守Hashable协议的类型一定要提供一个名为hashValue的Int属性。在同一个程序中通过不同执行的hashValue属性返回的值不需要相同,或者在不同程序中。

因为Hashable协议遵守Equatable,遵守的类型一定要提供一个相等(==)的实现。Equatable协议需要任何==的遵守实现是等价的关系。也就是,==的实现一定满足下面三个条件,对全部的a,b,和c:
  • a == a (Reflexivity)
  • a == b implies b == a (Symmetry)
  • a == b && b == c implies a == c (Transitivity)
关于遵守协议更多的信息,查看Protocols

Set类型语法(Set Type Syntax)

swift的set类型书写形式是Set<Element>,Element是set允许存储的类型。不像arrays,sets没有类似的简写形式。

创建和初始化空Set(Creating and Initializing an Empty Set)

你可以使用初始化语法创建一个明确类型的空set:

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>类型。

或者,如果上下文已经提供了类型的信息,例如函数参数或者一个已经定义了类型的变量或者常量,你可以用空数组字面量创建一个空的set:

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>

使用数组字面量创建一个Set(Creating a Set with an Array Literal)

你也可以使用array字面量初始化一个set,作为写一个或者多个值创建set集合的简便方式。

下面的例子创建了一个名为favoriteGenres来存储String值的set:

var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
// favoriteGenres has been initialized with three initial items

favoriteGeneres变量声明的是“一个String类型的值“,形式为Set<String>。因为这个特定的set有指定的一个String类型的值,它只能用来存储String类型的值。这里,favoriteGenres set用三个String值(”Rock“,”Classical“,和”Hip hop“)来初始化,写在一个array字面量中。

集合favoriteGenres声明的是一个变量(用var)而不是常量(用let),因为在下面的例子中要增添删除对象。

一个set类型不能只从数组字面量推导出来,因为Set类型必需要明确声明。不过,因为swift的类型推导,如果你用只包含一种类型的array初始化它的话你就没有必要写set的元素的类型。faboriteGenres初始化可以用更简便的形式代替:

var favoriteGenres: Set = ["Rock", "Classical", "Hip hop"]

因为array字面量中全部的值是同样类型的,swift可以推断Set<String>是用于变量favoriteGenres正确的类型。

访问和修改Set(Accessing and Modifying a Set)

通过它的方法和属性来访问和修改一个set。

查找set中项目的属性,查看只读count属性:

print("I have \(favoriteGenres.count) favorite music genres.")
// Prints "I have 3 favorite music genres."

使用布尔类型属性Empty作为查看是否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."

可以通过调用set的insert(_:)方法向set中添加新的对象:

favoriteGenres.insert("Jazz")
// favoriteGenres now contains 4 items

可以通过调用remove(_:)方法移除set中的项目,如果他是set的一员就会移除项目,并且返回移除的值,如果set没有包含它就返回nil。或者,set中全部的对象通过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."

查看set中是否包含指定对象,使用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."

遍历set(Iterating Over a Set)

可以使用for-in循环遍历一个set的值。

for genre in favoriteGenres {
    print("\(genre)")
}
// Classical
// Jazz
// Hip hop

更过for-in循环的信息,查看For-In Loops

swift的Set类型没有明确顺序的。希望按一个指定顺序遍历set的值的话,使用方法sorted(),这个方法返回了set的元素按<操作符排列的数组。

for genre in favoriteGenres.sorted() {
    print("\(genre)")
}
// Classical
// Hip hop
// Jazz

使用Set操作(Performing Set Operations)

可以高效的使用set基础的操作,像将两个set结合起来,确定两个set有哪些值,或者确定两个set是完全,部分还是没有相同的值。

基础Set操作(Fundamental Set Operations)

下面的图形表述了两个set-a和b-通过阴影区域表示多个set操作的结果。


  • 使用intersection(_:)方法只使用两个set共有的值创建一个新的set。
  • 使用symmetricDifference(_:)方法使用两个set的值,但不是两个都有的来创建一个新的set。
  • 使用union(_:)方法使用两个set中全部的值创建一个新的set。
  • 使用substracting(_:)方法使用不在指定set中的值来创建一个新的set。

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]

set关系和相等(Set Membership and Equality)

下面的图形表示三个set-a,b和c-使用重叠区域表示sets中共有的元素。Set a是set b的父set,因为a包含了b中全部的元素。相反的,set b是set a的子set,因为b中全部元素也被a包含。Set b和set c与另外一个都不想交,因为他们没有分享共有的元素。


  • 使用相等符号(==)确定两个sets包含的值是否都一样。
  • 使用isSubset(of:)方法确定一个set的全部值是否包含在另外一个指定的set中
  • 使用isSuperset(of:)方法确定一个set是否包含指定set的全部值
  • 使用isStrictSubset(of:)或者isStrictSuperset(of:)方法确定一个set是否是一个子set或者父set,但是不等于,一个指定的set。
  • 使用isIdsjoint(with:)方法确定两个set是否没有共有的值。

let houseAnimals: Set = ["🐶", "🐱"]
let farmAnimals: Set = ["🐮", "🐔", "🐑", "🐶", "🐱"]
let cityAnimals: Set = ["🐦", "🐭"]

houseAnimals.isSubset(of: farmAnimals)
// true
farmAnimals.isSuperset(of: houseAnimals)
// true
farmAnimals.isDisjoint(with: cityAnimals)
// true

Dictionaries

dictionary将相同类型key和相同类型的value之间的联系无序地存储在一个集合中。每一个值与唯一的值关联,是字典中值的标志。不像数组中的对象,字典中的对象没有指定的顺序。当你需要通过标记查看值的时候选择使用dictionary,和现实中的用字典查找指定词语的定义一样的方式。

swift的Dictionary类型和Foundation的NSDictionary类是桥接的。更多关于Foundation和Cocoa中Dictionary的信息,查看Bridging Between Dictionary and NSDictionary

字典类型的简写形式(Dictionary Type SHorthand Syntax)

swift的Dictionary类型完整形式写作Dictionary<Key,Value>,Key是用作Dictionary的key的值的类型,Valye是Dictionary为那些keys存储的值的类型。

字典key类型必需遵守Hashable协议,像set的值类型。

也可以将Dictionary的类型用简写的形式[Key:Value]。即便两种形式基本一样,简写形式更常用,当引用Dictionary类型的时候也用于这个手册。

创建一个空字典(Creating An Empty Dictionary)

和array一样,你可以使用初始化预压创建一个具体类型的空Dictionary:

var namesOfIntegers = [Int: String]()
// namesOfIntegers is an empty [Int: String] dictionary

这个例子创建了一个空的[Int:String]类型存储整型值的人类可读的名称的Dictionary。它的key类型是Int,它的值时String类型。

如果上下文已经提供了类型的信息,你可以使用空Dictionary字面量创建一个空的Dictionary,写作[:](在方括号中一个冒号):

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

使用自定字面量创建字典(Creating a Dictionary with Dictionary Literal)

你一可以用Dictionary字面量初始化一个Dictionary,有和array相似的Dictionary语法。dictionary字面量是是写一个或者多个key-value对我看看;

一个键值对是键和值的结合。在字典的遍历中,每一个键值对中的键和值由冒号分隔。键值对写成一个列表,由逗号分隔,周围一对方括号:

[key 1: value 1, key 2: value 2, key 3: value 3]

下面的例子创建了一个存储国际机场的字典。在这个字典中,key是三个字母的国际机场关联码,values是机场名称:

var airports: [String: String] = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]

机场字典声明的类型是[String:String],代表“字典的key是String,value也是String”。

机场字典声明的是一个变量(用var),不是常量(用let),因为下面的例子中有更多的对象添加到字典中。

机场字典使用包含两个键值对的字典初始化。第一对儿有键“YYZ“和值”Toronto Pearson”。第二对有键“DUB”和值“Dublin”。

这个字典遍历包含两个string:string对。key-value类型匹配airports变量声明(一个字典只有String键,String值),所以分配字面量的字典是可以作为一个初始化airports字典的方式。

像arrays一样,如果你使用一个keys和values有相同类型的字面量字典初始化它你不必写出字典的类型。airports的初始化可以用简写形式替代:

var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]

因为字面量中全部keys是一样的类型的,并且所有的values的也是相同类型的,swift可以推导出[String:String]是airports字典使用的正确类型。

访问和修改字典(Accessing and Modifying a Dictionary)

通过它的方法属性或者下标语法访问和修改一个字典。

和array一样,通过查看它的只读属性count查看Dictionary中对象的个数:

print("The airports dictionary contains \(airports.count) items.")
// Prints "The airports dictionary contains 2 items."

使用布尔值属性isEmpty作为查看count属性是否是0的捷径:

if airports.isEmpty {
    print("The airports dictionary is empty.")
} else {
    print("The airports dictionary is not empty.")
}
// Prints "The airports dictionary is not empty."

你可以使用下标语法向字典中添加一个新的对象。使用一个合适类型的新的key作为下标做一年,分配一个合适的类型的值:

airports["LHR"] = "London"
// the airports dictionary now contains 3 items

你也可以使用下表语法来改变关联指定key的value:

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

作为下标的替代方案,使用字典的updateValue(_:forkey:)方法来设置或者更新指定key的值。像上面下标的例子,updateValue(_:forKey:)方法给key设置了一个值如果不存在的话,如果key存在的话更新值。不像下标,不过,updateValue(_:forKey:)方法返回旧的value在更新完之后。这可以让你查看更新是否发生了。

updateValue(_:forKey:)方法返回了一个字典value类型的库限制。对于存储String值的Dictionary,例如,方法返回一个String?类型的值,或者“optional String”。这个可选类型的值包含那个key的旧值如果在更新之前存在,或者如果没有值存在返回nil:

if let oldValue = airports.updateValue("Dublin Airport", forKey: "DUB") {
    print("The old value for DUB was \(oldValue).")
}
// Prints "The old value for DUB was Dublin."

你也可以使用下标语法来从字典中获取指定key的值。因为他可能请求一个没有值得key,一个字典的下标返回一个字典值类型的可选值。如果字典包含请求key的值,下标返回一个包含那个key的value的可选类型。否则,下标返回nil:

if let airportName = airports["DUB"] {
    print("The name of the airport is \(airportName).")
} else {
    print("That airport is not in the airports dictionary.")
}
// Prints "The name of the airport is Dublin Airport."

你可以使用下标语法来通过为那个key分配一个nil值来从dictionary中移除一个key-value对:

airports["APL"] = "Apple International"
// "Apple International" is not the real airport for APL, so delete it
airports["APL"] = nil
// APL has now been removed from the dictionary

或者,使用removeValue(forKey:)方法从Dictionary众移除一个key-value对。如果存在这个方法移除key-value对并且返回被移除的值,或者如果没有值存在返回nil:

if let removedValue = airports.removeValue(forKey: "DUB") {
    print("The removed airport's name is \(removedValue).")
} else {
    print("The airports dictionary does not contain a value for DUB.")
}
// Prints "The removed airport's name is Dublin Airport."

遍历一个字典(Iterating Over a Dictionary)

你可以使用for-in循环遍历字典中的key-value对。字典中每一个对象返回一个(key,value)的元祖,并且你可以解压元祖的元素到临时常量或者变量中作为循环体的一部分:

for (airportCode, airportName) in airports {
    print("\(airportCode): \(airportName)")
}
// LHR: London Heathrow
// YYZ: Toronto Pearson

更多关于for-in循环的信息,查看For-In Loops

你可以通过访问它的keys和values属性获得一个字典的keys或者values的遍历集合:

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

for airportName in airports.values {
    print("Airport name: \(airportName)")
}
// Airport name: London Heathrow
// Airport name: Toronto Pearson

如果你需要通过Array实例中的API操作用字典的keys和values,初始化一个新的keys或者values属性的数组:

let airportCodes = [String](airports.keys)
// airportCodes is ["LHR", "YYZ"]

let airportNames = [String](airports.values)
// airportNames is ["London Heathrow", "Toronto Pearson"]

swift的Dictionary类型没有一个定义的顺序,用指定的顺序遍历一个字典的keys或者values,在它的keys或者values属性上使用sorted()方法。