为什么lazy.compactMap.first映射“第一个”元素两次?

Dmi*_*lov 1 swift

我正在测试 CompactMap 的惰性数组,以查找第一个元素并将其映射到几行代码中。

"abc5def".lazy
  .compactMap {
    print($0)
    return Int(String($0))
}.first as Int?
Run Code Online (Sandbox Code Playgroud)

印刷

a
b
c
5
5
Run Code Online (Sandbox Code Playgroud)

为什么最后一个元素被映射两次。如何避免这种行为?

Cri*_*tik 7

TL;DRcompactMap调用返回一个惰性序列链LazyMapSequence<LazyFilterSequence<LazyMapSequence<...,再加上first需要计算起始索引以及该起始索引处的元素的事实,导致转换闭包被调用两次:

  1. 何时startIndex计算
  2. 检索起始索引处的元素时

compactMap是over 的当前实现LazySequenceProtocol(所有惰性序列都遵循的协议):

public func compactMap<ElementOfResult>(
    _ transform: @escaping (Elements.Element) -> ElementOfResult?
  ) -> LazyMapSequence<
    LazyFilterSequence<
      LazyMapSequence<Elements, ElementOfResult?>>,
    ElementOfResult
  > {
    return self.map(transform).filter { $0 != nil }.map { $0! }
}
Run Code Online (Sandbox Code Playgroud)

这会让你"abc5def".lazy.compactMap { ... }成为一个有型的人LazyMapSequence<LazyFilterSequence<LazyMapSequence<String, Optional<Int>>>, Int>

其次,您询问的是first惰性序列中的元素。这解决了协议的默认实现(如果它们的基本序列也是一个集合,则所有惰性序列都会自动符合):firstCollectionCollection

public var first: Element? {
    let start = startIndex
    if start != endIndex { return self[start] }
    else { return nil }
}
Run Code Online (Sandbox Code Playgroud)

这意味着first必须检索两条信息:

  1. 起始索引
  2. 起始索引处的值(下标部分)

现在,由于这种实现startIndex,导致重复计算的是计算:LazyFilterSequence

public var startIndex: Index {
    var index = _base.startIndex
    while index != _base.endIndex && !_predicate(_base[index]) {
      _base.formIndex(after: &index)
    }
    return index
}
Run Code Online (Sandbox Code Playgroud)

subscript上面的实现是LazyMapSequence一个标准的实现:

public subscript(position: Base.Index) -> Element {
    return _transform(_base[position])
}
Run Code Online (Sandbox Code Playgroud)

但是,正如您所看到的,转换被再次调用,导致您看到第二个打印结果。