Original link: https://xuyisheng.top/kotlin8/
Kotlin has added a lot of fast high-order function operations to the set operation class. Various operators make many developers confused, especially looking at some Kotlin source code or coroutine source code, all kinds of dazzling operators , so that the code is completely unreadable. Therefore, this article will explain the collection high-order functions in Kotlin to reduce the difficulty of reading the source code. Let’s look at a few high-order functions that are used more frequently.
The first is sumOf, as a very convenient sum function, it can quickly sum some parameters in the collection, the code is as follows.
val list = mutableListOf(1, 2, 3, 4) val sumOf = list.sumOf { it }
Let’s take a look at its source code.
public inline fun <T> Iterable<T>.sumOf(selector: (T) -> Int): Int { var sum: Int = 0.toInt() for (element in this) { sum += selector(element) } return sum }
In fact, it is the accumulation of elements inside. There are many higher-order functions like this in Kotlin, which is one of the reasons why it is more convenient to develop many basic functions in Kotlin.
But sumOf has a limitation, that is, it can only be summed. After all, it is designed to be used for summation, so for more general scenarios, we can further abstract this operation, which is reduce.
For example, we now want to implement a multiplication function, and the code is as follows.
val list = mutableListOf(1, 2, 3, 4) val result = list.reduce { acc, i -> acc * i }
There are two operation parameters for reduce, the current accumulated value and the next element in the collection. The execution logic of reduce is to first take out the first element of the set as acc, and execute the logic in the block with the second element – i, return the value as acc, and continue the above steps.
If the collection is empty, an exception will be thrown.
However, reduce also has a limitation, that is, it uses the first element of the collection as the starting acc by default, so it can only return the generic type of the previous collection. If it is the following structure, it cannot be used.
data class Test(val num: Int, val name: String) val list = mutableListOf( Test(1, "x"), Test(2, "y"), Test(3, "z"), Test(4, "j") ) val result = list.reduce { acc, i -> acc.num * i.num // Error }
The problem is that the type of acc cannot be specified and can only be obtained from the collection. Therefore, Kotlin also provides a more general higher-order function – fold. The code is as follows.
data class Test(val num: Int, val name: String) val list = mutableListOf( Test(1, "x"), Test(2, "y"), Test(3, "z"), Test(4, "j") ) val result = list.fold(1) { acc, test -> acc * test.num }
fold is very similar to reduce, except that fold adds an initial parameter. Through this parameter, the initial value of acc can be set, and the return type can also be specified. In this way, unlike reduce, it needs to be consistent with the collection type. .
Since the initial value is specified by the initial parameter, no exception will result even if the collection is empty.
It can be seen that in Kotlin, reduce is actually an imperfect higher-order function. Most of the time, it should be avoided, and flod should be used instead, and it should be noted that in other languages, such as In JavaScript, its reduce function actually has the same logic as Kotlin’s fold function, not the implementation of reduce in Kotlin.
So what are the usage scenarios for fold? The above mentioned traversal of the collection, and then summing, multiplying, and splicing strings of certain items is a very common example.
Like most set higher-order functions, fold also provides extensions such as foldRight, foldIndexed, and foldRightIndexed, which can be used to obtain indexes or change the direction of traversal.
Fold and reduce are actually a reduction operation on a set, and finally return a value after the “reduction”, which is equivalent to the operation of extracting and reducing the collection.
In addition to the specification of collections, Kotlin has also made many improvements to the traversal of collections.
For example, we can use filter to filter the elements in the collection that meet certain rules, the code is as follows.
val result = list.filter { it.num > 2 }
Another example is sorting a collection. Although it can be done before, it is definitely not as clear as a higher-order function. Let’s take a look at the following code.
val sorted = lists.sorted() val sortedDescending = lists.sortedDescending() val sortedBy = lists.sortedBy { it.length } val sortedByDescending = lists.sortedByDescending { it.length } val sortedWith = lists.sortedWith( compareBy<String> { it.length }.thenBy { it } )
These are common sorting methods, which can basically cover most of our usage scenarios.
In addition to sorting, we can also check the collection to determine whether there are elements in the collection that meet the conditions, such as the following code.
val any = lists.any { it.length == 6 } val all = lists.all { it.length == 6 } val none = lists.none { it.length == 6 } val count = lists.count { it.length == 6 }
Similar higher-order functions such as Search and take will not be discussed, and they can basically be taken for granted.
Finally, let’s take a look at the Transform in the collection.
The simplest, we can use the map function to transform a collection, such as the following code.
val result = list.map { it.num }
This forms a new set of num.
Map is relatively easy to understand, it implements one-to-one conversion, but the other – flatMap is not so easy to understand.
So let’s first look at another operator – flatten.
Suppose we have such a nested List as shown below.
val list = listOf(listOf("abc", "xyz", "hjk"), listOf("123", "789"), listOf("+-"))
I need to flatten this two-dimensional List into a one-dimensional List, then it can be achieved by flatten, the code is as follows.
val result = list.flatten() // out [abc, xyz, hjk, 123, 789, +-]
So what if I do some processing on the data after leveling the List? Conveniently, we can chain other higher-order functions, such as map, as shown below.
val result = list.flatten().map { it.first() } // out [a, x, h, 1, 7, +]
Such operations are actually very common, so Kotlin provides a composite higher-order function – flatMap, we use flatMap to achieve the same function.
val result = list.flatMap { it.map { item -> item.first() } }
It actually uses flatten to flatten the data first, and then map each item. So, if you just need to flatten the data, then flatten is enough, if you need to do some processing on the data, then you need to use flatMap.
A very common scenario of flatMap is to generate the cross-product data of two Lists. Let’s look at the following example.
val SUITS = setOf("♣" /* clubs*/, "♦" /* diamonds*/, "♥" /* hearts*/, "♠" /*spades*/) val VALUES = setOf("2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A")
There are two lists above, which are the suits and numbers in the playing cards, so how do we generate the entire deck of playing cards through these two lists? With the help of flatMap, it can be easily implemented. The code is as follows.
val DECK = SUITS.flatMap { suit -> VALUES.map { value -> Card(suit, value) } }
This example is not the same as the above example. It can be said to be a reciprocal process. Earlier, we used a nested List and then flattened the data. In this example, two lists are cross-multiplied to generate a new List.
To sum up, we summarize the workflow of flatMap. First, flatMap traverses the elements in the collection, then passes each element into the block, returns a list after processing the block, and finally processes each element to generate a list. Tiling is performed to generate a flattened list, which is the complete execution flow of flatMap.
It can be seen that in most scenarios, we don’t even need to use the traversal function of the collection. Through these auxiliary higher-order functions, we can easily operate the collection, which is why Kotlin code is easier to develop than Java. , Of course, the functional programming method of Kotlin will be more difficult to get started than Java.
So when we use Kotlin’s higher-order functions to process collections, do we need to worry about some hidden performance overhead?
First of all, Kotlin’s default collection class higher-order functions are all inline functions, so they will be replaced at compile time, so the block of higher-order functions will not generate new inner classes, resulting in code bloat. However, because higher-order functions each time When processing a collection, a new collection will be generated, so it will indeed increase the memory, but for the mobile terminal, in the scenario with a small amount of data, this impact is minimal, so there is no need to worry about the performance overhead at all. Use it with confidence.
This article is reprinted from: https://xuyisheng.top/kotlin8/
This site is for inclusion only, and the copyright belongs to the original author.