内嵌集合类型
数组
数组和可变性
-
数组是一个容器,它以有序的方式存储相同类型的元素,并且允许随机访问每个元素。
-
和标准库中所有集合类型一样,数组是具有值语义的。
数组索引
- Swift 也有很多无需计算索引就能操作数组的方法:
- 迭代数组? for x in array
- 迭代除了第一个元素以外的数组其余部分? for x in array.dropFirst()
- 迭代除了最后5个元素以外的数组? for x in array.dropLast(5)
- 列举数组中的元素和对应的下标? for (num, element) in collection.enumerated()
- 寻找一个指定元素的位置? if let idx = array.index { someMatchingLogic($0) }
- 筛选出符合某个特定标准的元素? array.filter { someCriteria($0) }
- Swift 3 中传统的 C 风格 for 循环被移除了,这是Swift不鼓励你去做索引计算的另一个标志。手动计算和使用索引值往往会带来很多潜在的bug,所以最好避免这么做。
数组变形
-
Map
map就像是一个信号,一旦你看到它,就会知道即将有一个函数被作用在数组的每个元素上,并返回另一个数组,它将包含所有被转换后的结果。
-
使用函数将行为参数化
map 通过把调用者所提供的变换函数作为参数来将模板代码分离出来
纵观标准库,这种把行为参数化的设计模式有很多。例如:
- map 和 flatMap — 对元素进行变换。
- filter — 只包含特定的元素。
- allSatisfy — 针对一个条件测试所有元素。
- reduce — 将元素聚合成一个值。
- forEach — 访问每一个元素。
- sort(by:), sorted(by:),lexicographicallyPrecedes(_:by:) 和 partition(by:) — 重排元素。
- firstIndex(where:),lastIndex(where:),first(where:),last(where:) 和 contains(where:) — 一个元素是否存在?
- min(by:) 和 max(by:) — 找到所有元素中的最小或最大值。
- elementsEqual(_:by:) 和 starts(with:by:) — 将元素与另一个数组进行比较。
- split(whereSeparator:) — 把所有元素分成多个数组。
- prefix(while:) — 从头取元素直到条件不成立。
- drop(while:) — 当条件为真时,丢弃元素;一旦不为真,返回其余的元素(和 prefix 类似,不过返回相反的集合)。
- removeAll(where:) — 删除所有符合条件的元素。
-
可变和带有状态的闭包
- 闭包是指那些可以捕获和修改自身作用域之外的变量的函数,当它和高阶函数结合时也就成为了一种强大的工具。
-
filter
-
filter 会创建一个全新的数组,并且会对数组中的每个元素都进行操作。
-
一般来说,你只应该在需要所有结果时才去选择使用 filter
-
-
reduce
let fibs = [0,1,1,2,3,5]
fibs.reduce(0,+)
复制代码
-
一个展平的map
flatMap
-
使用 forEach 进行迭代
在 forEach 中的return 并不能让外部函数返回,它仅仅只是让闭包本身返回。
数组切片
let slice = fibs[1...]
slice// [1,1,2,3,5]
type(of: slice) // ArraySlice<Int>
复制代码
- 切片类型只是数组的一种表示方式,它背后的数据仍然是原来的数组,只不过是用切片的方式来表示。因为数组的元素不会被复制,所以创建一个切片的代价是很小的。
- 因为 ArraySlice 和 Array 都满足了相同的协议 (当中最重要的就是 Collection 协议),所以两者具有的方法是一致的,因此你可以把切片当做数组来进行处理。
- 切片和它背后的数组是使用相同的索引来引用元素的。因此,切片索引不需要从零开始。如果操作切片,请基于 startIndex 和 endIndex 属性做索引计算。
字典
- 字典,通过键来获取值所花费的平均时间是常数量级的。
- 字典是无序的。
- 字典查找总是返回一个可选值,当指定的键不存在时,它就返回nil。这点和数组有所不同,在数组中,使用越界下标进行访问将会导致程序奔溃。
可变性
一些有用的字典方法
-
Dictionary 有一个 merge(_: uniquingKeysWith:),它接受两个参数,第一个是要进行合并的键值对,第二个是定义如何合并相同键的两个值的函数。
-
如果能保证键是唯一的,那么就可以使用 Dictionary(uniqueKeysWithValues:)
extension Sequence where Elemnet: Hashable {
var frequencies: [Element: Int] {
let frequencyPairs = self.map{ ($0, 1)}
return Dictionary(frequencyPairs, uniquingKeysWith: +)
}
}
let frequencies = "hello".frequencies // ["h":1, "e":1, "0":1, "l":2]
frequencies.filter { $0.value > 1 } // ["l": 2]
复制代码
- 对字典的值做映射——map/mapValues
Hashable 要求
- 标准库中所有的基本数据类型都是遵守 Hashable 协议的,它们包括字符串,整数,浮点数以及布尔值。另外,像是数组,集合和可选值这些类型,如果它们的元素都是可哈希的,那么它们自动成为可哈希的。
Set
-
和 Dictionary 一样,Set 也是通过哈希表实现的,并拥有类似的性能特性和要求。和字典中的键一样,集合中的元素也必须满足 Hashable。
-
Set 遵守 ExpressibleByArrayLiteral 协议,也就是说,我们可以用数组字面量的方式初始化一个集合:
let naturals: Set = [1,2,3,2] naturals // [1,2,3] 复制代码
集合代数
- Set支持基本集合操作,求 补集 、交集 、并集 … 想要了解更多的集合操作,可以看看 SetAlgebra 协议
索引集合和字符集合
-
Set 和 OptionSet 是标准库中唯一实现了 SetAlgebra 的类型,但是这个协议在 Foundation 中还被另外两个很有意思的类型实现了:那就是 IndexSet 和 CharacterSet。
-
IndexSet 表示了一个由正整数组成的集合。我们可以用 Set 来做这件事,但是 IndexSet 更加高效,因为它内部使用了一组范围列表进行实现。
-
同样的,CharacterSet 是一个高效的存储 Unicode 编码点 (code point) 的集合。不过,和 IndexSet 有所不同, CharacterSet 并不是一个集合类型。它的名字,CharacterSet,是从 Objective-C 导入时生成的,在Swift 中它也不兼容 Swift 的 Character 类型。可能 UnicodeScalarSet 会是更好的名字。
在闭包中使用集合
extension Sequence where Element: Hashable {
func unique() -> [Element] {
var seen: Set<Element> = []
return filter { element in
if seen.contains(elemnet) {
return false
} else {
seen.insert(element)
return true
}
}
}
}
[1,2,3,12,1,3,4,5,6,4,6].unique() // [1,2,3,12,4,5,6]
复制代码
上面的这个方法让我们可以找到序列中的所有不重复的元素,并且通过元素必须满足 Hashable 这个约束来维持它们原来的顺序。
Range
-
范围代表的是两个值的区间,它由上下边界进行定义。
-
最常用的两种两种类型是 Range (由 ..< 创建的半开范围) 和 ClosedRange (由 … 创建的闭合范围)。两者都有一个 Bound 的泛型参数: 对于 Bound 的唯一的要求是它必须遵守 Comparable 协议。
-
半开范围和闭合范围各有作用:
-
只有半开范围能表达空间隔(也就是下界和上界相等的情况,比如 5..<5)。
-
只有闭合范围能包括其元素类型所能表达的最大值 (比如 0…Int.max)。而半开范围则要求上界是一个比自身所包含的最大值还要大1的值。
-
可数范围
-
让 Range 满足集合类型协议是有条件的,条件是它的元素需要满足 Strideable 协议 (你可以通过增加偏移来从一个元素移动到另一个),并且步长 (stride step) 是整数:
extension Range: Sequence where Bound: Strideable, Bound.Stride: SignedInteger {/* ... */} extension Range: Collection, BidirectionalCollection, RandomAccessCollection where Bound: Strideable, Bound.Stride: SignedInteger {/* ... */} 复制代码
换句话说,为了能遍历范围,它必须是可数的。
范围表达式
-
所有五种范围都满足 RangeExpression 协议。首先,它允许我们询问某个元素是否被包括在该范围中。其次,给定一个集合类型,它能够计算出表达式所指定的完整的 Range:
public protocal RangeExpression { associatedtype Bound:Comparable func contains(_ element: Bound) -> Bool func relative<C>(to collection: C) -> Range<Bound> where C: Collection, Self.Bound == C.Index } 复制代码