1

This one's a little goofy!

Based on this question, I'm re-ordering an array of objects based on indices in another array:

// pop this in a playground:

struct DataObject {
    let id: String
    let name: String
    let isRequired: Bool

    init(_ id: String, _ name: String, _ isRequired: Bool) {
        self.id = id
        self.name = name
        self.isRequired = isRequired
    }
}

struct User {
    let sortingIndex: [String]

    init(_ sortingIndex: [String]) {
        self.sortingIndex = sortingIndex
    }
}


let a = [DataObject("1", "A", false), DataObject("2", "B", true), DataObject("3", "C", true), DataObject("4", "D", false)]

func sort(a: [DataObject], forUser user: User) -> [DataObject] {
    return a.sort { user.sortingIndex.indexOf($0.id) < user.sortingIndex.indexOf($1.id) }
}

let user = User(["1", "2", "4", "3"])

sort(a, forUser: user)

The output look like this:

[{id "3", name "C", isRequired true}, {id "2", name "B", isRequired true}, {id "1", name "A", isRequired false}, {id "4", name "D", isRequired false}]

(mostly good!)

I want to prioritize the isRequired ones to be at the beginning of the array in alphabetical order, so I tried:

return a.sort { $0.isRequired ? $0.name < $1.name : user.sortingIndex.indexOf($0.id) < user.sortingIndex.indexOf($1.id) }
                                     \\ ^

(which does nothing)

or:

return a.sort { $0.isRequired ? $0.name > $1.name : user.sortingIndex.indexOf($0.id) < user.sortingIndex.indexOf($1.id) }
                                     \\ ^

(which works, but it's in reverse alphabetical order)

I want it to look like this in the end:

[
    {id "2", name "B", isRequired true}, 
    {id "3", name "C", isRequired true}, 
    {id "1", name "A", isRequired false}, 
    {id "4", name "D", isRequired false}
]

4 Answers 4

3

When only one of $0 or $1 is required, you should always return true or false respectively, regardless of the name. So something like this would work:

return a.sort {
        if $0.isRequired && $1.isRequired {
            return $0.name < $1.name
        }
        if $0.isRequired { return true }
        if $1.isRequired { return false }
        return user.sortingIndex.indexOf($0.id) < user.sortingIndex.indexOf($1.id)
    }
Sign up to request clarification or add additional context in comments.

Comments

1

Change your sort to:

a.sort { (lhs, rhs) -> Bool in
    if lhs.isRequired != rhs.isRequired { return lhs.isRequired }
    if lhs.name != hrs.name {return lhs.name < rhs.name}
    return user.sortingIndex.indexOf(lhs.id) < user.sortingIndex.indexOf(rhs.id)
}

Check the isRequired first, then check the name, and finally check the user index.

Comments

1

Here's a cute way of doing it :

return a.sort({  $0.isRequired != $1.isRequired   ? $0.isRequired
               : $0.isRequired                    ? $0.name < $1.name
               : user.sortingIndex.indexOf($0.id) < user.sortingIndex.indexOf($1.id)
              })

Yours is a varying level sort but, in a more general way, multi level sort conditions always have the same pattern :

  $0.level1 != $1.level1     ? $0.level1 < $1.level1
: $0.level2 != $1.level2     ? $0.level2 < $1.level2
...
: $0.levelN-1 != $1.levelN-1 ? $0.levelN-1 < $1.levelN-1
: $0.levelN < $1.levelN

4 Comments

I looked at each of the answers in detail, but I find this one is actually the most concise, and it's a one liner 😉 Also +points for creativity!
Blah! Except the compiler throws this ⊙_⚆ Expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions. I guess I'll have to go with a different one after all - while this works in the playground env, it doesn't in a project.
That's strange, i've used much more complex ones in the past. Perhaps you could make your user.sortIndex class work using a dictionary to make it more compact/concise (and faster too boot)
I just tried it again, and it works (in the playground).
0

You could define the > operator for Bool values and use that to sort the objects array:

func >(lhs: Bool, rhs: Bool) -> Bool {
    return lhs && !rhs
}

let sortedObjects = sort(a, forUser: user).sort{$0.isRequired > $1.isRequired || $0.name < $1.name}

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.