【Swift 3.1】04 - 集合类型 (Collection Types)

集合类型 (Collection Types)

自从苹果2014年发布Swift,到现在已经两年多了,而Swift也来到了3.1版本。去年利用工作之余,共花了两个多月的时间把官方的Swift编程指南看完。现在整理一下笔记,回顾一下以前的知识。有需要的同学可以去看官方文档>>


Swift提供了三种集合类型:数组(Array)、集合(Set)、字典(Dictionary)。Array是有顺序的值的集合;Set是多个唯一的值的无序集合;Dictionary是无序的键值对集合。

注意:Swift的ArraySetDictionary都属于泛型集合。

数组 (Array)

数组只能存储相同类型的值。相同的值可以出现在数组的不同位置中。

数组类型的速记语法 (Array Type Shorthand Syntax)

一个数组的类型是这样写的:Array<Element>Element是数组元素值的类型,也可以简写成:[Element]

创建一个空数组 (Creating an Empty Array)
var someInt = [Int]()
print("someInts is of type [Int] with \(someInts.count) items.")
// Prints "someInts is of type [Int] with 0 items."

someInt被推断为[Int]类型。

如果上下文已经提供了数组的类型,空素组还可以写成[]

someInt.append(3)
// someInts now contains 1 value of type Int
someInt = []
// someInts is now an empty array, but is still of type [Int]
创建一个有默认值的数组 (Creating an Array with a Default Value)
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)
let 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)
var shoppingLis = ["Eggs", "Milk"]
// shoppingList has been initialized with two initial items
访问和修改数组 (Accessing and Modifying an Array)

获取数组的个数:

print("The shopping list contains \(shoppingList.count) items.")
// Prints "The shopping list contains 2 items."

判断数组元素的个数是否为0:

if shoppingList.isEmpty {
    print("The shopping list is empty.")
} else {
    print("The shopping list is not empty.")
}

追加一个元素:

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"

更改元素:

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

使用下标一次性更改多个元素,甚至要更改的元素个数可以不等于新数组的个数:

shoppintList[4...6] = ["Bananas", "Apples"]
// 用两个替换三个

在特定的位置插入元素:

shoppingList.insert("Maple syrup", at: 0)
// shoppingList now contains 7 items
// "Maple Syrup" is now the first item in the list

删除特定位置的元素,并且返回被删除的元素:

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

删除最后一个元素:

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()方法遍历,这个方法返回包含索引和索引对应的元素的多元组:

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

集合 (Sets)

集合中无顺序地存储了同一类型的值,并且里面的每一个值都是唯一的。在元素的顺序不重要或者要求每一个元素都需要唯一的时候,可以使用集合,而不用数组。

集合类型的哈希值 (Hash Values for Set Types)

集合里面的元素类型必须hashable,也就是说,这个元素类型必须提供一个方法来计算他自己的哈希值。一个哈希值是一个用来判断两个对象是否相等的Int类型的整数。例如,如果a == b,那么a.hashValue == b.hashValue

所有Swift的基本类型(例如StringIntDoubleBool)默认都是hashable的,都可以作为集合的值类型或者字典的键类型。没有关联值的枚举值默认也是hashable的。

我们可以自定义类型,并且遵循Hashable协议,作为集合或者字典键的值类型。自定义的类型必须提供一个能读取的Int类型的属性,并命名为hashValue。在不同的程序或者同一个程序运行多次中,不要求每次hashValue属性返回的值都相等。

因为Hashable协议遵循Equatable协议,所以我们自定义的类型还需要提供一个相等运算符(==)的实现。Equatable协议要求每一个==的实现是一个等价关系。也就是说,==的实现必须满足下面三个条件:

  • a == a (自反性)
  • a == b,说明b == a (对称性)
  • a == b && b == c,说明 a == c (传递性)
集合类型语法 (Set Type Syntax)

使用Set<Element>来设置集合类型,Element是集合存储的元素类型。

创建和初始化一个空集合 (Creating and Initializing an Empty 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>类型。

同样地,如果上下文提供了集合的类型信息,可以使用[]来创建一个空的集合:

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>
使用数组字面值来创建一个集合 (Creating a Set with an Array Literal)
var favoriteGenres: Set<String> = ["Rock", "Classical", "Hip hop"]
// favoriteGenres has been initialized with three initial items

集合的类型不能通过数组的字面值来推断,所以Set的类型必须明确声明。但是因为Swift的类型推断,如果用一个包含相同类型字面值的数组来初始化集合,我们可以不写集合的类型。例如:

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

因为数组的全部字面值都是同一类型,所以Swfit能推断出Set<String>favoriteGenres的正确类型。

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

使用count属性获取集合元素个数:

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

使用isEmpty属性判断集合中元素的个数是否为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."

判断是否包含某个元素:

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."
遍历整个集合 (Iterating Over a Set)
for genre in favoriteGenres {
    print("\(genre)")
}
// Jazz
// Hip hop
// Classical

Swift的集合类型没有定义顺序,我们可以使用sorted()方法来排序,这个方法使用<运算符将元素从小到大排列:

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

执行集合操作 (Performing Set Operations)

基本集合操作 (Fundamental Set Operations)

下图是集合ab执行了不同的方法之后,得出的结果图:

Fundamental Set Operations
  • 使用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]
集合关系和相等性 (Set Membership and Equality)

下图演示了三个集合:abc,重叠区域代表有相同的元素。集合a是集合b的父集合,因为a包含了b的所有元素;相反,ba的子集合。集合b和集合c互不相交,因为他们没有相同的元素。

Set Membership and Equality
  • 使用“是否相等”运算符 (==)来判断两个集合的所有元素是否相等
  • 使用isSubset(of:)方法判断集合的所有元素是否包含于指定集合
  • 使用isSuperset(of:)方法判断集合是否包含指定集合的所有元素
  • 使用isStrictSubset(of:)或者isStrictSuperset(of:)方法判断集合是否子集合或者父集合,但是不等于指定的集合
  • 使用isDisjoint(with:)方法判断两个集合是否有相同的元素
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 Type Shorthand Syntax)

使用Dictionary<Key, Value>来指定字典的类型。

注意:字典的Key类型必须遵循Hashable协议,就像集合的值一样。

还可以是用简短的形式[Key: Value]来指定字典的类型.

创建一个空字典 (Creating an Empty Dictionary)
var namesOfIntegers = [Int: String]()
// namesOfIntegers is an empty [Int: String] 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 a Dictionary Literal)
var airports = ["YYZ": "Toronto Pearson", "DUB": "Dublin"]
访问和修改字典 (Accessing and Modifying Dictionary)

获取字典键值对的个数:

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

判断字典中键值对的个数是否为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."

使用下标语法添加新的键值对:

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

还可以使用updateValue(_:forKey:)方法来设置或更新一个键对应的值,并返回一个可选类型的值。如果这个键不存在,那么就添加一个新的键值对,并返回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."

使用下标语法来获取键对应的值:

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."

使用下标语法并把键对应的值设置为nil来删除一个键值对:

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:)方法来删除一个键值对,如果存在,返回键对应的值;如果不存在,返回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 (airportCode, airportName) in airports {
    print("\(airportCode): \(airportName)")
}
// YYZ: Toronto Pearson
// LHR: London Heathrow

使用keysvalues属性来遍历字典的所有键和所有值:

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

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

如果要使用字典的所有键和所有值,可以利用数组的API来创建:

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

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

Swift的字典类型没有定义顺序,为了遍历经过排序的所有键和所有值,需要使用keysvalues属性的sorted()方法。


第四部分完。下个部分:【Swift 3.1】05 - 控制流 (Control Flow)


如果有错误的地方,欢迎指正!谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,245评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,749评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,960评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,575评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,668评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,670评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,664评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,422评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,864评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,178评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,340评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,015评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,646评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,265评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,494评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,261评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,206评论 2 352

推荐阅读更多精彩内容