0

Let's say retrieve an array of objects from a 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"]
]

On the client, I'd like to allow the user to re-order these objects. To save the order, I'll store a list of ids in an array:

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

How can I reorder the original array of data objects based on the order of ids in the sorting index?

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

So that in the end, I get:

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

3 Answers 3

2

Approach A) Parsing the data into a simple dictionary where the key for the dictionary is the id used to sort it:

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)

Approach B) With some custom DataObject type: (probably best way)

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")
] */

Approach C) With the raw json values, it can be done in a less "clean" way:

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)
Sign up to request clarification or add additional context in comments.

4 Comments

Nope, not quite - my original array is an array of objects, not just key:value pairs. E.g. a looks like: let a = [["id": 48, "name": "Bob"], ["id": 198, "name": "Dave"], ["id": 2301, "name": "Amy"], ["id": 990, "name": "Colette"]]
if a looks like you commented I would suggest considering parsing the JSON array into an array of some custom type like in my answer, but even if you decide to stay with the raw data like you wrote it should be difficult, you would just call something like x["id"].intValue
Yeah, I was being lazy with my question - I'm doing what you're doing with a class instead of a struct, but same principle. Might as well remove the stuff above EDIT - your edit works perfectly!
@remus I left the first solution and added a third one to deal with the raw json data (the "lazy" approach) for the sake of community... glad it helped you
2

You could benefit of the functional support that Swift has, and use the map function to iterate over the index array and get the corresponding object:

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

This assumes that the index array is correctly computed before mapping the data objects.


Edit

Thanks to user3441734 for the flatMap suggestion. Here's a solution that makes use of it, basically it simply replaced map by flatMap in order to get rid of the possible nil values in the mapped array, and to convert the resulting array into an array of non-optionals.

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

If an empty index should result in the same order being kept, then we can simply test for that:

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

5 Comments

nice! I suggest to use flatMap instead.
@user3441734 yes flatMap would be a viable alternative, as it would skip the nil values. Thanks for the suggestion.
One thing to factor in though is that I may not always have a valid index - it may be an empty array, it may have a value that was previously deleted? If you can write up a flatMap option that would take those into consideration, that'd be awesome
@remus if index is empty then what should be the sort oder for the data objects? Similar questions for values that don't have an corresponding value in index.
If index is empty, then the original order should be used. If there are extraneous index values in the sorting array that don't exist in the data, they should be ignored.
1

You don't need to keep an array with the index you can sort by the object you want, let's says you have the following structure:

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"))

The you can use the sort function to sort and return the result in other array or just the sorted function if you want to make it in-place

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

I hope this help you.

2 Comments

This doesn't preserve the original order like the OP is asking, it just sorts the objects by id from lowest to highest.
@JAL Yep sorry I code quickly and I missunderstand the question. Fixed thanks

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.