Error[8]: Undefined offset: 9, File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 121
File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 473, decode(

代码地址

迭代器(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
        }
    }
}

可以做的还有很多,而不仅仅是移除元素。测试一下迭代器:

[+++]
)
File: /www/wwwroot/outofmemory.cn/tmp/route_read.php, Line: 126, InsideLink()
File: /www/wwwroot/outofmemory.cn/tmp/index.inc.php, Line: 165, include(/www/wwwroot/outofmemory.cn/tmp/route_read.php)
File: /www/wwwroot/outofmemory.cn/index.php, Line: 30, include(/www/wwwroot/outofmemory.cn/tmp/index.inc.php)
Error[8]: Undefined offset: 10, File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 121
File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 473, decode(

代码地址

迭代器(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
        }
    }
}

可以做的还有很多,而不仅仅是移除元素。测试一下迭代器:

[+++]
)
File: /www/wwwroot/outofmemory.cn/tmp/route_read.php, Line: 126, InsideLink()
File: /www/wwwroot/outofmemory.cn/tmp/index.inc.php, Line: 165, include(/www/wwwroot/outofmemory.cn/tmp/route_read.php)
File: /www/wwwroot/outofmemory.cn/index.php, Line: 30, include(/www/wwwroot/outofmemory.cn/tmp/index.inc.php)
Error[8]: Undefined offset: 11, File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 121
File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 473, decode(

代码地址

迭代器(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
        }
    }
}

可以做的还有很多,而不仅仅是移除元素。测试一下迭代器:

[+++]
)
File: /www/wwwroot/outofmemory.cn/tmp/route_read.php, Line: 126, InsideLink()
File: /www/wwwroot/outofmemory.cn/tmp/index.inc.php, Line: 165, include(/www/wwwroot/outofmemory.cn/tmp/route_read.php)
File: /www/wwwroot/outofmemory.cn/index.php, Line: 30, include(/www/wwwroot/outofmemory.cn/tmp/index.inc.php)
Error[8]: Undefined offset: 12, File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 121
File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 473, decode(

代码地址

迭代器(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
        }
    }
}

可以做的还有很多,而不仅仅是移除元素。测试一下迭代器:

[+++]
)
File: /www/wwwroot/outofmemory.cn/tmp/route_read.php, Line: 126, InsideLink()
File: /www/wwwroot/outofmemory.cn/tmp/index.inc.php, Line: 165, include(/www/wwwroot/outofmemory.cn/tmp/route_read.php)
File: /www/wwwroot/outofmemory.cn/index.php, Line: 30, include(/www/wwwroot/outofmemory.cn/tmp/index.inc.php)
Error[8]: Undefined offset: 13, File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 121
File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 473, decode(

代码地址

迭代器(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
        }
    }
}

可以做的还有很多,而不仅仅是移除元素。测试一下迭代器:

[+++]
)
File: /www/wwwroot/outofmemory.cn/tmp/route_read.php, Line: 126, InsideLink()
File: /www/wwwroot/outofmemory.cn/tmp/index.inc.php, Line: 165, include(/www/wwwroot/outofmemory.cn/tmp/route_read.php)
File: /www/wwwroot/outofmemory.cn/index.php, Line: 30, include(/www/wwwroot/outofmemory.cn/tmp/index.inc.php)
Error[8]: Undefined offset: 14, File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 121
File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 473, decode(

代码地址

迭代器(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
        }
    }
}

可以做的还有很多,而不仅仅是移除元素。测试一下迭代器:

[+++]
)
File: /www/wwwroot/outofmemory.cn/tmp/route_read.php, Line: 126, InsideLink()
File: /www/wwwroot/outofmemory.cn/tmp/index.inc.php, Line: 165, include(/www/wwwroot/outofmemory.cn/tmp/route_read.php)
File: /www/wwwroot/outofmemory.cn/index.php, Line: 30, include(/www/wwwroot/outofmemory.cn/tmp/index.inc.php)
Error[8]: Undefined offset: 15, File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 121
File: /www/wwwroot/outofmemory.cn/tmp/plugin_ss_superseo_model_superseo.php, Line: 473, decode(

代码地址

迭代器(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
        }
    }
}

可以做的还有很多,而不仅仅是移除元素。测试一下迭代器:

[+++]
)
File: /www/wwwroot/outofmemory.cn/tmp/route_read.php, Line: 126, InsideLink()
File: /www/wwwroot/outofmemory.cn/tmp/index.inc.php, Line: 165, include(/www/wwwroot/outofmemory.cn/tmp/route_read.php)
File: /www/wwwroot/outofmemory.cn/index.php, Line: 30, include(/www/wwwroot/outofmemory.cn/tmp/index.inc.php)
Swift函数式编程十(迭代器和序列)_app_内存溢出

Swift函数式编程十(迭代器和序列)

Swift函数式编程十(迭代器和序列),第1张

代码地址

迭代器(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
        }
    }
}

可以做的还有很多,而不仅仅是移除元素。测试一下迭代器:

					
										


					

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/996891.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-21
下一篇 2022-05-21

发表评论

登录后才能评论

评论列表(0条)

保存