In-depth explanation of Swift high-order functions-Map, Filter, Reduce

In Swift, functions exist 一等公民as actions, so it is still necessary to learn higher-order functions, which can make your code more scalable and more Swift-like.

A first look at higher-order functions

There are several higher-order functions:

  • map: For each element of the given array, execute the mapping in the closure, and return the mapping result in the array.
  • flatMap: For each element of the given array, execute the mapping in the closure, perform the mapping result 合并操做,and then place the result of the merge operation in the array and return.
  • compactMap: For each element of the given array, execute the mapping in the closure, and 非空的return the mapping result in the array.
  • filter: For each element of the given array, perform the operation in the closure and 符合条件的元素return it in the array.
  • reduce: For each element of the given array, execute the operation in the closure to perform the element 合并, and return the combined result.

1、map

mapmethod takes a closure expression as its only parameter.

The closure function is called once for each element in the array and returns the value mapped to that element.

For the map function, the usage scenario is to map the type of the array to another type. For example, we have an array of models, and the id field of the model we get from the server is of String type. In a certain scenario, we need to convert it to Int type. At this time, we can use the map function to realize this requirement. bash

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) //map得到的是一个可选类型的数组 [Optional(1001), Optional(1002), Optional(1003), Optional(1004)]

The above code maps the id field from a String to an Int? type, which is not an Int type. If you want to access elements, you need to unpack them, so how can you map the elements and automatically filter the value of nil? At this time, it is the turn of compactmap.

optional 也可使用map函数Closure

var num: Int? = 2
let result = num.map {
    $0 * 2
}

print(result) // Optional(4)

// ----而不是使用下面的代码
var result: Int?

if let n = num {
    result = n * 2
} else {
    result = nil
}

2、compactMap

Let's replace the above code with: app

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

At this time, if you print intIds again, you will find that it is already of type Int

compactMapValues

For Set and Array, you can use compactMap to get a non-empty collection, but for Dictionary, this function does not work. function

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

At this time, you need to use the compactMapValues ​​function to get a non-empty dictionary. study

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

3、flatMap

For flatMap, the main application scenario is that you want to get an array of single-level collections. Take a look at the difference between map and flapMap through the following code.

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]

map will place the elements directly in the array, while flatMap will flatten the elements in an array. In fact, s.flatMap(transform) equivalent to s.map(transform).joined().

4、filter

This function is just like the meaning of the word: search. Find the elements that meet the conditions and place them in the array to return. For example, you want to find all students who are older than 18 years old.

let adults = stus.filter { (stu) -> Bool in
    stu.age >= 18
}
print(adults) // 数组中只包含stu4 学生
 Count the number of elements in a collection,
let array = ["A", "A", "B", "A", "C"]

// 1.
var count = 0
for value in array {
    if value == "A" {
        count += 1
    }
}

// 2.
count = 0
for value in array where value == "A" {
    count += 1
}

// 3.
count = array.filter { $0 == "A" }.count

We know that it is enough to get the total number of collections .count. In order to make the code in the project more unified and maintainable, instead of having so many ways of writing above, it is better to define a similar extension:count(where:)

extension Sequence where Element: Equatable {
    func count(where isIncluded: (Element) -> Bool) -> Int {
        self.filter(isIncluded).count
    }
}

We directly add extensions to Sequence, so that the pattern is opened again, like ArraySlice, also supports.

["A", "A", "B"].count(where: { $0 == "A" }) // 2

["B", "A", "B"].dropLast(1) // --> ArraySlice<String>
.count(where: { $0 == "B" }) // 1
5、reduce

For reduce, the usage scenario is to perform combined operations on the elements in the array. For example, we want to calculate the ages of all students and load them together.

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

print(totalAges) // 62

The first parameter of this function is the initial value, the first parameter in the following tuple is the result of each calculation, and the second parameter is the element of each traversal. Finally, return the calculated result.

6. Combined use

The biggest benefit of using higher-order functions is the ability to perform functional programming. Let's use a few small chestnuts to combine these high-order functions.

Map the String type to the Int type, and find all students whose id is greater than 1002

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

print(adults) //[1003, 1004]

Calculate the sum of the ages of all students whose age is greater than 12

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

print(totalAge) // 50

7. Remove duplicate objects specified in the collection

In daily development, I think I will encounter such a requirement. I get an array from the network request data, and now I need to delete the duplicate elements in it while ensuring the original order. Look at the first method first:

  • First, inner elements are hashable
extension Sequence where Element: Hashable {
    func uniqued() -> [Element] {
        var seen = Set<Element>()
        return filter { seen.insert($0).inserted }
    }
}

[ 1, 2, 3, 1, 2 ].uniqued()   // [ 1, 2, 3 ]

contains (_:)Internally, Set is used to search for contains, which has a lower time complexity than the array method.

  • Second, inner elements are not hashable

If the elements inside the array are parsed models and do not comply with Hashable, the above method will not work.

extension Sequence {
    func uniqued(comparator: (Element, Element) -> Bool) -> [Element] {
        var result: [Element] = []
        for element in self {
            if result.contains(where: {comparator(element, $0)}) {
                continue
            }
            result.append(element)
        }
        return result
    }
}

let article1 = ArticleModel(title: "111", content: "aa", articleID: "11111", comments: [])
let article2 = ArticleModel(title: "222", content: "aaa", articleID: "22222", comments: [])
let article3 = ArticleModel(title: "111", content: "aaaa", articleID: "33333", comments: [])
let article4 = ArticleModel(title: "333", content: "aaaaa", articleID: "44444", comments: [])
let articles = [article1, article2, article3, article4]

let newarticles = articles.uniqued(comparator: {$0.title == $1.title})
print(newarticles)    //结果 article3 会被删除
  • The third type, keypath version

Finally, there is a keypath version, which is just a layer of encapsulation of the second type, and the second type is still called internally.

extension Sequence {
    func uniqued<T: Equatable>(_ keyPath: KeyPath<Element, T>) -> [Element] {
        uniqued { $0[keyPath: keyPath] == $1[keyPath: keyPath] }
    }
}

let newarticles = articles.uniqued(\.title)

Summarize

  • Using functional programming can not only reduce the number of lines of code, but also use chain structure to build complex logic.
  • When you need to map an array without changing the hierarchical structure of the returned array, use map, otherwise, use flatMap.
  • When the value in the returned array must be non-empty, use compactMap; when the value in the key-value pair in the returned dictionary must be non-empty, use compactMapValues.
  • When you need to query, use filter.
  • Use reduce when you need to perform some calculation on an array and return a value.

Guess you like

Origin blog.csdn.net/RreamigOfGirls/article/details/127072873