将项目移动到集合开头的最佳方法

时间:2017-07-19 12:21:44

标签: arrays swift

如果我有收藏品

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我确实知道这听起来像是一个家庭作业问题 - 我保证不是:)

2 个答案:

答案 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))
}

i33_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

在所有这些结果中,第二对通常更可靠,因为两者都应该受益于第一次运行所做的分支预测。