mduttondev
mduttondev

Reputation: 105

Sort one String array to match another

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

Answers (5)

Rob
Rob

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

Connor Neville
Connor Neville

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

twiz_
twiz_

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

Dr.Sun
Dr.Sun

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

user28434&#39;mstep
user28434&#39;mstep

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

Related Questions