为什么lazy.compactMap.first映射'first'元素两次?



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

"abc5def".lazy
.compactMap {
print($0)
return Int(String($0))
}.first as Int?

指纹

a
b
c
5
5

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

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

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

这是compactMapoverLazySequenceProtocol(所有惰性序列都遵循的协议(的当前实现:

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

这使您的"abc5def".lazy.compactMap { ... }属于LazyMapSequence<LazyFilterSequence<LazyMapSequence<String, Optional<Int>>>, Int>

。其次,您询问的是惰性序列中的first元素。这解析为通过Collection协议默认实现first(如果所有惰性序列的基本序列也是集合,则会自动符合Collection(:

public var first: Element? {
let start = startIndex
if start != endIndex { return self[start] }
else { return nil }
}

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

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

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

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

LazyMapSequencesubscript实现是标准的:

public subscript(position: Base.Index) -> Element {
return _transform(_base[position])
}

但是,如您所见,将再次调用转换,从而生成您看到的第二次打印。

最新更新