Reputation: 105
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
Upvotes: 0
Views: 907
Reputation: 437882
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)
}
Upvotes: 3
Reputation: 7361
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)!
})
Upvotes: 1
Reputation: 1198
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]
}
}
Upvotes: 0
Reputation: 391
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.
Upvotes: 0
Reputation: 6600
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.
Upvotes: 3