美文网首页
Swift5 理解SubSequence替代类型的实现

Swift5 理解SubSequence替代类型的实现

作者: 醉看红尘这场梦 | 来源:发表于2020-03-12 09:58 被阅读0次

这一节,我们从剩下的两个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就是BaseElementIterator就是BaseIterator。但是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的时候,可以理解为它只是逻辑上代表去掉了前两次用户输入内容的序列;
  • 第三,当要获取dfIterator时,我们才真正开始输入内容。这时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对象接受了BaseIterator和筛选出来的_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:)的时候,DropWhileSequenceinit方法就会开始读区原始序列的数据。前三次输入,都可以通过谓词函数的检测。第四次输入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类型的代码,才会受到影响。

相关文章

网友评论

      本文标题:Swift5 理解SubSequence替代类型的实现

      本文链接:https://www.haomeiwen.com/subject/quhyjhtx.html