Codementor Events

Swift Higher Order Functions .map

Published Jul 27, 2018Last updated Jan 23, 2019
Swift Higher Order Functions .map

Higher Order functions in Swift can be confusing at first glance. However, once you break it down they aren't intimidating. You'll start using more often than you think.

.map

Let's focus on the .map function...

Can you think of a time like this? You have an array of Address objects (or similar) but you just need an array of the street names. That's where you can use .map.

If you find yourself saying "I have a collection of X but I need to convert (map) it to an array of Y" - that's where .map shines.

Examples:

  • Map an array of user accounts to emails
  • Convert an array of addresses to coordinates
  • Take an array of contacts and get a list of just phone numbers

.map - take a array of some number of items and produce an array of other items of the same quantity

Code Example

Let's take a scenario where we have an array of structs called Address. The properities are:

  • street
  • city
  • state
  • postal

Struct

struct Address {
    var street = ""
    var city = ""
    var state = ""
    var postal = 0
}

Array

We create multiple instances of it into an array:

let a1 = Address(street: "100 Main", city: "Mandeville", state : "LA", postal: 70448)
let a2 = Address(street: "200 Main", city: "Denton", state : "TX", postal: 76210)
let a3 = Address(street: "300 Main", city: "Steamboat Springs", state : "CO", postal: 80487)

let addresses = [a1, a2, a3]

.map Closure

The code completion helps us out when we try to use .map on addresses:

mapClosureType.png

So .map takes a closure of type: (Address) throws -> T

Return Type

That means we need to write a closure that takes an Address and returns something. But what?

Whatever we want. 😃

Our closure returns some type and .map will return an array of that type. So if our closure returns a String, the .map call ultimately returns an array of Strings.

In that case, zips' type is Array<String>.

.map Return Type

Our zips constant implies we're getting the postal code. So our closure just needs to return the postal. In Address postal is defined as an Int (inferred type from 0).

So our closure needs to return an Int: the postal property.

let zips = addresses.map { (addr) -> Int in
    return addr.postal
}

Our closure takes each Address as the parameter (addr). It returns the postal property (Int) and zips is now an array of Ints that are all the postal values. It will have the same number of items and in the same order as addresses.

Lab Work

Your turn. Open a Playground, take the code above and add calls to addresses.map. Here's some things to try...

  1. Map to an array of just streets:
    Q1. What's the closure return type?

  2. Map to an array of Strings that are the full addresses (e.g., "100 Main Mandeville, LA 70448")
    Q2. What's the resulting array type?

  3. Map to an array of the length of the city value (e.g., .count)
    Q3. How many values are in the resulting array?

Answers below the Bonus...

If you find yourself saying "I have a collection of X but I need to convert (map) it to an array of Y" - that's where .map shines.

Bonus

For closures, the return type can be inferred, the parameters can be replaced with shorthand argument names and single line closures can remote the return. So the call to .map could be like this:

let zips2 = addresses.map { $0.postal }

This means:

  • The return type of the closure is Int.
  • The shorthand argument name $0 was used in place of naming the parameter.
  • The single line evaluation is returned without the return statement.

Answers

  1. Map to an array of just streets:
    Q1. What's the closure return type?
    A: String
// return .street (String) to create an array of all the street vals
let streets = addresses.map { (addr) -> String in
    return addr.street
}
  1. Map to an array of Strings that are the full addresses (e.g., "100 Main Mandeville, LA 70448")
    Q2. What's the resulting array type?
    A: Array<Int>
// return an interpolated String of the values to make an array of full addresses
let fullAddresses = addresses.map { (addr) -> String in
    return "\(addr.street) \(addr.city), \(addr.state) \(addr.postal)"
}
  1. Map to an array of the length of the city value (e.g., .count)
    Q3. How many values are in the resulting array?
    A: 3. The same number as the original array - always.
// return the .count of the city to create an array of the city (String) lengths
let lens = addresses.map { (addr) -> Int in
    return addr.city.count
}

Of course you can more functionality in your closure if you need to. You might want to process the values somehow or even load data from a file. I'd warn against doing too much which might affect performance.

Also, your return type doesn't have to be a primitive type. You might have an array of Strings and want to create instances of Address out of them. Your return type then would be Address (or whatever your class is).

I hope this has been helpful!

Feel free to keep in touch: BrainwashInc.com, @brainofbear, Lynda.com/Linkedin Learning courses and Linkedin.

Discover and read more posts from Bear Cahill
get started
post commentsBe the first to share your opinion
Show more replies