Wednesday 18 November 2015

Higher Order Functions: Map, Filter, Reduce and more

The code in this post was written in Xcode 6 beta 5. It may not work in future releases of Xcode without changes. All code examples are using Swift’s trailing closure syntax.
For-in-loops are often used to iterate through an array of objects, modifying another object with each iteration. For example, looping through an array of numbers in order to create a new array of numbers formatted as strings:

let numbers = [ 10000, 10303, 30913, 50000, 100000, 101039, 1000000 ]
var formattedNumbers: [String] = []  
for number in numbers {
    let formattedNumber =
          NSNumberFormatter.localizedStringFromNumber(number, numberStyle: .DecimalStyle)
   formattedNumbers.append(formattedNumber)
}

// [ "10,000", "10,303", "30,913", "50,000", "100,000", "101,039", "1,000,000" ]


With Swift, iOS and Mac developers have map, filter, and reduce methods on arrays. In my experience, these methods can replace almost all uses of for-in-loops, and are more clear and concise.

Map

The map method takes a function (transform), and returns an array containing the results of calling transform on each element in the array. The example above could be written like this:

let formattedNumbers = numbers.map {
            NSNumberFormatter.localizedStringFromNumber
                                             ($0, numberStyle: .DecimalStyle)
   }

// [ "10,000", "10,303", "30,913", "50,000", "100,000", "101,039", "1,000,000" ]

Filter

The filter method takes a function (includeElement) which, given an element in the array, returns a Bool indicating whether the element should be included in the resulting array. For example, removing all the odd numbers from the numbers array could be done like this:

let evenNumbers = numbers.filter { $0 % 2 == 0 }  

// [ 10000, 50000, 100000, 1000000 ]

Reduce

The reduce method reduces an array into a single value. It takes two parameters: a starting value, and a function which takes a running total and an element of the array as parameters, and returns a new running total. For example, getting the sum of all the numbers in an array could be done like this:

let total = numbers.reduce(0) { $0 + $1 }  

// 1302255 

Chaining

These three methods can be very powerful, especially when they are chained. For example, given an array of numbers, we can filter out all odd numbers, map the remaining numbers into strings using NSNumberFormatter, and reduce those strings into one string, with a formatted number on each line:

let evenNumbersAsString = numbers
       .filter { $0 % 2 == 0 }
       .map { NSNumberFormatter.localizedStringFromNumber($0, numberStyle: .DecimalStyle) }
       .reduce("") { countElements($0) == 0 ? $1 : $0 + "\n" + $1 }

// "10,000\n50,000\n100,000\n1,000,000"

Monday 9 February 2015

Nil Coalescing Operator Swift

The nil coalescing operator (a ?? b) unwraps an optional a if it contains a value, or returns a default value b if a is nil. The expression a is always of an optional type. The expression b must match the type that is stored inside a.

The nil coalescing operator is shorthand for the code below:

a​ != ​nil​ ? ​a​! : ​b
The code above uses the ternary conditional operator and forced unwrapping (a!) to access the value wrapped inside a when a is not nil, and to return b otherwise. The nil coalescing operator provides a more elegant way to encapsulate this conditional checking and unwrapping in a concise and readable form.


NOTE

If the value of a is non-nil, the value of b is not evaluated. This is known as short-circuit evaluation.

The example below uses the nil coalescing operator to choose between a default color name and an optional user-defined color name:

let defaultColorName = "red"
var userDefinedColorName: String?     // defaults to nil

var colorNameToUse = userDefinedColorName ?? defaultColorName

// userDefinedColorName is nil, so colorNameToUse is set to the default of "red"

The userDefinedColorName variable is defined as an optional String, with a default value of nil. Because userDefinedColorName is of an optional type, you can use the nil coalescing operator to consider its value. In the example above, the operator is used to determine an initial value for a String variable called colorNameToUse. Because userDefinedColorName is nil, the expression userDefinedColorName ?? defaultColorName returns the value of defaultColorName, or "red".

If you assign a non-
nil value to userDefinedColorName and perform the nil coalescing operator check again, the value wrapped inside userDefinedColorName is used instead of the default:

userDefinedColorName = "green"
 colorNameToUse = userDefinedColorName ?? defaultColorName

// userDefinedColorName is nil, so colorNameToUse is set to the default of "green"