Higher Order Functions - Swift Programming

HigherOrder Functions

  1. Introduction
  2. Sort
  3. Map
  4. Reduce
  5. Filter

Introduction:

What is Function?


Functions are self-contained chunks of code that perform a specific task. Function  name should give clarity that what it does, and that name is used to “call” the function to perform its task when needed.


Every function in Swift has a type, consisting of the function’s parameter types and return type. You can use this type like any other type in Swift, which makes it easy to pass functions as parameters to other functions, and to return functions from functions. 


Functions can also be written within other functions to encapsulate useful functionality within a nested function scope.


If the entire body of the function is a single expression, the function implicitly returns that expression.


What is HigherOrder Function?


A higher order function is a function that accepts

  1. one or more functions as an input (or)
  2. returns a value of function type as output (or)
  3. doing both
Passing Function

In languages where functions are first-class citizens, functions can be passed as arguments to other functions in the same way as other values.

Returning Function
When returning a function, we are in fact returning its closure.

Explanation:


func sampleFunction() { }


Strictly speaking, this version of the sampleFunction() function does still return a value, even though no return value is defined. Functions without a defined return type return a special value of type Void. This is simply an empty tuple, which is written as ()

sampleFunction have no params and Void as return type (which means no return type). 


So the function type of the sampleFunction would be mentioned as "() -> Void", where () represents no params and Void represents no return values


function type as output param


The below function have no params and  "() -> Void" as return type. So it can return sampleFunction


func higherOrderFunctionReturningFunction() -> () -> Void {

    print(#function, "With in function print statement")

    return sampleFunction

}


higherOrderFunctionReturningFunction() // prints - higherOrderFunctionReturningFunction() With in function print statement


function type as input param


The below function have params "() -> Void" and Void as return type

func higherOrderFunctionAcceptingFunctionAsParams(functionParam: () -> Void) {

    print(#function, "With in function print statement")

    functionParam()

}


Usage:

higherOrderFunctionAcceptingFunctionAsParams { () -> Void in

    print("With in closure - mentioning function type - print statement")

}


higherOrderFunctionAcceptingFunctionAsParams {

    print("With in closure - without mentioning function type - print statement")

}


The above two wouldn't executes their inner blocks until they called in function (calling non-escaping closure), as below


func higherOrderFunctionAcceptingFunctionAsParams(functionParam: () -> Void) {

    print(#function"With in function print statement")

    functionParam()

}


Output:

higherOrderFunctionAcceptingFunctionAsParams(functionParam:) With in function print statement

With in closure - mentioning function type - print statement


higherOrderFunctionAcceptingFunctionAsParams(functionParam:) With in function print statement

With in closure - without mentioning function type - print statement



function type as input & output param


The below function have params "() -> Void" and  "() -> Void" as return type

func higherOrderFunctionDoingBoth(functionParam: () -> Void) -> () -> Void {

    print(#function, "With in function print statement")

    functionParam()

    return sampleFunction

}


Usage:

higherOrderFunctionDoingBoth { () -> Void in

    print("With in closure - mentioning function type - print statement")

}


higherOrderFunctionDoingBoth {

    print("With in closure - without mentioning function type - print statement")

}


Output:

higherOrderFunctionDoingBoth(functionParam:) With in function print statement

With in closure - mentioning function type - print statement


higherOrderFunctionDoingBoth(functionParam:) With in function print statement

With in closure - without mentioning function type - print statement


Swift have default higher-order functions.

  1. Sort
  2. Map
    1. CompactMap
    2. FlatMap
  3. Reduce
  4. Filter

Sort:

Sorts the collection in place. You can sort any mutable collection of elements that conform to the Comparable protocol by calling this method. 

mutating func sort(by areInIncreasingOrder: (Element, Element) throws -> Boolrethrows


Elements are sorted in ascending order by default


var numbers = [1, 2, 5, 3, 8]numbers.sort()print(numbers) // [1, 2, 3, 5, 8]


numbers.sort(by: >)print(numbers) // [8, 5, 3, 2, 1]

The sorting algorithm is not guaranteed to be stable. A stable sort preserves the relative order of elements that compare equal.

Complexity: O(n log n), where n is the length of the collection.


var getEvenNumbersFirst = [8, 2, 5, 1, 3]

getEvenNumbersFirst.sort { (valueOne, valueTwo) -> Bool in

    return valueOne % 2 == 0

}

print(getEvenNumbersFirst// [2, 8, 5, 1, 3]


var getOddNumbersFirst = [8, 2, 5, 1, 3]

getOddNumbersFirst.sort { (valueOne, valueTwo) -> Bool in

    return valueOne % 2 != 0

}

print(getOddNumbersFirst// [3, 1, 5, 8, 2]

Map:

map

Returns an array containing the results of mapping the given closure over the sequence’s elements.

func map<T>(_ transform: (Element) throws -> T) rethrows -> [T]

A mapping closure. transform accepts an element of this sequence as its parameter and returns a transformed value of the same or of a different type.


let cast = ["Vivien", "Marlon", "Kim", "Karl"]

let lowercaseNames = cast.map { $0.lowercased() } // ["vivien", "marlon", "kim", "karl"]


let numbers = ["1", "2", "5", "3", "Sun"]

let numbersMapped = numbers.map { Int($0) } // [Optional(1), Optional(2), Optional(5), Optional(3), nil]


compactMap

Returns an array containing the non-nil results of calling the given transformation with each element of this sequence.

func compactMap<ElementOfResult>(_ transform: (Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]


A closure that accepts an element of this sequence as its argument and returns an optional value.


An array of the non-nil results of calling transform with each element of the sequence.


an array of non-optional values when your transformation produces an optional value.


let numbersCompactMapped = numbers.compactMap { Int($0) } // [1, 2, 5, 3]


Complexity: O(m + n), where n is the length of this sequence and m is the length of the result.


flatMap

Returns an array containing the concatenated results of calling the given transformation with each element of this sequence.

func flatMap<SegmentOfResult>(_ transform: (Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Element] where SegmentOfResult : Sequence

A closure that accepts an element of this sequence as its argument and returns a sequence or collection.


The resulting flattened array.


receive a single-level collection when your transformation produces a sequence or collection for each element.

In this example, note the difference in the result of using map and flatMap with a transformation that returns an array

let numbers = [1, 2, 3, 4]let mapped = numbers.map { Array(repeating: $0, count: $0) }// [[1], [2, 2], [3, 3, 3], [4, 4, 4, 4]] let flatMapped = numbers.flatMap { Array(repeating: $0, count: $0) }// [1, 2, 2, 3, 3, 3, 4, 4, 4, 4]


In fact, 
s.flatMap(transform) is equivalent to Array(s.map(transform).joined()).

Complexity: O(m + n), where n is the length of this sequence and m is the length of the result.

Reduce:
Returns the result of combining the elements of the sequence using the given closure.

func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result
Returns the result of combining the elements of the sequence using the given closure.

The value to use as the initial accumulating value. initialResult is passed to nextPartialResult the first time the closure is executed.

A closure that combines an accumulating value and an element of the sequence into a new accumulating value, to be used in the next call of the nextPartialResult closure or returned to the caller.

The final accumulated value. If the sequence has no elements, the result is initialResult.

Use the reduce(_:_:) method to produce a single value from the elements of an entire sequence.

Complexity: O(n), where n is the length of the sequence.

var numbers = [1, 2, 5, 3, 8]

let numbersSumnumbers.reduce(0) { (partialResult, nextValue) in

    partialResult + nextValue

}


Filter:

Returns an array containing, in order, the elements of the sequence that satisfy the given predicate. (instance of Sequence method)
func filter(_ isIncluded: (Self.Element) throws -> Boolrethrows -> [Self.Element]
A closure that takes an element of the sequence as its argument and returns a Boolean value indicating whether the element should be included in the returned array.

An array of the elements that isIncluded allowed.

Complexity: O(n), where n is the length of the sequence.

let names = ["Sunil""Siri""Sunil N""Sunny"]

let shortNames = names.filter { $0.count <= 5 } // ["Sunil", "Siri", "Sunny"]



Reference Links:

  1. https://gist.github.com/ixcoder001/3b449e39e857077957444e4c1121dbdc

Comments