目前见到的编程语言都以某种方式提供了数组、队列、栈等基础数据结构。这些数据结构的设计都十分巧妙,在事件、空间两个维度来说都表现优秀。Swift也提供了数组、字典、集合三种数据结构,这三种数据结构都是基于泛型来实现的,同时也都是值类型,并且基于写时复制的技术,在各方面都表现优秀。本章,对于这三种技术数据结构进行介绍。
除了,了解这些基础的数据结构的用法之外,通过阅读本章,还可以把解决一个问题——什么时候用C风格的for循环、什么时候for in、for each、什么时候用map(还有flatMap等一些列函数)。
一、数组
1.1 数组和可变性
在Swift当中实用let、var来定义变量的类型:变量、常量。通过此种方式能够在编译器角度来控制常量不可能实际上是一个变量。与OC中的NSArry、NSMutableArray相比,这种基于编译器的常量更加可靠。
1.2 数组索引
有人听说Swift会去检查数组下标越界,因此可能不考虑这个问题。这是一个错误的想法,Swift确实会检查越界,它值保证数组越界的代码不会实际执行,但是,这个越界却会产生崩溃。
var mutableFibs = [0, 1, 1, 2, 3, 5]
// for in 循环
for x in mutableFibs {
print(x)
}
// dropFirst返回的类型是ArraySlice<Int>,其也是数组类型,所以可以进行for in循环
for x in mutableFibs.dropFirst() {
print(x)
}
// dropFirst返回的类型是ArraySlice<Int>,其也是数组类型,所以可以进行for in循环
for x in mutableFibs.dropLast(3){
print(x)
}
// 同时获得下标与元素
for (idx, element) in mutableFibs.enumerated() {
print("idx=\(idx) element=\(element)")
}
// 获取指定元素的下标(原书有误,已经不存在index方法)
if let idx = mutableFibs.firstIndex(where: { (aElement) -> Bool in
return aElement == 3
}) {
print("idx=\(idx)")
}
// 取自函数式编程的方法
let fibsString = mutableFibs.map { (aElement) -> String in
return String(aElement)
}
print(fibsString)
// 取自函数式编程的方法
let elementIsOne = mutableFibs.filter { (aElement) -> Bool in
return aElement == 1
}
print(elementIsOne)
1.3 数组变形
map方法引入自函数式编程思想,其与传统的c语言的for循环类似。那么,如何选用呢?笔者认为:如果要对元素组的元素执行某种“变形”操作,则应该选用map。
与map类似的方法还有很多,它们的普遍特点是:将函数行为参数化(像map函数,对于数组的循环、调用用户传递的方法这些写在标准库里,而其他用户关心的仅仅是以参数形式传递给map的具体业务代码)。
var mutableFibs = [0, 1, 1, 2, 3, 5]
let fibsString = mutableFibs.map { (aElement) -> String in
return String(aElement)
}
print(fibsString) // ["0", "1", "1", "2", "3", "5"]
var fm = mutableFibs.flatMap { (aElement) -> [Int] in
// 构造一个新数组,每个数组有aElement个元素,每个元素的值与aElement相等
return Array(repeating: aElement, count: aElement)
}
print(fm) // [1, 1, 2, 2, 3, 3, 3, 5, 5, 5, 5, 5]
let elementIsOne = mutableFibs.filter { (aElement) -> Bool in
return aElement == 1
}
print(elementIsOne)//[1, 1]
let result = mutableFibs.allSatisfy { (aElement) -> Bool in
return aElement == 2
}
print(result) // false
let reduceResult = mutableFibs.reduce(0) { (aResult, aElement) -> Int in
return aResult + aElement
}
print(reduceResult) // 12
mutableFibs.forEach { (aElement) in
print(aElement)
} // 0, 1, 1, 2, 3, 5
// firstIndex、lastIndex、first、last 这些方法都类似
if let idx = mutableFibs.firstIndex(where: { (aElement) -> Bool in
return aElement == 3
}) {
print("idx=\(idx)") // idx=4
}
let min = mutableFibs.min { (first, second) -> Bool in
first < second
}
print(min) // Optional(0)
let max = mutableFibs.max { (first, second) -> Bool in
first < second
}
print(max) // Optional(5)
let equal = mutableFibs.elementsEqual([1,2,3])
print(equal) // false
let split = mutableFibs.split { (aElement) -> Bool in
return aElement > 3
}
print(split) // [ArraySlice([0, 1, 1, 2, 3])]
let prefix = mutableFibs.prefix { (aElement) -> Bool in
return aElement < 3
}
print(prefix) // [0, 1, 1, 2]
let drop = mutableFibs.drop { (aElement) -> Bool in
return aElement < 3
}
print(drop) // [3, 5]
mutableFibs.removeAll { (aElement) -> Bool in
return aElement > 3
}
print(mutableFibs) // [0, 1, 1, 2, 3]
数组的forEach方法与C里面的for极其类似,但是forEach更强调的是对各元素都执行某个操作且不会中途返回。另外,forEach适合执行带有副作用(参见函数式编程中的副作用,简而言之就是会影响其东西)的循环。
1.4 数组切片
let slice = fibs[1...]
print(slice) // [1, 1, 2, 3, 5]
type(of: slice) // ArraySlice<Int>
print(slice[2], slice[3]) // 1 2
ArraySlice就是数组切片类型,其是符合Sequece协议的一种类型。他与数组的最大区别是:其下标是从获得切片的原始数组的下标开始。
此书对于高阶函数(map、flatMap)讲解的不是很详细,需要详细详细了解的,请移步:Swift高阶函数解析
二、字典
enum Setting {
case text(String)
case int(Int)
case bool(Bool)
}
let defaultSettings: [String:Setting] = ["Airplane Mode": .bool(false),
"Name": .text("My iPhone"),]
print(defaultSettings["Name"]) // Optional(Setting.text("My iPhone"))
这是一个字典的示例。字典的可变性与数组相同。字典要求其键符合Hashable的要求,标准库提供的所有数据类型都已符合此协议,对于自定义的结构体、枚举,如果其成员是符合Hashable要求的,那么其也会有Swift编译器自动合成出符合Hashable要求的方法。Hashable协议继承自Equatable。
三、集合
标准库中第三种主要的集合类型是集合 Set (虽然听起来有些别扭)。集合是一组无序的元素, 每个元素只会出现一次。你可以将集合想像为一个只存储了键而没有存储值的字典。和 Dictionary 一样,Set 也是通过哈希表实现的,并拥有类似的性能特性和要求。测试集合中是 否包含某个元素是一个常数时间的操作,和字典中的键一样,集合中的元素也必须满足 Hashable。
let naturals: Set = [1, 2, 3, 2]
print(naturals) // [1, 2, 3]
print(naturals.contains(3)) // true
print(naturals.contains(0)) // false
let iPods: Set = ["iPod touch", "iPod nano", "iPod mini", "iPod shuffle", "iPod Classic"]
let discontinuedIPods: Set = ["iPod mini", "iPod Classic", "iPod nano", "iPod shuffle"]
let currentIPods = iPods.subtracting(discontinuedIPods)
print(currentIPods)// ["iPod touch"] 补集
let touchscreen: Set = ["iPhone", "iPad", "iPod touch", "iPod nano"]
let iPodsWithTouch = iPods.intersection(touchscreen)
print(iPodsWithTouch)// ["iPod nano", "iPod touch"] 交集
var discontinued: Set = ["iBook", "Powerbook", "Power Mac"]
discontinued.formUnion(discontinuedIPods)
print(discontinued)//["iPod shuffle", "iBook", "iPod Classic", "Powerbook", "iPod mini", "iPod nano", "Power Mac"] 并集
IndexSet、CharacterSet是标准库提供另外两个集合。CharacterSet 是一个高效的存储 Unicode 编码点 (code point) 的集合。IndexSet 表示了一个由正整数组成的集合。
四、Range
范围代表的是两个值的区间,它由上下边界进行定义。你可以通过 ..< 来创建一个不包含上边界 的半开范围,或者使用 ... 创建同时包含上下边界的闭合范围。
// 0 到 9, 不包含 10
let singleDigitNumbers = 0..<10
print(Array(singleDigitNumbers)) // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] //包含"z"
let lowercaseLetters = Character("a")...Character("z")
let fromZero = 0...
let upToZ = ..<Character("z")
for i in 0..<10 {
print("\(i)", terminator: " ")
} // 0 1 2 3 4 5 6 7 8 9
// 错误:'Character' 类型没有实现 'Strideable' 协议
// 原因是Unicode
for c in lowercaseLetters {
...
}
- 只有半开范围能表达空间隔 (也就是下界和上界相等的情况,比如 5..<5)。
- 只有闭合范围能包括其元素类型所能表达的最大值 (比如 0...Int.max)。而半开范围则要
求范围上界是一个比自身所包含的最大值还要大 1 的值。