Mutation and Stateful closures (28)
When we access the whole array, you can use “map” to perform a side effect (eg adding an element to the array being searched). We do not recommend doing that. Take a look at the following paragraph:
1 2 3 4 | array.map { item in table.insert(item) } |
The side effect has been hidden (the part that changes the lookup table) in a structure that looks like an array transformation. If you see something like this, it is used to explicitly describe the loop within the method as a map. The forEach function is also more suitable than “map” in this case, but it has its own problems. We will learn about forEach later.
The effect of the side effect is different from creating local closure states, which is a useful skill for creating closures – a method of capturing and transforming values outside the scope – public. powerful tool when combined with higher-order methods. For example, the storage method described below can be implemented with map and stateful closures:
1 2 3 4 5 6 7 8 9 10 11 12 | extension Array { func accumulate<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) -> Result) -> [Result] { var running = initialResult return map { next in running = nextPartialResult(running, next) return running } } } |
It will create a temporary variable to store the current value and then use a map to create an array of the current values as the calculation process:
1 2 | [1,2,3,4].accumulate(0, +) // [1, 3, 6, 10] |
Note that this code assumes that “map” performs the sequence conversion on the string. In our case “map” above. But there are implementations that can possibly transform sequences in order – for example, one that performs simultaneous conversion of elements. The official standard library version of “map” does not specify whether or not it can be sequenced, although it appears to be safe.
Filter
It is very common for an operator to get an array, creating a new array with new elements with certain conditions. The model iterates over an array and selects the appropriate values and records them in the “filter” function:
1 2 3 | let nums = [1,2,3,4,5,6,7,8,9,10] nums.filter { num in num % 2 == 0 } // [2, 4, 6, 8, 10] |
We can use the shorthand argument of a closure expression to make them simpler. Instead of the “num” argument’s name, we can write the replacement code as follows:
1 2 | nums.filter { $0 % 2 == 0 } // [2, 4, 6, 8, 10] |
With such a short closure, it is still readable. If closures become more complicated, it is better to name the arguments explicitly, just as we did before. This is a personal problem, your favorite style. One thing is essential for this: If closures are suitable for presenting on one line, abbreviating arguments is a good idea.
With the combination of “map” and “filter”, we can easily write a lot of computational expressions on arrays without using intermediate variables, and the result of the code will become short and easy to read and understand. than. For example, to find all squares area below 100, we can “map” in the range 0 .. <10, calculate the area of it, and then filter out all the odd numbers:
1 2 | (1..<10).map { $0 * $0 }.filter { $0 % 2 == 0 } // [4, 16, 36, 64] |
The implementation with “filter” is the same as “map”:
1 2 3 4 5 6 7 8 9 10 | extension Array { func filter(_ isIncluded: (Element) -> Bool) -> [Element] { var result: [Element] = [] for x in self where isIncluded(x) { result.append(x) } return result } } |
Also in the “where” clause, learn more about Optionals. A quick tip: if you’re seeing something like this, stop immediately!
1 2 | bigArray.filter { someCondition }.count > 0 |
“filter” creates a new matching array and accesses each element in the array. But the above is not necessary. With the above code, just check if an element matches – in this case contains (where 🙂 will do it:
1 2 | bigArray.contains { someCondition } |
This is much faster for two reasons: it doesn’t create a new array of filtered values just to count them and it releases as quickly as it finds the first value. In general, only use “filter” if you want all the results.
Usually you want to do the same thing and it can be done with “contain” but it looks ugly. For example, you want to check each value of the sequence that matches the predicate used
1 2 | !sequence.contains { !condition } |
but it may be easier to read by contributing it in a new way with writing the description of the argument name:
1 2 3 4 5 6 7 8 9 10 | extension Sequence { public func all(matching predicate: (Element) -> Bool) -> Bool { // Every element matches a predicate if no element doesn't match it: return !contains { !predicate($0) } } } let evenNums = nums.filter { $0 % 2 == 0 } // [2, 4, 6, 8, 10] evenNums.all { $0 % 2 == 0 } // true |
Reduce
Both “map” and “filter” take an array and create a newly defined array. Occasionally, you want to combine all elements into a new element. For example, the total of elements, you can write as follows:
1 2 3 4 5 | let fbs = [0, 1, 1, 2, 3, 5] var total = 0 for num in fbs { total = total + num } total // 12 |
The sample “reduce” function is divided into two parts: the initialization of the value (in this case, 0), and the method of combining the intermediate value (total) and the element (“num”). Using “reduce”, you can write the above example as below:
1 2 | let sum = fbs.reduce(0) { total, num in total + num } // 12 |
Operator is also a method. so we can rewrite the above example:
1 2 | fbs.reduce(0, +) |
The result of “reduce” is not necessarily the same as the type of the element. For example, if you want to convert an array of integers into letters, with the numbers followed by a space, you can write the following:
1 2 | fbs.reduce("") { str, num in str + "(num), " } // 0, 1, 1, 2, 3, 5, |
This is the part used for “reduce”
1 2 3 4 5 6 7 8 9 10 11 12 | extension Array { func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) -> Result) -> Result { var result = initialResult for x in self { result = nextPartialResult(result, x) } return result } } |
Another important tip: “reduce” is very flexible, and it’s popular for building arrays and performing other operators. For example, you can implement “map” and “filter” using each “reduce”:
1 2 3 4 5 6 7 8 9 10 11 12 13 | extension Array { func map2<T>(_ transform: (Element) -> T) -> [T] { return reduce([]) { $0 + [transform($1)] } } func flter2(_ isIncluded: (Element) -> Bool) -> [Element] { return reduce([]){ isIncluded($1) ? $0 + [$1] : $0 } } } |
Come here, stop for a moment, and ponder the content as well as the code above. Thanks for reading