LAST CHANCE: Save 50% on all my Swift books and bundles! >>

SOLVED: captainFirstSorted Explained

Forums > 100 Days of SwiftUI

Just finished reviewing Day 9 Closures and was hoping someone could explain how the captainFirstSorted works.

I understand the coding syntax but am not sure how it all works. The array "team" has four strings but the captainFirstSorted function has only two strings as parameters. Does the code somehow iterate through the array? Is that taken care of by the sort() function?

Thanks.


func captainFirstSorted(name1: String, name2: String) -> Bool {
    if name1 == "Suzanne" {
        return true
    } else if name2 == "Suzanne" {
        return false
    }

    return name1 < name2
}

let captainFirstTeam = team.sorted(by: captainFirstSorted)
print(captainFirstTeam)

3      

Sometimes you need a different pairs of glasses to see the problem more clearly.

Let's look at a different example. Dump all the forks, spoons, and ONE knife on your kitchen table. How would you sort these? What's a good way to sort knives, forks, and spoons?

There isn't a way, is there? So you have to provide the rules. If you decide to sort them alphabetically, you will be comparing the whole pile, but will do it two pieces at a time.

Is this fork greater than this spoon? Is this knife greater than this fork?

As you look at the pile and sort them you are making this comparison over and over and over and over again. You keep doing this comparison until they are all sorted from forks, the knife, then all the spoons.

Now, add a new rule. The knife should ALWAYS be sorted on the top. So introduce a new comparison in your rule. IF the object on the left is a knife, then it is GREATER than the other object, end of story. If the object on the right is a knife, then it is GREATER than the other object, full stop.

In short, the rules in the sorting algorithm state that if you give me two pieces of cutlery, I should sort them alphabetically. UNLESS one of them is a knife. If either is a knife then it is sorted higher than the other item.

// If you have 50 forks, spoons, and knives on your table, 
// the sorted() function may be called 100s of times!
// Each time the sorted() function is called, it uses these rules.
// The sorted function only compares two items at a time.
func knifeFirstSorted(cutleryOne: String, cutleryTwo: String) -> Bool { 
    if cutleryOne == "knife" { return true } // It's true, the KNIFE is greater than the other object
    else if cutleryTwo == "knife" { return false } // It's false, the other object is NOT greater than the knife.
    return cutleryOne < cutleryTwo  // Otherwise, sort alphabetically
}

// Send in cutlery specific sorting rules
let sortedCutleryDrawer = cutlery.sorted(by: knifeFirstSorted)  
print(sortedCutleryDrawer)

3      

If it helps, the function signature for sorted(by:) is this:

func sorted(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows -> [Element]

As you can see, the parameter name for the closure you pass to sorted(by:) is areInIncreasingOrder. So the purpose of your closure is to be given two elements from the array being sorted and report back to the system whether or not those two items are already sorted in increasing order, i.e., does the 2nd element come after the 1st element? The system will loop through the array, passing in two elements at a time and reordering them (or not) based on the answer it gets from your closure.

3      

Hacking with Swift is sponsored by Essential Developer.

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until July 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Thank you both for your help. Much appreciated. I hope you don't mind but I have a couple of questions for futher clarification.

1) @Obelix , when you say "If the object on the right is a knife, then it is GREATER than the other object, full stop.". What do you mean by "full stop". 2) The function is defined to return a Bool. The If statements obviously return a Bool; however, the final return statement is "return cutleryOne < cutleryTwo //Otherwise, sort alphabetically". I don't recognize that as a Bool. Is it?

3      

cutleryOne < cutleryTwo is a statement that is either true or false (i.e., a Bool), yes?

So return cutleryOne < cutleryTwo returns either true or false, depending on the outcome of the comparison. In other words, it returns a Bool.

3      

Much appreciated. Thanks!

3      

Come for the Swift, get a free English lesson on the side!


Haus asks:

When you say "If the object on the right is a knife, then it is GREATER than the other object, full stop."
What do you mean by "full stop"?

Full Stop is a Briticism. Maybe it's said elsewhere in the world, but used quite a bit in Britain.

At the end of sentences you'll see a small dot of punctuation. In US and Canada, it's call a period. Not sure what's used in Australia, New Zealand and other english speaking locales. In Britain, however, it's call a full stop.

When used in the context above, it's usually meant to add emphasis to the statement. Often the implication is there is nothing more to be said.

So when I said

"If the object on the right is a knife, then it is GREATER than the other object, full stop."

I was pointing out that no other evaluation was necessary. The knife object is always greater than anything else it is compared to. Nothing more to say.

3      

Howdy,

@roosterboy

func sorted(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows -> [Element]

Paul doesn't cover rethrows, but are you saying the func returns a Bool & can also rethrow an Element & said element is: cutleryOne < cutleryTwo

I came here for the exact same question as OP as my brain was not understanding how this code worked...still not 100% clear, but we're getting there.

3      

Hi @Baron_Blackbird - ignore the throws and rethrows for now. Let's for one second pretend that it's not there. You then get the definition of sorted as

func sorted(by areInIncreasingOrder: (Element, Element) -> Bool) -> [Element] 

so as you can see, sorted takes as it's parameter a closure. The areInIncreasingOrder closure which is a closure that takes 2 elements and returns a Bool . The closure that is passed in is the rule that we are stating how we want the sorted function to do it's job sorting the array.

The function sorted actually returns an array of elements [Element]. This is the array of sorted elements. So the function is sorting the entire array based on the rule (closure) that we provide and in the end it returns a sorted array. We don't have to concern with how it's sorting the whole array. All we care about is that we've given it the rule to compare 2 items. It can use that rule go through the whole array and sort it.

Hope this helps clear things up

3      

Hey @Baron_Blackbird...

So @Azzaknight pretty much covered the details of how sorted(by:) works, here's an explanation of the rethrows bit.

You can see that the areInIncreasingOrder parameter takes a function with the following signature:

(Element, Element) throws -> Bool

meaning that it is a function that takes two items of type Element (which is the generic placeholder for the type of your collection elements, whether they are String or Int or whatnot) and returns a Bool indicating if the two elements are already in the correct sort order. Also, the function you supply can throw errors. But, crucially, the throws attribute here means this function can be a function that throws errors, not that it must be a function that throws errors. That "can not must" is important here.

You will only ever see the rethrows attribute on a function that takes another function as a parameter, like in the case of sorted(by:). What it means is: "If the function you have given me throws errors, then so do I. If the function you have given me does not throw errors, then neither do I."

This is useful because it allows you to skip using try and a do {} catch {} block to handle errors if all you are passing in for areInIcreasingOrder is something simple and non-throwing like <:

let names = ["Charlotte", "Shauna", "Mildred", "Linton", "Sonny", "Jack"]
let sortedNames = names.sorted(by: <)
print(sortedNames)
//["Charlotte", "Jack", "Linton", "Mildred", "Shauna", "Sonny"]

Or captainFirstSorted way back upthread in @hausmark's original post.

So, looking again at the definition for sorted(by:):

func sorted(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows -> [Element]

we can describe it as:

sorted(by:) is a function that takes a function of type (Element, Element) throws -> Bool for its areInIncreasingOrder parameter. The areInIcreasingOrder function can be a function that throws errors. If areInIncreasingOrder throws errors, then so does sorted(by:); if areInIncreasingOrder doesn't throw errors, then neither does sorted(by:). Finally, sorted(by:) will return an array of Elements.

3      

Here is my playing around, with Swift's version of closures.

If it helps anyone. I did one closure, that you pass a closure into. Just to see the errors I could trip over.

let team = ["Gloria", "Suzanne", "Piper", "Tiffany", "Tasha"]
let captain = "Suzanne"
print(team)

let descSort = { (s1: String, s2: String) -> Bool in
    s1 > s2
}

let ascSort = { (s1: String, s2: String) -> Bool in
    s1 < s2
}

let captFirst = { (s1: String, s2: String, sort :(String, String)->Bool ) -> Bool in
    if s1 == captain { return true }
    else if s2 == captain { return false }
    return sort(s1, s2)
}

let captSortAsc = { (s1: String, s2: String) -> Bool in
    captFirst(s1, s2, ascSort)
}

let captSortDesc = { (s1: String, s2: String) -> Bool in
    captFirst(s1, s2, descSort)
}
print("ascending:    ", team.sorted(by: ascSort))
print("captain first:", team.sorted(by: captSortAsc))
print("descending:   ", team.sorted(by: descSort))
print("captain first:", team.sorted(by: captSortDesc))

gives output

["Gloria", "Suzanne", "Piper", "Tiffany", "Tasha"]
ascending:     ["Gloria", "Piper", "Suzanne", "Tasha", "Tiffany"]
captain first: ["Suzanne", "Gloria", "Piper", "Tasha", "Tiffany"]
descending:    ["Tiffany", "Tasha", "Suzanne", "Piper", "Gloria"]
captain first: ["Suzanne", "Tiffany", "Tasha", "Piper", "Gloria"]

3      

Sorry to ressurect an old thread but this has been hurting my head too, but I think I now understand how the name1, name2 selection is working.. (maybe) (apologies for the text results posted as code, it was the simplest way to illustrate what I seeing)

@Obelisk

// The sorted function only compares two items at a time.

Are you saying that this will carry out the sort as many times as it needs and will choose any 2 strings to compare if they don't match the specified name1 or name2 ?

using the print method from @dsnovaes (thank you for suggesting this method) the result I got was :

Suzanne is not being compared, so just sort Piper and Gloria
Suzanne is not being compared, so just sort Tiffany and Piper
Suzanne is not being compared, so just sort Tasha and Tiffany
Suzanne is not being compared, so just sort Tasha and Piper

For fun I added another name to the list "Helen" and instead of it checking through 4 times it now did this 8 times.

Suzanne is not being compared, so just sort Piper and Gloria
Suzanne is not being compared, so just sort Tiffany and Piper
Suzanne is not being compared, so just sort Tasha and Tiffany
Suzanne is not being compared, so just sort Tasha and Piper
Suzanne is not being compared, so just sort Helen and Tiffany
Suzanne is not being compared, so just sort Helen and Tasha
Suzanne is not being compared, so just sort Helen and Piper
Suzanne is not being compared, so just sort Helen and Gloria

I then took Suzanne out of the captain seat and added in a ficticous "Beryl", who isn't in the array at all and it then went through the sort process 11 times

let team = ["Gloria", "Suzanne", "Piper", "Tiffany", "Tasha", "Helen"]
// ok, I've created a constant Array called team and set this to Gloria, Suzanne, Piper, Tiffany, Tasha, Helen

let sortedTeam = team.sorted()
// ok, I've created a new constant Array called sortedTeam and I've set this to the values from team but I've sorted this alphabetically
    //["Helen", "Gloria", "Piper", "Suzanne", "Tasha", "Tiffany"]

print(sortedTeam)
print()
// here you are, I've printed the constant Array called sortedTeam.["Helen", "Gloria", "Piper", "Suzanne", "Tasha", "Tiffany"]
//what now?

func captainFirstSorted(name1: String, name2: String) -> Bool {
    //ok I'm creating a function called captainFirstSorted, I'll accept two Strings which are called name1 and name2, and I'm going to return a true or false result with no errors being thrown

    if name1 == "Beryl"{
        print("\(name1) is being compared to \(name2), and \(name1) should come first")
        print()
                return true
        // So, if the first name is equal to Suzanne, I'll return true so that name1 is sorted before name2

    } else if name2 == "Beryl" {
        print("\(name1) should not be before \(name2)")
               return false

        // On the other hand, if name2 is Suzanne, I'll return false so that name1 is sorted after name2
    }
    print("Beryl is not being compared, so just sort \(name1) and \(name2)")
    return name1 < name2
    // If neither name is Suzanne, I'll just use < to do a normal alphabetical sort.

}

let captainFirstTeam = team.sorted(by: captainFirstSorted)
// I'm calling the function and I'm passing in team.sorted ["Gloria","Helen", "Piper", "Suzanne", "Tasha", "Tiffany"] and I'm applying the new sort captainFirstSorted

print(captainFirstTeam)
// here you are, this is your new sorted Array with Suzanne at the front.

result

Beryl is not being compared, so just sort Suzanne and Gloria
Beryl is not being compared, so just sort Piper and Suzanne
Beryl is not being compared, so just sort Piper and Gloria
Beryl is not being compared, so just sort Tiffany and Suzanne
Beryl is not being compared, so just sort Tasha and Tiffany
Beryl is not being compared, so just sort Tasha and Suzanne
Beryl is not being compared, so just sort Helen and Tiffany
Beryl is not being compared, so just sort Helen and Tasha
Beryl is not being compared, so just sort Helen and Suzanne
Beryl is not being compared, so just sort Helen and Piper
Beryl is not being compared, so just sort Helen and Gloria

3      

@Natalie continues an old thread with a great follow-up question:

Are you saying that this will carry out the sort as many times as it needs ......

Yes!

and will choose any 2 strings to compare if they don't match the specified name1 or name2 ?

Not quite! If your array has 1,000 name elements, SwiftUI will send two names at a time into your function to evaluate.

Which two? We don't really know. But let's assume it's somewhat smart about the order.

For instance, if it sends Natalie and Obelix into the function, Natalie is ordered before Obelix. Then it might try Caprial and Natalie. Caprial is ordered before Natalie. In this case, we don't need to compare Caprial to Obelix! SwiftUI can deduce that Caprial is sorted before Natalie and Obelix!

You didn't write the sorted() function. This is built into Swift's Array() structure, but you need to provide the guts of this function!

Take a peek at Apple's documentation for Array:

func sorted(by: (Self.Element, Self.Element) throws -> Bool) rethrows -> [Self.Element]
Returns the elements of the sequence, sorted using the given predicate as the comparison between elements.

In this initializer, Swift says it will give you just TWO elements in your vast array. (Could be thousands of array elements!) You provide the rule to determine which element is GREATER than the other element.

Are glasses > contact lenses?
Are toothbrushes > hair brushes?

You can make up your own sorting rules and provide them as a FUNCTION that takes two array elements and returns a BOOL.

Swift doesn't care how you sort your glasses or toothbrushes. We also don't know the order in which it selects the items from your array. Just be comfortable with the idea that your FUNCTION may be called hundreds of times before SwiftUI returns the final, sorted a array!

Nice to revisit a tough topic!

Keep Coding!

5      

@Obelix, Thank you,(and sorry for the spell check mis-correcting me earlier and resulting in a mis-spell of your name) learning swift becomes a little clearer each day........at least until Paul adds something new for me to wrap my head around, lol

3      

and will choose any 2 strings to compare if they don't match the specified name1 or name2 ?

Not quite! If your array has 1,000 name elements, SwiftUI will send two names at a time into your function to evaluate.

Which two? We don't really know. But let's assume it's somewhat smart about the order.

Thanks @Obelix!

Using the original array from the lesson and the adding a few print statements to the closure:

let team = ["Gloria", "Suzanne", "Piper", "Tiffany", "Tasha"]

func captainFirstSorted(name1: String, name2: String) -> Bool {
    print("name1 is \(name1)")
    print("name2 is \(name2)")
    if name1 == "Suzanne" {
        print("True")
        return true
    } else if name2 == "Suzanne" {
        print("False")
        return false
    }
    print(name1 < name2)
    return name1 < name2
}

let captainFirstTeam = team.sorted(by: captainFirstSorted)
print(captainFirstTeam)

I was super confused as to why Suzane and Gloria were selected as name1 and name2, respectively, on my first loop through the closure.

3      

Hacking with Swift is sponsored by Essential Developer.

SPONSORED Join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer! Hurry up because it'll be available only until July 28th.

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.