By Will Braynen
As part 2 to my previous post, this post gets us closer to real industry applications, at least within the mobile space and the space of experience APIs.
In my previous post, we saw this Swift example:
[5,5].map { a in a * 2 } // [5,5] -> [10,10]
Go ahead, try it in Xcode’s Playground! Or, equivalently:
[5,5].map { $0 * 2 } // [5,5] -> [10,10]
This transformed [5,5] into [10,10]. This is a clean example, but I have never had to do that in industry. But what I have had to do in industry is something that is very similar to the following.
Given
// MARK: - Types struct Book { let numberOfPages: Int let title: String let author: String } // MARK: - Properties let book1 = Book( numberOfPages: 304, title: "Battleborn", author: "Claire Veye Watkins" ) let book2 = Book( numberOfPages: 248, title: "Her Body and Other Parties", author: "Carmen Maria Machado" ) let book3 = Book( numberOfPages: 240, title: "Dance of the Happy Shades", author: "Alice Munro" ) let books = [book1, book2, book3]
The question
What’s the total number of pages? Or rather: how would you programmatically compute the total number of pages for any array of Book
s?
A right solution
Given what we saw in my previous post, we can now answer The Question by first creating an array that, for each book, only contains page counts (the map step) and then adding up the individual books' page counts (the reduce step). In other words, instead of an array with the books, we would just want [304, 248, 240]. Then, we would want to add up the pages: 304+248+240. Here goes:
let totalPages = books.map { $0.numberOfPages }.reduce(0, +)
You could go a little slower and write this one liner as a two liner:
let pages = books.map { $0.numberOfPages } // [304,248,240] let totalPages = pages.reduce(0, +) // 792
And if you want the mean (colloquially referred to as the average), then all you have to do is divide it by books.count
This is a different application of map
because it feels more like extraction rather than transformation. The extraction of something buried one level deep in the struct instances. But it is perfectly valid nonetheless and can also be combined with reduce
.
The wrong solution
For loops (noun, stress on “for” instead of “loops”). I mean, it’s a solution, but it’s kind of old school. It might not be parallelizable, which would be a pity because otherwise a smart compiler could cut computation time by the number of cores available at runtime. And, debatably, it might be less declarative and so more difficult to understand for someone familiar with map-reduce.
Other cool things you can do in swift
This steps outside map-reduce, but still cool. If you wanted to know the title of the shortest and longest books, then you could do it like so:
let shortestBook = books.min { a, b in a.numberOfPages < b.numberOfPages } let longestBook = books.max { a, b in a.numberOfPages < b.numberOfPages }
Or equivalently:
let shortestBook = books.min { $0.numberOfPages < $1.numberOfPages } let longestBook = books.max { $0.numberOfPages < $1.numberOfPages }
Or equivalently:
let shortestBook = books.min { let pages1 = $0.numberOfPages let pages2 = $1.numberOfPages return pages1 < pages2 } let longestBook = books.max { let pages1 = $0.numberOfPages let pages2 = $1.numberOfPages return pages1 < pages2 }
Whichever of the three ways above you do it, you should now be able to easily get the titles of the shortest and longest books like so:
shortestBook?.title // Dance of the Happy Shades longestBook?.title // Battleborn
No for-loops and no array indices to keep track of. Yay!