0

What would be the cleanest way to sort one array to match another array:

Example:

public class Account {

    public var identifier: String

    init(id identifier:String) {
        self.identifier = identifier
    }

}


let knownOrder = ["abc", "klm", "def", "hij"]

var givenOrder = [Account(id: "abc"), Account(id: "def"), Account(id: "hij"), Account(id: "klm")]

what would be the easiest way to make the output for givenOrder match knownOrder without altering knownOrder?

Added a little more to the example. Im trying to get the given list of Account objects with an identifier property to be in the same order as a stored list of strings which match the users preference to have their accounts

6
  • 2
    Why would you want to do this? Aren't you just asking to set givenOrder = knownOrder? Commented Oct 20, 2016 at 13:24
  • if i understand you correctly....why not just set them equal to each other? (agreeing with @ConnorNeville) Commented Oct 20, 2016 at 13:26
  • This is just more of a contrived example. knownOrder would be a list of account identifiers and givenOrder would be an array of JSON objects where one field is the identifier. I just want to sort the received objects to match the stored order a user would have set previously. Commented Oct 20, 2016 at 13:30
  • 1
    That's definitely a different question. Post an actual example, as any answer would depend on the structure of the 2 objects. Commented Oct 20, 2016 at 13:32
  • Added a little more to the example. Im trying to get the given list of Account objects with an identifier property to be in the same order as a stored list of strings which match the users preference to have their accounts Commented Oct 20, 2016 at 13:45

5 Answers 5

3

Do you need something like that?

let knownOrder = ["a1", "b2", "c3", "d4"]
var givenOrder = ["c3", "a1", "d4", "b2"]

givenOrder.sort { (lhs, rhs) -> Bool in
    (knownOrder.index(of: lhs) ?? 0) < (knownOrder.index(of: rhs) ?? 0)
}

?? 0 is there in case if givenOrder contains values not in knownOrder, those values would be unsorted at the beginning of a list.

Sign up to request clarification or add additional context in comments.

2 Comments

So I took this and modified it a bit to: givenOrder.sort { (lhs, rhs) -> Bool in (knownOrder.index(of: lhs) ?? knownOrder.count) < (knownOrder.index(of: rhs) ?? knownOrder.count) }
That makes any values in givenOrder that are not in knownOrder move to the end in an unsorted manner. Does that seem right?
3

In Swift 5, index(of:) has been replaced by firstIndex(of:):

let results = givenOrder.sorted {
    (knownOrder.firstIndex(of: $0. identifier) ?? 0) < (knownOrder.firstIndex(of: $1. identifier) ?? 0)
}

Note, though, that firstIndex(of:) (previously known as index(of:)) is O(n) and doing that repeatedly in the sorted closure is a little inefficient. You might want to build a dictionary:

let order = Dictionary(uniqueKeysWithValues: knownOrder.enumerated().map { ($0.1, $0.0) })

And then you now enjoy O(1) performance when you look up each objects’ order with in the order dictionary:

let results = givenOrder.sorted {
    (order[$0.identifier] ?? 0) < (order[$1.identifier] ?? 0)
}

Comments

1

You can accomplish this with:

givenOrder = givenOrder.sort({ 
    (knownOrder.indexOf($0.identifier) ?? 0) < 
    (knownOrder.indexOf($1.identifier) ?? 0) 
})

If you're positive that knownOrder will contain all of the identifiers (and aren't concerned about a potential crash if it didn't), you can condense this to:

givenOrder = givenOrder.sort({ 
    knownOrder.indexOf($0.identifier)! < 
    knownOrder.indexOf($1.identifier)!
})

2 Comments

Could this be applied to an array of SCNVector4s?
This code just uses Swift's builtin sort function on an array of objects. As long as you have an array, and some sort of criteria for determining how to sort 2 of the thing, this applies.
0

The easiest way is use "for in".

let knownOrder = ["a1", "b2", "c3", "d4"]
var givenOrder = ["c3", "a1", "d4", "b2"]

var result = [String]()

for item in knownOrder {
    if let index = givenOrder.index(of: item) {
        result.append(item)
        givenOrder.remove(at: index)
    }
}

for item in givenOrder {
    result.append(item)
}

print(result)

Loop each item of knownOrder, if the item also contains in givenOrder add it to result, and remove it from givenOrder.

Then add the left items of givenOrder to the result.

So easy to understand, but if you have a big data, the code may be very slow.

1 Comment

I agree that this is the most 'Straight forward' but as you suggested I'm working with big data so just looping over will be expensive. Example seems simple but between big data and my brain just not working today ... ¯_(ツ)_/¯ here I am
0

You could make a subscriptable class to manage the accounts, though I'm not sure how slow searching a dictionary is for big data...

class AccountManager {

var accounts: [String: Account]

init(accounts: [Account]) {
    var accountDict = [String: Account]()
    for i in accounts {
        accountDict[i.identifier] = i
    }
    self.accounts = accountDict
}

func sort(by ids: [String]) -> [Account] {

    return ids.map({accounts[$0]}).flatMap({$0})
}

subscript(id: String) -> Account? {
    return accounts[id]
}
}

Comments

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.