一种可能的实现方式是将其作为
MutableCollection
上的一对变异方法(不需要调整集合大小):
extension MutableCollection {
mutating func placeFirst(from index: Index) {
var i = startIndex
while i < index {
swap(&self[i], &self[index])
formIndex(after: &i)
}
}
mutating func placeFirst(where predicate: (Iterator.Element) throws -> Bool) rethrows {
var i = startIndex
while i < endIndex {
if try predicate(self[i]) {
placeFirst(from: i)
}
formIndex(after: &i)
}
}
}
var initial = ["a", "b", "c", "d", "e", "c", "q"]
initial.placeFirst(where: { $0 == "c" })
print(initial)
在 `placeFirst(from:)` 中,我们只需要一个单一的索引,并交换从起始索引到所需索引的所有元素,有效地将给定索引处的元素放置在开头,并将剩余的元素“向上移动”。
然后在谓词版本 `placeFirst(where:)` 中,我们遍历并检查集合的所有索引与谓词匹配,如果找到匹配项,则调用 `placeFirst(from:)`。
而且,正如
Martin 所说 的那样,可以通过先构造一个 `Array` 轻松创建所有序列的非变异变体。
extension Sequence {
func placingFirst(
where predicate: (Iterator.Element) throws -> Bool
) rethrows -> [Iterator.Element] {
var result = Array(self)
try result.placeFirst(where: predicate)
return result
}
}
let initial = ["a", "b", "c", "d", "e", "c", "q"]
let final = initial.placingFirst(where: { $0 == "c" })
print(final)
为了与
Martin的实现进行基准测试,我改变了我的
placeFirst(where:)
的实现,只考虑谓词匹配的第一个元素,以使两个实现都能短路:
extension MutableCollection {
mutating func placeFirstSwap(from index: Index) {
var i = startIndex
while i < index {
swapAt(i, index)
formIndex(after: &i)
}
}
mutating func placeFirstSwap(where predicate: (Iterator.Element) throws -> Bool) rethrows {
if let index = try index(where: predicate) {
placeFirstSwap(from: index)
}
}
}
extension RangeReplaceableCollection {
mutating func placeFirstInsertRemove(where predicate: (Iterator.Element) throws -> Bool) rethrows {
if let index = try index(where: predicate) {
insert(remove(at: index), at: startIndex)
}
}
}
extension Sequence {
func placingFirstInsertRemove(where predicate: (Iterator.Element) throws -> Bool) rethrows -> [Iterator.Element] {
var result = Array(self)
try result.placeFirstInsertRemove(where: predicate)
return result
}
func placingFirstSwap(where predicate: (Iterator.Element) throws -> Bool) rethrows -> [Iterator.Element] {
var result = Array(self)
try result.placeFirstSwap(where: predicate)
return result
}
}
然后,在 Swift 4 发布版中使用以下设置:
import Foundation
let a = Array(0 ... 50_000_000)
let i = 33_000_000
print("pivot \(100 * Double(i) / Double(a.count - 1))% through array")
do {
let date = Date()
let final = a.placingFirstInsertRemove(where: { $0 == i })
print(final.count, "Martin's:", Date().timeIntervalSince(date))
}
do {
let date = Date()
let final = a.placingFirstSwap(where: { $0 == i })
print(final.count, "Hamish's:", Date().timeIntervalSince(date))
}
print("---")
do {
let date = Date()
let final = a.placingFirstInsertRemove(where: { $0 == i })
print(final.count, "Martin's:", Date().timeIntervalSince(date))
}
do {
let date = Date()
let final = a.placingFirstSwap(where: { $0 == i })
print(final.count, "Hamish's:", Date().timeIntervalSince(date))
}
当
i约为33,000,000时,两种实现似乎具有类似的性能:
pivot 66.0% through array
50000001 Martin's: 0.344986021518707
50000001 Hamish's: 0.358841001987457
---
50000001 Martin's: 0.310263991355896
50000001 Hamish's: 0.313731968402863
在马丁的表现稍微好一些的情况下,例如当
i=45,000,000时:
pivot 90.0% through array
50000001 Martin's: 0.35604602098465
50000001 Hamish's: 0.392504990100861
---
50000001 Martin's: 0.321934998035431
50000001 Hamish's: 0.342424035072327
我的表现略好于这个值以下的
i,例如当
i等于
5_000_000
时:
pivot 10.0% through array
50000001 Martin's: 0.368523001670837
50000001 Hamish's: 0.271382987499237
---
50000001 Martin's: 0.289749026298523
50000001 Hamish's: 0.261726975440979
在所有这些结果中,第二组通常更可靠,因为两个都应该受益于第一次运行时的分支预测。