Swift, as a new language, has absorbed the advantages of many languages, and functional programming is one of them. In Swift, functions are first-class citizens, so learning higher-order functions is necessary to make your code more extensible and Swift compliant. Without further ado, let’s begin!

A preliminary study of higher order functions

In Swift, higher-order functions are as follows:

  • Map: Performs a mapping in the closure for each element of a given array, returns the result of the mapping placed in the array.
  • FlatMap: For each element of a given array, the mapping in the closure is performed on the result of the mappingMerge operation,The result of the merge operation is then returned in an array.
  • CompactMap: For each element of a given array, perform the mapping in the closure, willnon-emptyMapping results placed in an array are returned.
  • CompactMap performs the mapping in the closure for each element of the given array, which willnon-emptyMapping result – key value pair placed in dictionary returns.
  • Filter: Performs the operation in the closure for each element of the given array, and willQualifying elementsPut it in an array.
  • Reduce: For each element of a given array, perform the operations in the closure on the elementsmergeAnd returns the result of the merge.

Now that we have a general idea of what these functions do, let’s look at some examples of how to use them in code.

map

For the map function, the use scenario is to map the type of the array to another type. For example, if we have an array of models, and the id field of the model is a String from the server, we need to convert it to an Int in certain scenarios, and we can use the map function to do that.

struct Student {
    let id: String
    let name: String
    let age: Int
}

let stu1 = Student(id: "1001", name: "stu1", age: 12)
let stu2 = Student(id: "1002", name: "stu2", age: 14)
let stu3 = Student(id: "1003", name: "stu3", age: 16)
let stu4 = Student(id: "1004", name: "stu4", age: 20)
let stus = [stu1, stu2, stu3, stu4]

let intIds = stus.map { (stu) in
    Int(stu.id)
}

print(intIds) //[Optional(1001), Optional(1002), Optional(1003), Optional(1004)]
Copy the code

With the code above, we map the ID field from String to Int? Type, that’s not what we want for an Int. If we need to access elements and we need to unpack them, how do we map elements and automatically filter nil values? At this point, it’s compactMap’s turn.

Optional can also use the map function

var num: Int? = 2
let result = num.map {
    $0* 2}print(result) // Optional(4) // ---- instead of using the following codeif let n = num {
    result = n * 2
} else {
    result = nil
}
Copy the code

compactMap

Let’s replace the above code with:

let intIds = stus.compactMap { (stu) in
    Int(stu.id)
}
Copy the code

If we print intIds, we’ll see that it’s already an Int.

compactMapValues

For sets and arrays, you can use compactMap to get non-empty collections, but for dictionaries, this function does not work.

let dict = ["key1": 10, "key2": nil]
let result = dict.compactMap { $0 }
print(result) //[(key: "key1", value: Optional(10)), (key: "key2", value: nil)]
Copy the code

At this point, we need to use the compactMapValues function to get the non-empty dictionary.

let dict = ["key1": 10, "key2": nil]
let result = dict.compactMapValues { $0 }
print(result) //["key1"10] :Copy the code

flatMap

For flatMaps, the main application scenario is that you want to get an array of single-layer collections. Use the following code to see the difference between map and flapMap.

let scoresByName = ["Henk": [0, 5, 8], "John": [2, 5, 8]]

let mapped = scoresByName.map { $0.value }
// [[2, 5, 8], [0, 5, 8]]
print(mapped)

let flatMapped = scoresByName.flatMap { $0.value }
// [2, 5, 8, 0, 5, 8]

Copy the code

A map places elements directly in an array, while a flatMap tils elements in an array. Actually, s.latmap (transform) is the same as s.map.transform (transform).joined().

filter

This function is exactly what the word means: find. Returns the element that matches the criteria and places it in an array. Let’s say we want to find all students older than 18.

let adults = stus.filter { (stu) -> Bool in
    stu.age >= 18
}

print(adults) // The array contains only STU4 studentsCopy the code

reduce

For reduce, our usage scenario is the combination operation of elements in the array, for example, we want to calculate the age of all students together.

let totalAges = stus.reduce(0) { (result, stu) in
    return result + stu.age
}

print(totalAges) // 62
Copy the code

The first argument to this function is the initial value, the first argument in the subsequent tuple is the result of each evaluation, and the second argument is the element of each iteration. Finally, the result of the calculation is returned.

Use a combination of

One of the biggest benefits of using higher-order functions is that you can do functional programming. Now let’s combine these higher-order functions through several small chestnuts.

Map String to Int and find all students with ids greater than 1002

let adults = stus.compactMap { (stu) in
    Int(stu.id)
    }.filter { (id) -> Bool in
        id > 1002
}

print(adults) //[1003, 1004]
Copy the code

Calculate the sum of the ages of all students older than 12

let totalAge = stus.filter { (stu) -> Bool in
    stu.age > 12
    }.reduce(0) { (result, stu) in
        return result + stu.age
}

print(totalAge) // 50
Copy the code

Implement the above functions yourself

Through the above we know the working principle of these functions, let’s start our own implementation of the following functions, deepen the understanding of the function.

The following functions are just a general idea, not very detailed implementation of official functions.

map

func customMap<T>(translate: (Element) -> T) -> [T] {
    var results = [T]()
    self.forEach { (ele) in
        results.append(translate(ele))
    }
    return results
}
Copy the code

compactMap

func customCompactMap<T>(translate: (Element) -> T?) -> [T] {
    var results = [T]()
    
    for ele in self {
        if let value = translate(ele) {
            results.append(value)
        }
    }
    return results
}
Copy the code

filter

func customFilter(condition: (Element) -> Bool) -> [Element] {
    var results = [Element]()
    
    self.forEach { (ele) in
        if condition(ele) {
            results.append(ele)
        }
    }
    return results
}
Copy the code

reduce

func customReduce<T>(initialvalue: T, produce:(T, Element) -> T) -> T {
    var total = initialvalue
    self.forEach { (ele) in
        total = produce(total, ele)
    }
    return total
}
Copy the code

conclusion

  • Using functional programming not only reduces the number of lines of code, but also builds complex logic using chained structures.
  • Use map when you need to map an array without changing the hierarchy of the returned array, and flatMap when you need to map an array.
  • Use compactMap when the value in the returned array must be non-empty. Use compactMapValues when the value in the key-value pair returned from the dictionary must be non-null.
  • When you need to query, use filter.
  • Use reduce when you need to perform some sort of calculation on an array and return a value.

Mind mapping:

Refer to the link

  • Apple Document
  • CompactMap vs flatMap: The differences explained
  • A behind the scenes look at Map, Filter, and Reduce in Swift