这一节,我们从剩下的两个drop
方法开始:
public func dropFirst(_ k: Int = 1) -> DropFirstSequence<Self>
public func drop(
while predicate: (Element) throws -> Bool
) rethrows -> DropWhileSequence<Self>
为什么这两个方法没有使用数组来保存“丢弃”后的结果呢?在回答这个问题之前,我们先来回答另外一个问题:如果我们就用数组作为这两个方法的返回值可以么?答案是当然可以,都不用写代码,想想原理上就是可行的,只要用一个数组把序列获取的值都缓存下来,然后从中去掉特定的元素返回就行了,逻辑上肯定是没问题的。
emm...那标准库为什么没有这么做呢?如果用大白话说,就是太不经济了。说两个极端的例子,如果要忽略一个包含10000个元素的序列中的前9999个呢?或者,如果一个序列中每一个元素predicate
方法返回的都是false
呢?那岂不是用于缓存元素的内存空间都白白浪费了。
因此,对这两个方法,Swift标准库采用了非缓存的策略。无论是DropFirstSequence
还是DropWhileSequence
,它们都是在序列中有值之后立即评估的。为了准确理解它们的工作方式,我们还是通过代码来分析。
DropFirstSequence
先来看相对容易理解的DropFirstSequence
,它的定义在这里:
public struct DropFirstSequence<Base: Sequence> {
internal let _base: Base
internal let _limit: Int
public init(_ base: Base, dropping limit: Int) {
_precondition(limit >= 0,
"Can't drop a negative number of elements from a sequence")
_base = base
_limit = limit
}
}
其中:
-
_base
表示要处理的原始序列; -
_limit
表示要从base
开始丢弃的元素个数;
接下来,为了让DropFirstSequence
用起来也像一个序列,它也遵从了Sequence
协议:
extension DropFirstSequence: Sequence {
public typealias Element = Base.Element
public typealias Iterator = Base.Iterator
public __consuming func makeIterator() -> Iterator {
var it = _base.makeIterator()
var dropped = 0
while dropped < _limit, it.next() != nil { dropped &+= 1 }
return it
}
}
可以看到,DropFirstSequence
作为一个序列,它的Element
就是Base
的Element
,Iterator
就是Base
的Iterator
。但是DropFirstSequence
在返回Base.Iterator
之前会对废弃的元素进行计数,在达到计数或者原序列已经没有元素之前,DropFirstSequence
会持续消耗掉原序列的元素(这里之所以用了“消耗”,是因为Sequence
不一定是可以重复遍历的)。
我们用之前定义过的InputStream
理解这个过程:
let stdin = InputStream()
let dropped = stdin.dropFirst(2)
var dfIterator = dropped.makeIterator()
while let elem = dfIterator.next() {
print("for: \(elem)")
}
执行一下,就会得到下面这样类似的结果:
>> ha
(1) ha
>> haha
(2) haha
>> hahaha
(3) hahaha
for: hahaha
>> Ctrl + D
我们来分析一下:
- 首先,定义
stdin
的时候,可以理解为现在它只是逻辑上代表用户输入内容的序列。实际上我们还没有开始输入内容,stdin
也什么都没有; - 其次,定义
dropped
的时候,可以理解为它只是逻辑上代表去掉了前两次用户输入内容的序列; - 第三,当要获取
df
的Iterator
时,我们才真正开始输入内容。这时df.makeIterator()
就会开始消耗掉stdin
中的前两个元素,在消耗完之前,makeIterator
并不会返回; - 最后,在
while
循环中通过dfIterator.next()
得到的,就应该是第三次开始输入的内容了,从dropped
的角度看,它就表示了“忽略用户前2次输入之后的用户输入序列”。这就是我们在一开始说到的“序列中有值之后立即评估”要表达的含义;
dropFirst
了解了DropFirstSequence
的实现之后,我们来看dropFirst
的实现,它的定义在这里:
extension Seq{
public func dropFirst(_ k: Int = 1) -> DropFirstSequence<Self> {
return DropFirstSequence(self, dropping: k)
}
}
其实,就是返回一个DropFirstSequence
对象罢了。
DropWhileSequence
接下来,我们来分析DropWhileSequence
。它的定义在这里:
public struct DropWhileSequence<Base: Sequence> {
public typealias Element = Base.Element
internal var _iterator: Base.Iterator
internal var _nextElement: Element?
internal init(
iterator: Base.Iterator,
predicate: (Element) throws -> Bool) rethrows {
_iterator = iterator
_nextElement = _iterator.next()
while let x = _nextElement, try predicate(x) {
_nextElement = _iterator.next()
}
}
}
其中:
-
Base
是要截取的原始序列类型; -
Element
就是Base
中的Element
; -
_iterator
就是Base
中的Iterator
; -
_nextElement
是从原始序列中读入的下一个元素,要理解这个属性的含义,我们得从init
方法去找答案;
在它的init
方法里,除了用于筛选元素的谓词函数之外,它还接受一个原始序列的Iterator
作为参数。在它的实现里,可以看到只要predicate(x)
为true
,并且原始序列中有元素,while
循环就会一直向后遍历序列。因此,创建出来的DropWhileSequence
里,_nextElement
要么是第一个不满足谓词要求的元素,要么就是nil
。
以上,都是DropWhileSequence
的私有部分,我们无法通过外部module来使用。接下来,来看它的公开部分。同样,为了让DropWhileSequence
用起来像一个序列,它也遵从了Sequence
协议:
extension DropWhileSequence: Sequence {
public func makeIterator() -> Iterator {
return Iterator(_iterator, nextElement: _nextElement)
}
}
可以看到,返回的Iterator
对象接受了Base
的Iterator
和筛选出来的_nextElement
作为参数。我们跟进去来看DropWhileSequence
自己的Iterator
实现:
extension DropWhileSequence {
public struct Iterator {
internal var _iterator: Base.Iterator
internal var _nextElement: Element?
internal init(_ iterator: Base.Iterator, nextElement: Element?) {
_iterator = iterator
_nextElement = nextElement
}
}
}
extension DropWhileSequence.Iterator: IteratorProtocol {
public typealias Element = Base.Element
public mutating func next() -> Element? {
guard let next = _nextElement else { return nil }
_nextElement = _iterator.next()
return next
}
}
这里唯一要说的,就是最后next
的实现。可以看到,DropWhileSequence
表达的序列,是从原始序列中第一个不满足谓词函数的元素开始的。我们用下面这段代码试一下:
let droppedWhile = stdin.drop(while: { $0 != "hehe" })
var dwIterator = droppedWhile.makeIterator()
while let elem = dwIterator.next() {
print("for: \(elem)")
}
执行一下,就会看到类似这样的结果:
>> ha
(1) ha
>> haha
(2) haha
>> hahaha
(3) hahaha
>> hehe
(4) hehe
>> Ctrl + D
for: hehe
当调用stdin.drop(while:)
的时候,DropWhileSequence
的init
方法就会开始读区原始序列的数据。前三次输入,都可以通过谓词函数的检测。第四次输入hehe
的时候,predicate
返回了false
,这时init
函数结束,DropWhileSequence
对象被创建出来。此时的_nextElement
就是第一个不满足predicate
的输入hehe
。
接下来就是while
循环。通过之前的实现我们知道,这时DropWhileSequence
中已经有了一个元素hehe
,但是它的Iterator
还在等待下一次输入。因此,只要按Ctrl + D
结束输入,就可以在控制台看到for: hehe
的结果了。
对于这四次输入来说,站在stdin
的立场,它表示的,就是从开始到结束我们输入的内容。而站在DropWhileSequence
的立场,就是一个把hehe
之前的输入都忽略掉的序列。
最后,来看下drop(while:)
的实现,它的定义在这里:
extension Sequence {
public __consuming func drop(
while predicate: (Element) throws -> Bool
) rethrows -> DropWhileSequence<Self> {
return try DropWhileSequence(self, predicate: predicate)
}
}
其实就是直接创建了DropWhileSequence
对象而已。这里,使用了DropWhileSequence
的另一个init
方法,第一个参数直接传递了原始序列。这个init
方法的定义在这里,其实就是之前我们看到过的init
方法的一层包装而已。
What's next?
通过两节的内容,我们分析了SubSequence
被去掉之后,之前隐藏在AnySequence
背后的三种类型:Array / DropFirstSequence / DropWhileSequence
。掌握了这些内容之后,再去自己分析下面的这三个方法:
public func suffix(_ maxLength: Int) -> [Element]
public func prefix(_ maxLength: Int) -> PrefixSequence<Self>
public func prefix(
while predicate: (Element) throws -> Bool
) rethrows -> [Element]
应该就完全没有难度了,它们的思路和drop
系列的实现是完全一样的。作为练习,大家可以自己去看看代码。从实现的角度上说,这个改动破坏了很多内容,涉及到了ABI的结构。但从使用的角度上说,由于这些新暴露出来的类型仍旧遵从Sequence
,它对我们的影响并没有想象中的严重,只有之前明确依赖了Sequence.SubSequence
类型的代码,才会受到影响。
网友评论