Swift函数式编程十(迭代器和序列)
Posted 酒茶白开水
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Swift函数式编程十(迭代器和序列)相关的知识,希望对你有一定的参考价值。
代码地址
迭代器(Iterators) 和序列(Sequences) 组成了 Swift 中 for 循环的基础部分。
迭代器
迭代器是每次根据请求生成新元素的“过程”,一个迭代器是遵守一下协议的任何类型:
protocol IteratorProtocol
typealias Element
func next() -> Element?
这个协议需要一个关联类型——Element,还有一个产生新元素的next方法,如果新元素存在就返回新元素,否则返回nil。
下面的迭代器从数组的末尾开始生成序列值,一直到0,Element可以由next函数推断出来,所以不必显式指定:
struct ReverseIndexIterator: IteratorProtocol
var index: Int
init<T>(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<I: IteratorProtocol>: 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<Int>
return AnyIterator
var i = self - 1
guard i >= 0 else
return nil
defer i -= 1
return i
还可以使用AnyIterator定义对迭代器操作、组合的函数:
func +<I: IteratorProtocol, J: IteratorProtocol>(first: I, second: J) -> AnyIterator<I.Element> where I.Element == J.Element
var i = first
var j = second
return AnyIterator i.next() ?? j.next()
先取第一个迭代器的元素,耗尽之后再从第二个迭代器取元素,直到第二个迭代器耗尽,返回nil。
可以对上面定义的第二个参数进行延迟化处理,返回结果完全相同,只是如果只返回部分元素将会更加高效:
func +<I: IteratorProtocol, J: IteratorProtocol>(first: I, second: @escaping @autoclosure () -> J) -> AnyIterator<I.Element> 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<T>: 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<T>(_ transform: (Iterator.Element) throws -> T) rethrows -> [T]
public func filter(_ isIncluded: (Iterator.Element) throws -> Bool) rethrows -> [Iterator.Element]
使用map映射就可以得到相应的元素:
let reverseElements = reverseSequence.map array[$0]
for x in reverseElements
print("元素是:\\(x)")
/*
输出:
元素是:three
元素是:two
元素是:one
*/
map、filter这些方法不会返回新的序列,而是通过遍历生成一个新的数组。Sequence协议中有一个内建的reversed方法就返回一个新的数组。
BidirectionalCollection协议中的reversed方法,根据序列值返回内部集合的反向视图。但是在Sequence协议中没有序列值的概念,在计算出全部结果之前,无法给出一个高效的reveresed序列。
延迟化序列
操作序列可以将一些简单的变换步骤组合起来,例如下面这段代码:
let _ = (1...10).filter $0%3 == 0 .map $0*$0
将上述代码用一个for循环编写:
var result: [Int] = []
for element in (1...10)
if element%3 == 0
result.append(element*element)
for 循环的命令式版本更复杂,难以理解,不利于扩展。函数式版本更加浅显,可以继续链接其他操作,相互独立的各个操作易读,整体含义也便于理解。
但是命令式版本还是有一个好处的,那就是性能更高,只对序列进行了一次迭代。而函数式版本对序列做了两次迭代(过滤、映射各一次),还生成了一个过渡数组将filter的结果传递给map。
多数情况可读性比性能重要,但还是可以优化,使用LazySequence可以在链式操作的同时一次性计算所有操作之后的结果。这样可以将filter、map合并为一步:
let lazyResult = (1...10).lazy.filter $0%3 == 0 .map $0*$0
lazyResult的类型比较复杂,实际上新元素还没有被计算出来。由于该类型遵守Sequence协议,所以既可以使用for循环迭代也可以将转换为数组:
let _ = Array(lazyResult)
for e in lazyResult
print(e)
/*
输出:
9
36
81
*/
多个方法链接时,使用lazy合并所有循环,性能可以媲美命令式代码。
案例一遍历二叉树
首先定义二叉树:
indirect enum BinarySearchTree<Element: Comparable>
case leaf
case node(BinarySearchTree<Element>, Element, BinarySearchTree<Element>)
扩展二叉树,使其准守Sequence协议:
extension BinarySearchTree: Sequence
func makeIterator() -> AnyIterator<Element>
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<Element>?
if self.isEmpty return nil
return Array(dropFirst())
这确实有助于收缩反例,但计算出所有子数组非常昂贵,一个长度为n的数组有2^n个子数组,生成和计算这些并不是一个好主意。
下面将使用迭代器来生成更小的值,重新定义Smaller协议:
protocol Smaller
func small() -> AnyIterator<Self>
不同于前面只一次数组的第一个元素,而是计算出一系列数组,每个数组都移除一个元素,每个数组都比原始数组少一个元素:
extension Array: Smaller
func small() -> AnyIterator<Array<Element>>
var i = 0
return AnyIterator
guard i < self.endIndex else return nil
var result = self
result.remove(at: i)
i += 1
return result
可以做的还有很多,而不仅仅是移除元素。测试一下迭代器:
let v = Array([1, 2, 3].small())
print(v)
/*
输出:
[[2, 3], [1, 3], [1, 2]]
*/
以上是关于Swift函数式编程十(迭代器和序列)的主要内容,如果未能解决你的问题,请参考以下文章