在Swift中根据另一个数组重新排序数组

3

我有一个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]

我需要实现一个重新排序的方法。有没有聪明的建议如何在Swift中实现这个方法?我使用的是Swift 3,所以所有新的API都对我可用。

4个回答

8
您可以对对象进行排序,以便按照编写ids的顺序进行跟随。
let sorted = objects.sort { ids.indexOf($0.id) < ids.indexOf($1.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($0.id) < ids.indexOf($1.id) }
// [{id "3"}, {id "2"}, {id "1"}]

这段代码是使用 Swift 2.2 编写的


Swift 4.2 解决方案

let sorted = objects.sorted { ids.index(of: $0.id)! < ids.index(of: $1.id)! }

太棒了,这正是我正在寻找的! - Banana
2
在Swift 3中,这将是let sorted = objects.sorted { ids.index(of: $0.id)! < ids.index(of: $1.id)! } - Banana
@Banana的解决方案同样适用于Swift 4,有可能被纳入被接受的答案中。 - Dylan Reich
@DylanReich 我刚刚添加了它。 - JeremyP

5
一种可能的解决方案:
let sorted = objects.flatMap { obj in
    ids.index(of: obj.id).map { idx in (obj, idx) }
}.sorted(by: { $0.1 < $1.1 } ).map { $0.0 }

解释:

  • 首先,每个对象都与数组中对应的id位置绑定在一起,这样就得到了一个(object, index)元组的数组。
  • 该数组按照索引位置进行排序。
  • 最后,再次提取对象。

可能的优势:

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

1
这可能是最好的解决方案(尽管一开始看起来有点难理解)-请注意,内部的flatMap可以只是一个普通的map,因为返回的元组是非可选的 :) - Hamish
@Hamish:你说得对,谢谢。- indexOf 返回一个可选值,但是使用 map 就足够处理了。 - Martin R

1
我不是计算机科学专业的人,但我认为反复调用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")]

现在排序只需要一行代码:

let objectsSorted = objects.sorted { d[$0.id]! < d[$1.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($0.id,$1.id)}

-1
请注意,如果在unsorted中找不到id,则重新排序方法将失败。
func reorder(items: [MyObject], order : [String]) -> [String] {
    var unsorted = items
    var ordered : [String] = []
    for orderId in order {
        let index = unsorted.filter{ $0.id == orderId }.first
        let found = unsorted.remove(at: index!)
        ordered.append(found)
    }
    return ordered
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接