如何解决Swift 数组 reversed()[n] 是否有效?
当您在 Swift 中对数组调用 reversed()
时,您会得到一个 ReverseCollection,它仅使用反向访问包装原始数组。因此这是非常有效的:
let arr = [1,2,3,4]
for i in arr.reversed() { print(i) }
除了访问之外,实际上没有任何东西被逆转;这里 reversed
的时间复杂度是 O(1)。酷!
但是当我用一个整数索引 reversed()
并查看 Quick Help 时,似乎我已经失去了所有的效率;我看到了生成新数组的 Sequence reversed()
:
let arr = [1,4]
let i = arr.reversed()[1] // ???? this is a different `reversed()`!
这似乎是真的,因为 reversed()
数组本身不支持按数字索引:
let arr = [1,4]
let rev = arr.reversed()
let i = rev[1] // compile error!
所以我的问题是:在我的第二个示例中按数字索引到 reversed()
数组中真的会失去 ReverseCollection 索引反转的效率吗?
解决方法
是的,通过 Int
进行索引会导致您失去对反向数组的 O(1)
访问权限。相当有问题!
正如您所注意到的,这里的 reversed()
是一个重载方法;特别是在 Array
上,您有两个定义可供选择:
-
BidirectionalCollection.reversed()
,返回一个ReversedCollection
,以及 -
Sequence.reversed()
,将任何序列转换为反转的[Element]
这里的重载对于 Array
本身来说是最令人困惑的,因为它是唯一一个 Sequence
类型的 type(of: x) == type(of: x.reversed())
。
Swift 类型检查器更喜欢更具体的重载而不是不太具体的重载,因此一般来说,编译器会尽可能使用 BidirectionalCollection
重载而不是 Sequence
重载。问题:BidirectionalCollection
有一个 opaque index type,不能使用 Int
建立索引;当您使用 Int
索引到集合中时,编译器会被迫选择 Sequence
重载而不是 BidirectionalCollection
重载。这也是您的第二个代码示例无法编译的原因:Swift 代码推理不考虑其他行上的周围上下文;就其本身而言,rev
首选为 ReversedCollection<Array<Int>>
,因此尝试使用 Int
对其进行索引会失败。
您可以通过以下方式更清楚地看到这一点:
func collType1<T: Collection>(_: T) {
print(T.self) // ReversedCollection<Array<Int>>
print(T.Index.self) // Index
}
func collType2<T: Collection>(_: T) where T.Index == Int {
print(T.self) // Array<Int>
print(T.Index.self) // Int
}
let x: [Int] = [1,2,3]
collType1(x.reversed())
collType2(x.reversed())
当基于 Int
的索引似乎没有任何其他副作用时,为了避免您怀疑编译器是否可以对此进行优化,在撰写本文时,答案似乎是否定的。 Godbolt output 有点太长,无法在这里重现,但目前,比较
func foo1(_ array: [Int]) {
if array.reversed()[100] > 42 {
print("Wow!")
}
}
与
func foo2(_ array: [Int]) {
if array.reversed().dropFirst(100).first! > 42 {
print("Wow!")
}
}
启用优化后显示 foo2
执行直接数组访问
cmp qword ptr [rdi + 8*rax + 24],43
完全优化掉了 ReversedCollection
包装器,而 foo1
经历了明显更多的间接性。
Ferber 很好地解释了原因。
这是一个临时解决方案(可能不是每个人都喜欢,因为我们是从标准库扩展类型):
// RandomAccessCollection ensures fast index creation
extension ReversedCollection where Base: RandomAccessCollection {
subscript(_ offset: Int) -> Element {
let index = index(startIndex,offsetBy: offset)
return self[index]
}
}
[1,3].reversed()[0] // 3
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。