迭代器(Iterators) 和序列(Sequences) 组成了 Swift 中 for 循环的基础部分。
迭代器迭代器是每次根据请求生成新元素的“过程”,一个迭代器是遵守一下协议的任何类型:
protocol IteratorProtocol {
typealias Element
func next() -> Element?
}
这个协议需要一个关联类型——Element,还有一个产生新元素的next方法,如果新元素存在就返回新元素,否则返回nil。
下面的迭代器从数组的末尾开始生成序列值,一直到0,Element可以由next函数推断出来,所以不必显式指定:
struct ReverseIndexIterator: IteratorProtocol {
var index: Int
init(array: [T]) {
index = array.endIndex - 1
}
mutating func next() -> Int? {
guard index >= 0 else { return nil }
defer { index -= 1 }
return index
}
}
使用 ReverseIndexIterator 来倒序遍历数组:
let letters = ["A", "B", "C"]
var iterator = ReverseIndexIterator(array: letters)
while let i = iterator.next() {
print("数组的第\(i)个元素是\(letters[i])")
}
/* 输出:
数组的第2个元素是C
数组的第1个元素是B
数组的第0个元素是A
*/
如果需要另一种方式排序序列值,只需要更新迭代器。
有时迭代器并不需要生成 nil 值,例如定义一个迭代器来生成无数个2的幂值(直到某个最大值,使得 NSDecimalNumber 溢出):
struct PowerIterator: IteratorProtocol {
var power: NSDecimalNumber = 1
mutating func next() -> NSDecimalNumber? {
power = power.multiplying(by: 2)
return power
}
}
现在可以在2的幂值中搜索一些有趣的值,定义一个 find 函数带有一个NSDecimalNumber -> Bool 类型的predicate参数,并返回复合该条件的最小值:
extension PowerIterator {
mutating func find(where predicate: (NSDecimalNumber) -> Bool) -> NSDecimalNumber? {
while let x = next() {
if predicate(x) {
return x
}
}
return nil
}
}
例如计算2的幂值中大于1000的最小值:
var powerIterator = PowerIterator()
let num = powerIterator.find { $0.intValue > 1000 }
if let value = num {
print(value)
}
/// 输出:1024
下面迭代器生成一组字符串,与文件中以行为单位的内容相对应:
struct FileLinesIterator: IteratorProtocol {
let lines: [String]
var currentLine: Int = 0
init(filename: String) throws {
let contents: String = try String(contentsOfFile: filename)
lines = contents.components(separatedBy: .newlines)
}
mutating func next() -> String? {
guard currentLine < lines.endIndex else { return nil }
defer { currentLine += 1 }
return lines[currentLine]
}
}
通过定义这种迭代器,将数据的生成和使用分离开,确保在使用数据时不用去考虑其他问题。
基于迭代器协议,可以定义一些通用的泛型函数,例如下面findPower函数的泛型版本:
extension IteratorProtocol {
mutating func find(predicate:(Element) -> Bool) -> Element? {
while let x = next() {
if predicate(x) {
return x
}
}
return nil
}
}
这个find函数适用任何迭代器,由于调用了next方法可能修改迭代器,所以在申明中添加mutating标注。
可以层级式组合迭代器。比如希望限制生产元素个数,可以构建一个迭代器转换器,适用参数limit来限制迭代器G生成元素的个数:
struct LimitIterator: IteratorProtocol {
var limit = 0
var iterator: I
init(limit: Int, iterator: I) {
self.limit = limit
self.iterator = iterator
}
mutating func next() -> I.Element? {
guard limit > 0 else {
return nil
}
return iterator.next()
}
}
为每个迭代器引入一个新的类型是非常繁琐的,Swift提供了一个AnyGenerator类,通过传入一个next函数来初始化。
适用AnyIterator可以更简短的定义迭代器,如下重写ReverseIndexIterator:
extension Int {
func countDown() -> AnyIterator {
return AnyIterator {
var i = self - 1
guard i >= 0 else {
return nil
}
defer { i -= 1 }
return i
}
}
}
还可以使用AnyIterator定义对迭代器 *** 作、组合的函数:
func +(first: I, second: J) -> AnyIterator where I.Element == J.Element {
var i = first
var j = second
return AnyIterator { i.next() ?? j.next() }
}
先取第一个迭代器的元素,耗尽之后再从第二个迭代器取元素,直到第二个迭代器耗尽,返回nil。
可以对上面定义的第二个参数进行延迟化处理,返回结果完全相同,只是如果只返回部分元素将会更加高效:
func +(first: I, second: @escaping @autoclosure () -> J) -> AnyIterator where I.Element == J.Element {
var one = first;
var other: J? = nil
return AnyIterator {
if other != nil {
return other?.next()
} else if let result = one.next() {
return result
} else {
other = second()
return other?.next()
}
}
}
函数通过将参数标记为 @autoclosure 来接收一个自动闭包。自动闭包是一种自动创建的闭包,用于包装传递给函数作为参数的表达式。这种闭包不接受任何参数,当它被调用的时候,会返回被包装在其中的表达式的值。这种便利语法能够省略闭包的花括号,用一个普通的表达式来代替显式的闭包。自动闭包能够延迟求值,因为直到调用这个闭包,代码段才会被执行。延迟求值对于那些有副作用(Side Effect)和高计算成本的代码来说是很有益处的,因为它能控制代码的执行时机。
序列迭代器为序列提供了基础类型,迭代器提供“单次触发”机制反复计算下一个元素,不支持反查、重生已经生成过的元素。而SequenceType协议为这些提供了合适的接口:
protocol Sequence {
associatedtype Iterator: IteratorProtocol
func makeIterator() -> Iterator
// ...
}
每个序列都有一个关联的迭代器类型和一个生成新的迭代器的方法,可以据此使用迭代器来遍历序列。例如定义ReverseIndexIterator序列用以生成数组倒序序列值:
struct ReverseArrayIndices: Sequence {
let array: [T]
init(array: [T]) {
self.array = array
}
func makeIterator() -> ReverseIndexIterator {
return ReverseIndexIterator(array: array)
}
}
使用序列:
var array = ["one", "two", "three"]
let reverseSequence = ReverseArrayIndices(array: array)
var reverseIterator = reverseSequence.makeIterator()
while let i = reverseIterator.next() {
print("第\(i)个元素是\(array[i])")
}
/*
输出:
第2个元素是three
第1个元素是two
第0个元素是one
*/
同一个序列可以使用多次,只需要调用makeIterator来生成新的迭代器。Sequence的定义中封装了迭代器的创建过程,使用序列不必担心迭代器的创建过程。这跟面向对象中使用和创建分离的思想一脉相承,具有更高的内聚性。
Swift处理序列有一个特别的语法,就是可以用for循环遍历:
for i in reverseSequence {
print("第\(i)个元素是\(array[i])")
}
/*
输出:
第2个元素是three
第1个元素是two
第0个元素是one
*/
其实应该是对数组中的元素更感兴趣,但是ReverseArrayIndices只是生成元素序号,不过序列也有标准的map、filter方法:
public protocol Sequence {
public func map(_ transform: (Iterator.Element) throws -> T) rethrows -> [T]
public func filter(_ isIncluded: (Iterator.Element) throws -> Bool) rethrows -> [Iterator.Element]
}
使用map映射就可以得到相应的元素:
let reverseElements = reverseSequence.map { array[let _ = (1...10).filter { var result: [Int] = []
for element in (1...10) {
if element%3 == 0 {
result.append(element*element)
}
}
%3 == 0 }.map { let lazyResult = (1...10).lazy.filter { let _ = Array(lazyResult)
for e in lazyResult {
print(e)
}
/*
输出:
9
36
81
*/
%3 == 0 }.map { let v = Array([1, 2, 3].small())
print(v)
/*
输出:
[[2, 3], [1, 3], [1, 2]]
*/
* }
* }
] }
for x in reverseElements {
print("元素是:\(x)")
}
/*
输出:
元素是:three
元素是:two
元素是:one
*/
map、filter这些方法不会返回新的序列,而是通过遍历生成一个新的数组。Sequence协议中有一个内建的reversed方法就返回一个新的数组。
BidirectionalCollection协议中的reversed方法,根据序列值返回内部集合的反向视图。但是在Sequence协议中没有序列值的概念,在计算出全部结果之前,无法给出一个高效的reveresed序列。
延迟化序列*** 作序列可以将一些简单的变换步骤组合起来,例如下面这段代码:
将上述代码用一个for循环编写:
for 循环的命令式版本更复杂,难以理解,不利于扩展。函数式版本更加浅显,可以继续链接其他 *** 作,相互独立的各个 *** 作易读,整体含义也便于理解。
但是命令式版本还是有一个好处的,那就是性能更高,只对序列进行了一次迭代。而函数式版本对序列做了两次迭代(过滤、映射各一次),还生成了一个过渡数组将filter的结果传递给map。
多数情况可读性比性能重要,但还是可以优化,使用LazySequence可以在链式 *** 作的同时一次性计算所有 *** 作之后的结果。这样可以将filter、map合并为一步:
lazyResult的类型比较复杂,实际上新元素还没有被计算出来。由于该类型遵守Sequence协议,所以既可以使用for循环迭代也可以将转换为数组:
多个方法链接时,使用lazy合并所有循环,性能可以媲美命令式代码。
案例一遍历二叉树首先定义二叉树:
indirect enum BinarySearchTree {
case leaf
case node(BinarySearchTree, Element, BinarySearchTree)
}
扩展二叉树,使其准守Sequence协议:
extension BinarySearchTree: Sequence {
func makeIterator() -> AnyIterator {
switch self {
case .leaf:
return AnyIterator { nil }
case let .node(left, element, right):
return left.makeIterator() + CollectionOfOne(element).makeIterator() + right.makeIterator()
}
}
}
使用重载的+运算符来生成二叉树元素的序列。makeIterator遍历访问左右子树、根节点,没有元素返回空迭代器,如果是节点,则递归调用并与该节点存储的值拼接作为结果返回。
注意使用的是延迟化方案的+,如果是非延迟化方案的+,则makeIterator需要访问整棵树再返回结果。
案例二优化QuickCheck的范围收缩之前在QuickCheck中实现的Smaller协议是这样定义的:
protocol Smaller {
func small() -> Self?
}
使用Smaller去判断和收缩测试中的反例,small函数反复调用来生成更小的值,如果这个值依然无法通过测试则说明可能存在一个更好的反例。
定义数组遵守Smaller协议,反复移除第一个元素:
extension Array: Smaller {
func small() -> Array? {
if self.isEmpty { return nil }
return Array(dropFirst())
}
}
这确实有助于收缩反例,但计算出所有子数组非常昂贵,一个长度为n的数组有2^n个子数组,生成和计算这些并不是一个好主意。
下面将使用迭代器来生成更小的值,重新定义Smaller协议:
protocol Smaller {
func small() -> AnyIterator
}
不同于前面只一次数组的第一个元素,而是计算出一系列数组,每个数组都移除一个元素,每个数组都比原始数组少一个元素:
extension Array: Smaller {
func small() -> AnyIterator> {
var i = 0
return AnyIterator {
guard i < self.endIndex else { return nil }
var result = self
result.remove(at: i)
i += 1
return result
}
}
}
可以做的还有很多,而不仅仅是移除元素。测试一下迭代器:
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)