【问题标题】:Sort array of objects based on values in another array that correspond to specific properties根据与特定属性对应的另一个数组中的值对对象数组进行排序
【发布时间】:2016-01-21 20:10:24
【问题描述】:

假设从 JSON API 检索对象数组:

[
    {
        "id": 48,
        "name": "Bob"
    },
    {
        "id": 198,
        "name": "Dave"
    },
    {
        "id": 2301,
        "name": "Amy"
    },
    {
        "id": 990,
        "name": "Colette"
    }
]

// e.g. for ease of reproduction:

let dataObjects = [
    ["id": 48, "name": "Bob"], 
    ["id": 198, "name": "Dave"], 
    ["id": 2301, "name": "Amy"], 
    ["id": 990, "name": "Colette"]
]

在客户端,我想允许用户重新排序这些对象。为了保存订单,我将一个 id 列表存储在一个数组中:

let index: [Int] = [48, 990, 2103, 198]

如何根据排序索引中 id 的顺序重新排序原始数据对象数组?

dataObjects.sort({ 
    // magic happens here maybe?
}

所以最后,我得到:

print(dataObjects)
/* 
[
    ["id": 48, "name": "Bob"], 
    ["id": 990, "name": "Colette"], 
    ["id": 2301, "name": "Amy"], 
    ["id": 198, "name": "Dave"]
]
/*

【问题讨论】:

    标签: ios arrays swift sorting


    【解决方案1】:

    方法 A) 将数据解析为一个简单的字典,其中字典的键是用于对其进行排序的 id:

    func sort<Value>(a: [Int: Value], basedOn b: [Int]) -> [(Int, Value)] {
        return a.sort { x, y in
            b.indexOf(x.0) < b.indexOf(y.0)
        }
    }
    
    // ....
    let a = [48:"Bob", 198:"Dave", 2301:"Amy", 990:"Colette"]
    let b = [48, 198, 2301, 990]
    sort(a, basedOn: b)
    

    方法 B) 使用一些自定义 DataObject 类型:(可能是最好的方法)

    struct DataObject {
        let id: Int
        let name: String
    
        init(_ id: Int, _ name: String) {
            self.id = id
            self.name = name
        }
    }
    
    func sort(a: [DataObject], basedOn b: [Int]) -> [DataObject] {
        return a.sort { x, y in
            b.indexOf(x.id) < b.indexOf(y.id)
        }
    }
    
    
    let a = [DataObject(48, "Bob"), DataObject(198, "Dave"), DataObject(2301, "Amy"), DataObject(990, "Colette")]
    let b = [48, 198, 2301, 990]
    
    sort(a, basedOn: b)
    /* [
         DataObject(id: 48, name: "Bob"), 
         DataObject(id: 990, name: "Colette"), 
         DataObject(id: 198, name: "Dave"), 
         DataObject(id: 2301, name: "Amy")
    ] */
    

    方法 C) 使用 raw json 值,可以以不那么“干净”的方式完成:

    func sort<Value>(a: [[String: Value]], basedOn b: [Int]) -> [[String: Value]] {
        return a.sort { x, y in
            let xId = x["id"] as! Int
            let yId = y["id"] as! Int
            return b.indexOf(xId) < b.indexOf(yId)
        }
    }
    
    let dataObjects = [
        ["id": 48, "name": "Bob"],
        ["id": 198, "name": "Dave"],
        ["id": 2301, "name": "Amy"],
        ["id": 990, "name": "Colette"]
    ]
    
    let b = [48, 198, 2301, 990]
    
    sort(dataObjects, basedOn: b)
    

    【讨论】:

    • 不,不完全——我的原始数组是一个对象数组,而不仅仅是键:值对。例如。 a 看起来像:let a = [["id": 48, "name": "Bob"], ["id": 198, "name": "Dave"], ["id": 2301, "name": "Amy"], ["id": 990, "name": "Colette"]]
    • 如果a 看起来像您评论了我会建议考虑将 JSON 数组解析为一些自定义类型的数组,就像我的回答一样,但即使您决定保留 raw 像你写的数据应该很难,你只需调用类似x["id"].intValue
    • 是的,我对我的问题很懒惰 - 我正在用类而不是结构做你正在做的事情,但原理相同。不妨删除 EDIT 上面的内容 - 您的编辑效果很好!
    • @remus 我离开了第一个解决方案,并添加了第三个解决方案来处理原始 json 数据(“惰性”方法)为了社区......很高兴它帮助了你
    【解决方案2】:

    您可以受益于 Swift 的功能支持,并使用 map 函数遍历 index 数组并获取相应的对象:

    let sortedObjects = index.map{ id in
        dataObjects.filter{ $0["id"] as? Int == id }.first
    }
    

    这假设在映射数据对象之前正确计算了index 数组。


    编辑

    感谢 user3441734 的 flatMap 建议。这是一个使用它的解决方案,基本上它只是将map 替换为flatMap 以消除映射数组中可能的nil 值,并将生成的数组转换为非可选数组.

    let sortedObjects = index.flatMap{ id in
        dataObjects.filter{ $0["id"] as? Int == id }.first
    }
    

    如果一个空的index 应该导致保持相同的顺序,那么我们可以简单地测试一下:

    let sortedObjects = index.count > 0 ? index.flatMap{ id in
        dataObjects.filter{ $0["id"] as? Int == id }.first
    } : dataObjects
    

    【讨论】:

    • 不错!我建议改用 flatMap。
    • @user3441734 是的 flatMap 将是一个可行的替代方案,因为它会跳过 nil 值。感谢您的建议。
    • 要考虑的一点是,我可能并不总是有一个有效的索引——它可能是一个空数组,它可能有一个以前被删除的值?如果您可以编写一个将这些考虑在内的 flatMap 选项,那就太棒了
    • @remus 如果index 为空,那么数据对象的排序顺序应该是什么? index 中没有对应值的值的类似问题。
    • 如果索引为空,则应使用原始顺序。如果排序数组中存在数据中不存在的无关索引值,则应忽略它们。
    【解决方案3】:

    您不需要保留一个带有索引的数组,您可以按您想要的对象排序,假设您具有以下结构:

    struct DataObject {
       var id: Int
       var name: String
    }
    
    var data = [DataObject]()
    data.append(DataObject(id: 48, name: "Bob"))
    data.append(DataObject(id: 198, name: "Dave"))
    data.append(DataObject(id: 2301, name: "Amy"))
    data.append(DataObject(id: 990, name: "Colette"))
    

    您可以使用sort 函数对结果进行排序并在其他数组中返回结果,或者如果您想就地进行排序,则可以只使用sorted 函数

    // ordered by id
    var sortedData = data.sort { index.indexOf($0.id) < index.indexOf($1.id) }
    

    希望对你有所帮助。

    【讨论】:

    • 这不会像 OP 要求的那样保留原始顺序,它只是按 id 从低到高对对象进行排序。
    • @JAL 是的,对不起,我的代码很快,我误解了这个问题。固定感谢
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-06-17
    • 1970-01-01
    • 2019-06-11
    • 1970-01-01
    • 1970-01-01
    • 2021-11-20
    相关资源
    最近更新 更多