Leave a rating/review
Notes: 16. Challenge: Closures & Collections
Update Notes: This course was originally recorded in 2019. It has been reviewed and all content and materials updated as of October 2021.
I’ve got another challenge for you! This time you’ll try out using forEach, map, compactMap, and flatMap on your own.
You’ll find everything you need on page 8 of the Playground for this part of the course, including some starter code you might recognize from the Swift Fundamentals course!
Pause the video, give it a try, and then come back to see how I did things.
Hey, welcome back! At the top of this playground page I included the Student class from Programming in Swift: Fundamentals. I also set up some students for you, and collected them in an array.
The first challenge was to replace these two for loops with forEach or map. So, the first part of that challenge is figuring out which method to use to replace which loop.
The important difference you need to remember to figure that out, is that map will return an array for you, but forEach doesn’t return anything.
So, map is the best candidate for creating this libraryBooks array. And forEach is better suited to give extra credit to these slackers.
I’ll try forEach first. I’ll just comment out this for loop, but leave it here for reference. And then I’ll start by calling forEach on students
students.forEach
and I’ll hit return to let that closure stub itself out for me.
students.forEach { (<#Student#>) in
<#code#>
}
I’ll call the parameter student…
students.forEach { (student) in
and, like I said, forEach doesn’t return anything. I could put -> Void here, but I’m not going to. To finish up, the body of the closure can use exactly the same code as the for loop. Call earnExtraCredit on the student!
student.earnExtraCredit()
Run the playground and over on the side, click the little gray rectangle. This will show you the results in the editor so they’re a little easier to read!
That’s working as expected! So let’s give map a try. Again, I’ll comment all of this out, including the empty classLibraryBooks array.
map will make that array for us, so I’ll create a new constant with the same name.
let classLibraryBooks =
And then I can start by calling map on students, and letting the closure autocomplete.
let classLibraryBooks = students.map { (<#Student#>) -> T in
<#code#>
}
I’ll call the parameter student, again
students.map { (student)
and I’m trying to pull out the library books each student has. libraryBooks is a String array, so it can hold multiple book titles for each student. So, String array will be the return type.
-> [String]
The body of this closure can be a little simpler than the code in the for loop. We just need to return the student’s libraryBooks property.
student.libraryBooks
Behind the scenes, map is doing the work of adding each of those arrays to a new array for us. And this time, print out classLibraryBooks to get a better view of the final result.
print(classLibraryBooks)
Run the playground to make sure that worked and there’s all of the books! Ok, Challenge 2! Replace this for loop with compactMap.
Remember that pet is an optional property of a Student. So this code is looping over all of the students, checking to see if they have a pet with if let, and then adding any pets that do exist to the classPets array.
I’ll comment all of this code out, again, and leave it for reference. Then make a new classPets constant.
let classPets =
and try using compactMap to do the same work as that loop and variable combo.
students.compactMap {...
student makes sense, again, as a parameter name. And this closure will return an optional string for the pet.
{ (student) -> String? in
compactMap is doing all the work of checking for that optional’s value, and then adding any real values to an array for us. In the body, then, we just need to return that optional property of student!
student.pet
This is pretty straightforward, right? I think I’ll shorten this one up. I’ll get rid of all of this parameter name and return type, and just use $0.pet.
{ ❌(student) -> String? in❌
$0.pet
}
And if I run the playground now, there’s Mango and Ozma and Kitten hanging out together in an array.
Last challenge! You may have thought to yourself, earlier, “Hey, it would be handy to have all of those library books in one array, instead of an array of arrays.”
And that’s what this challenge is for. To do that with flatMap! Now, you could flatMap the existing classLibraryBooks array, and in the closure just return each element, unchanged. That would work…
classLibraryBooks.flatMap { $0 }
But, really, if thats what we wanted from the start, we should just replace our use of map with flatMap.
So, I’ll undo that and copy that code from Challenge 1, commend it out, and then paste the code down under Challenge 3
let classLibraryBooks = students.map { (student) -> [String] in
student.libraryBooks
}
print(classLibraryBooks)
Now, to fix this so it flattens everything into a single array, I just need to replace map with flatMap
let classLibraryBooks = students.flatMap
That’s it! Run the playground and down in the console you should see a one-dimensional array of book titles!