Home > front end >  Is there any way to sort by first and last name using only Swift 5 ?
Is there any way to sort by first and last name using only Swift 5 ?

Time:12-13

I've been wracking my brain trying to figure out a way to sort a string by the first and last name. I have this normal sort implementation going (see below) but wanted to expand it a bit.

Sorting feature:

@objc func handleSortByName() {
        let sortedList: [List] = self.viewModel.names.sorted() { $0.fullName < $1.fullName }
        replaceNewList(newList: sortedList)

    }

I'm thinking of iterating through the string and checking if the character after the space is lower than the next string's character after a space but I can't make sense of that at all.

CodePudding user response:

Iterating over the characters in the strings is doable, but considering that the names consist of two parts that need to be compared separately, and each can vary in length from name to name, it would be tricky to get right and maintain.

An easier way is to split the name into something that contains the first and last names separately, and use that to do the comparison.

To give a code example, I need some explicit format, so let's assume that your names all look like "Bobby Landry", "Sarah McMillan", etc... basically the first non-whitespace substring is the first name, followed by exactly one space, and followed by more non-whitespace that form the last name, with no more spaces. If your actual format is different, you'll need to modify FirstLast.init appropriately.

I also assume that the primary sort should be on last name, with the secondary sort on first name. You can modify the func < implementation to do what you actually need.

@objc func handleSortByName() 
{
    struct FirstLast: Comparable
    {
        let first: Substring
        let last : Substring

        init(_ name: String)
        {
            guard let spaceIndex = name.firstIndex(of: " ") else {
                fatalError("Missing space - do something more appropriate here")
            }
            guard let lastNameStartIndex = name.index(after: spaceIndex) else {
                fatalError("No last name - do something more appropriate here")
            }
            self.first = name[..<spaceIndex]
            self.last  = name[lastNameStartIndex...]
        }

        // Primary sort by last name, secondary sort on first
        static func < (left: Self, right: Self) -> Bool
        {
            return left.last == right.last
                ? left.first < right.first
                : left.last < right.last
        }
    }

    let sortedList: [List] = self.viewModel.names.sorted() {
        FirstLast($0.fullName) < FirstLast($1.fullName)
    }
    replaceNewList(newList: sortedList)
}

I've chosen to make FirstLast a local type, just because I'm assuming nothing else needs it. You can move it outside of handleSortByName, if you think it would be useful for other code.

That should work fine if there aren't too many names; however, it will construct a pair of FirstLast for each and every comparison, because they only live for the comparison closure, and each of those involves searching for the first space in order to split the name. That may be fine for your app.

For large numbers of names, it would make more sense to construct the FirstLast instances just once for the entire sort so they are re-used for all the comparisons. One way is to map the [List] to a [FirstLast] then sort the indices of the [FirstLast] array, which you then use to create your sorted [List]:

@objc func handleSortByName() 
{
    struct FirstLast: Comparable
    {
        // Same implementation as above
        ...
    }

    let firstLastNames = self.viewModel.names.map { FirstLast($0.fullName) }

    var firstLastIndices = [Int](firstLastNames.indices)
    firstLastIndices.sort { firstLastNames[$0] < firstLastNames[$1] }

    let sortedList: [List] = firstLastIndices.map { self.viewModel.names[$0] }

    replaceNewList(newList: sortedList)
}
  • Related