我正在测试惰性数组的compactMap,以找到第一个元素并在几行代码中映射它。
"abc5def".lazy
.compactMap {
print($0)
return Int(String($0))
}.first as Int?
指纹
a
b
c
5
5
为什么最后一个元素被映射两次。如何避免这种行为?
TL;DRcompactMap
调用返回一个惰性序列链LazyMapSequence<LazyFilterSequence<LazyMapSequence<...
,再加上first
需要同时计算起始索引以及该起始索引处的元素,导致变换闭包被调用两次:
- 计算
startIndex
时
在 - 起始索引处检索元素时
这是compactMap
overLazySequenceProtocol
(所有惰性序列都遵循的协议(的当前实现:
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
必须检索两条信息:
- 起始索引
- 起始索引处的值(下标部分(
现在,由于LazyFilterSequence
的实现,导致重复评估的是startIndex
计算:
public var startIndex: Index {
var index = _base.startIndex
while index != _base.endIndex && !_predicate(_base[index]) {
_base.formIndex(after: &index)
}
return index
}
LazyMapSequence
subscript
实现是标准的:
public subscript(position: Base.Index) -> Element {
return _transform(_base[position])
}
但是,如您所见,将再次调用转换,从而生成您看到的第二次打印。