简体   繁体   中英

Sort array by specified index, prioritizing boolean values while maintaining alphabetical order

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}
]

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

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.

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

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}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM