如果我有收藏品
let initial = [ "a", "b", "c", "d", "e" ]
我想将一个项目从该集合移动到开头(但保持其他项目的顺序完整)
let final = initial.placeFirst { $0 == "b" }
assert(final == [ "b", "a", "c", "d", "e" ])
实施placeFirst
的最佳方式是什么?
我的示例包含Equatable
元素 - 这只是为了使问题可读,遗憾的是在现实生活中并非如此,因此传入placeFirst
的谓词将返回true
对于我想要的项目。
对于我的用例,应该只有一个项与谓词匹配 - 如果有多个匹配,那么在开始时放置任何(或部分或全部)匹配元素都可以。
我有一些想法,但看起来那种问题会有一个非常简洁的解决方案,它使用了我还不知道的收集/序列。
PS我确实知道这听起来像是一个家庭作业问题 - 我保证不是:)
答案 0 :(得分:4)
作为RangeReplaceableCollection
上的变异方法的可能实现(Swift 3):
extension RangeReplaceableCollection {
mutating func placeFirst(where predicate: (Iterator.Element) -> Bool) {
if let index = index(where: predicate) {
insert(remove(at: index), at: startIndex)
}
}
}
示例:
var array = [ "a", "b", "c", "d", "e" ]
array.placeFirst(where: { $0 == "b" })
print(array) // ["b", "a", "c", "d", "e"]
与How do I shuffle an array in Swift?类似,您可以添加 采用任意序列并返回数组的非变异方法:
extension Sequence {
func placingFirst(where predicate: (Iterator.Element) -> Bool) -> [Iterator.Element] {
var result = Array(self)
result.placeFirst(where: predicate)
return result
}
}
示例:
let initial = [ "a", "b", "c", "d", "e" ]
let final = initial.placingFirst { $0 == "b" }
print(final) // ["b", "a", "c", "d", "e"]
答案 1 :(得分:1)
作为MutableCollection
上的一对变异方法的可能实现(不需要调整集合的大小):
extension MutableCollection {
mutating func placeFirst(from index: Index) {
var i = startIndex
while i < index {
swap(&self[i], &self[index]) // in Swift 4: swapAt(i, index)
formIndex(after: &i)
}
}
// in Swift 4, remove Iterator.
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) // ["c", "c", "a", "b", "d", "e", "q"]
在placeFirst(from:)
中,我们只取一个索引,并将起始索引中的所有元素交换到所需的索引,有效地将元素放在开头的给定索引处,并“移动”剩余的元素了。
然后在谓词版本placeFirst(where:)
中,我们迭代并检查集合中所有索引的谓词,如果找到匹配则调用placeFirst(from:)
。
as Martin says,首先构建Array
可以轻松创建所有序列的非变异变体:
extension Sequence {
// in Swift 4, remove Iterator.
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) // ["c", "c", "a", "b", "d", "e", "q"]
为了对Martin's implementation进行基准测试,我将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
Martin对i
的值比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
并且我的{em>稍微更好地使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
:
InProc
在所有这些结果中,第二对通常更可靠,因为两者都应该受益于第一次运行所做的分支预测。