与 Swift 中的另一个数组相比对数组重新排序

Reorder array compared to another array in Swift

我在 Swift 中有一个数组包含一些对象的 ID,我还有另一个数组包含这些对象及其 ID,所以像这样:

class MyObject {
   let id: String
   ...
}

let ids: [String] = ...
let objects: [MyObject] = ...

现在 ID 数组已按某种自定义顺序排序,我需要按相同顺序对对象数组进行排序。例如:

let ids: [String] = ["1", "2", "3"]
let objects: [MyObject] = [obj3, obj1, obj2]
// obj1.id = "1"
// obj2.id = "2"
// obj3.id = "3"
objects.reorder(template: ids) 
// Now objects = [obj1, obj2, obj3]

所以我基本上需要实现这个reorder方法。 有没有人对如何在 Swift 中执行此操作有明智的建议?我正在使用 Swift 3,所以我可以使用所有新的 API。

您可以对 objects 进行排序,以便遵循 ids 写作中的顺序

let sorted = objects.sort { ids.indexOf([=10=].id) < ids.indexOf(.id) }
// [{id "1"}, {id "2"}, {id "3"}]

另一个例子

let ids: [String] = ["3", "2", "1"]
let objects: [MyObject] = [MyObject(id: "3"), MyObject(id: "1"), MyObject(id: "2")]

let sorted = objects.sort { ids.indexOf([=11=].id) < ids.indexOf(.id) }
// [{id "3"}, {id "2"}, {id "1"}]

This code is in Swift 2.2


Swift 4.2 解决方案

let sorted = objects.sorted { ids.index(of: [=12=].id)! < ids.index(of: .id)! }

可能的解决方案:

let sorted = objects.flatMap { obj in
    ids.index(of: obj.id).map { idx in (obj, idx) }
}.sorted(by: { [=10=].1 < .1 } ).map { [=10=].0 }

解释:

  • 首先,每个对象都与相应的对象绑定在一起 id 在数组中的位置,这给出了一个数组 (object, index) 个元组。
  • 此数组已根据索引位置排序。
  • 最后,再次提取对象。

可能的优势:

  • 每个对象只在数组中搜索一次。
  • 忽略数组中不存在 id 的对象。

请注意,如果在 unsorted

中找不到 ID,重新排序方法将失败
func reorder(items: [MyObject], order : [String]) -> [String] {
    var unsorted = items
    var ordered : [String] = []
    for orderId in order {
        let index = unsorted.filter{ [=10=].id == orderId }.first
        let found = unsorted.remove(at: index!)
        ordered.append(found)
    }
    return ordered
}

我不是计算机科学专业的人,但在我看来,这种反复调用 indexOf 的做法很浪费。当然,正确的方法是提前一次遍历模板数组(ids),构建一个字典,将每个元素与其在数组中的位置相关联:

let ids = ["1", "2", "3"]
var d = [String:Int]()
for (ix,id) in ids.enumerated() {
    d[id] = ix
}

现在我们有了字典,在字典中查找很快。所以我们可以使用每个对象的 id 作为键,并根据相应的值进行排序。假设这是我们的初始对象数组:

class MyObject {
    let id: String
    init(id:String) {self.id = id}
}
let objects = [MyObject(id:"3"), MyObject(id:"1"), MyObject(id:"2")]

现在排序是 one-liner:

let objectsSorted = objects.sorted { d[[=12=].id]! < d[.id]! }

如果你知道你将经常使用 ids 作为模板,这里的优势尤其明显,因为你只需要形成字典 d 一次 现在您可以根据该模板多次排序了。实际上,字典记住了排序顺序。 (当然,我没有考虑如果字典查找失败会发生什么;我们就崩溃了。)


我们可以推广这种方法,基于这样一个事实,即为了用作字典键,模板数组中的值必须是可散列的:

struct Cosorter<K:Hashable> {
    let d : [K:Int]
    init(_ arr:[K]) {
        var dd = [K:Int]()
        for (ix,val) in arr.enumerated() {
            dd[val] = ix
        }
        self.d = dd
    }
    func lt(_ v1:K, _ v2:K) -> Bool {
        return self.d[v1]! < self.d[v2]!
    }
}

现在每个模板都被转换成一个用模板数组初始化的Cosorter实例,从而导致准备好字典,一次:

let idsTemplate = Cosorter(ids)

任何时候我们想要在该模板上进行排序,我们只需使用该模板的 lt 作为排序函数:

let objectsSorted = objects.sorted {idsTemplate.lt([=15=].id,.id)}