kotlin函数式编程_我最喜欢的Kotlin函数式编程示例

本文介绍了Kotlin中函数式编程的特点,包括收集处理、电源组和快速排序的示例。作者展示了如何利用Kotlin进行高效且可读性强的代码实现,如找到符合特定条件的优秀学生、生成幂集和实现快速排序算法。尽管快速排序函数的性能相对较慢,但它以简洁和易读性为优点。
摘要由CSDN通过智能技术生成

kotlin函数式编程

by Marcin Moskala

通过Marcin Moskala

One of the great things about Kotlin is that it supports functional programming. Let’s see and discuss some simple but expressive functions written in Kotlin.

Kotlin的一大优点是它支持函数式编程。 让我们看一下并讨论一下用Kotlin编写的一些简单但富有表现力的函数。

收集处理 (Collection processing)

Kotlin has some of the best support for collection processing. It is expressive and supports a lot of functions. To see an example, let’s say that we make a system for a University. We need to find the best students that deserve a scholarship. We have following Student model:

Kotlin为收集处理提供了一些最好的支持。 它具有表现力,并支持许多功能。 来看一个例子,假设我们为大学建立了一个系统。 我们需要找到值得奖学金的最好的学生。 我们有以下Student模型:

class Student(
    val name: String,
    val surname: String,
    val passing: Boolean,
    val averageGrade: Double
)

Now we can make the following processing to get a list of the best 10 students that match all criteria:

现在,我们可以进行以下处理以获取符合所有条件的10名最佳学生的列表:

students.filter { it.passing && it.averageGrade > 4.0 } // 1
    .sortedBy { it.averageGrade } // 2
    .take(10) // 3
    .sortedWith(compareBy({ it.surname }, { it.name })) // 4
  1. We get only students who are passing and with a grade point average of greater than 4.0.

    我们只会招收及格且平均分数高于4.0的学生。
  2. We sort by the average grade.

    我们按平均成绩排序。
  3. We take first 10 students.

    我们招收了前10名学生。
  4. We sort students alphanumerically. The comparator compares surnames first, and if equal then it compares names.

    我们按字母数字排序学生。 比较器首先比较姓氏,如果相等,则比较名字。

What if, instead of alphanumerical order, we need to keep students in the same order as they were before? What we can do is preserve the order using indexes:

如果我们需要让学生保持以前的顺序,而不是字母数字顺序,该怎么办? 我们可以做的是使用索引保留顺序:

students.filter { it.passing && it.averageGrade > 4.0 }
    .withIndex() // 1
    .sortedBy { (i, s) -> s.averageGrade } // 2
    .take(10)
    .sortedBy { (i, s) -> i } // 3
    .map { (i, s) -> s } // 4
  1. We add current index to every element.

    我们将当前索引添加到每个元素。
  2. We need to destructure value and index before use.

    我们需要在使用前对值和索引进行解构

  3. We sort by index.

    我们按索引排序。
  4. We remove index and keep only students.

    我们删除索引,只保留学生。

This shows how simple and intuitive collection processing in Kotlin is.

这表明Kotlin中的收集过程非常简单直观。

电源组 (Powerset)

If you had algebra at your University, then you might remember what a powerset is. For any set, its powerset is the set of all its subsets including this set and the empty set. For instance, if we have the following set:

如果您在大学学习过代数,那么您可能会记得什么是幂集。 对于任何集合,其幂集是其所有子集的集合,包括该集合和空集合。 例如,如果我们有以下设置:

{1,2,3}

{1,2,3}

Its powerset is the following:

其功率集如下:

{{}, {1}, {2}, {3}, {1,2}, {1,3}, {2,3}, {1,2,3}}

{{}, {1}, {2}, {3}, {1,2}, {1,3}, {2,3}, {1,2,3}}

Such a function is very useful in algebra. How can we implement it?

这样的函数在代数中非常有用。 我们如何实施呢?

If you want to challenge yourself, then stop right now and try to solve it yourself first.

如果您想挑战自己,请立即停止并尝试自己解决问题。

Let’s start our analysis from simple observation. If we take any element of the set (like 1), then the powerset will include an equal number of sets with these elements ({1}, {1,2}, {1,3}, {1,2,3}), and without these ({}, {2}, {3}, {2,3}).

让我们从简单的观察开始分析。 如果我们采用集合的任何元素(如1),则幂集将包含与这些元素相等的集合({1}, {1,2}, {1,3}, {1,2,3}) ,而没有这些({}, {2}, {3}, {2,3})

Note that the second is a powerset({2,3}), and the first is a powerset({2,3}) with 1 added to every set. So we can calculate the powerset by taking the first element, calculating the powerset for all others, and returning the sum of the result and the result with the first element added to every set:

请注意,第二个是powerset({2,3}) ,第一个是powerset({2,3}) ,每个集合中都添加了1。 因此,我们可以通过采用第一个元素,计算所有其他元素的幂集,然后返回结果与将第一个元素添加到每个集合中的结果的和,来计算幂集:

fun <T> powerset(set: Set<T>): Set<Set<T>> {
   val first = set.first()
   val powersetOfRest = powerset(set.drop(1))
   return powersetOfRest.map { it + first } + powersetOfRest
}

The above declaration will not work correctly. The problem is with the empty set: first will throw an error when the set is empty. Here, the definition comes with a solution: powerset({}) = {{}}. When we fix it, we will have our algorithm ready:

上面的声明将无法正常工作。 问题出在空集合上:当​​集合为空时, first将引发错误。 在这里,定义附带一个解决方案:powerset({})= {{}}。 修复后,我们将准备好算法:

fun <T> powerset(set: Set<T>): Set<Set<T>> =
    if (set.isEmpty()) setOf(emptySet())
    else {
       val powersetOfRest = powerset(set.drop(1))
       powersetOfRest + powersetOfRest.map { it + set.first() }
    }

Let’s see how it works. Let’s say we need to calculate the powerset({1,2,3}). The algorithm will count it this way:

让我们看看它是如何工作的。 假设我们需要计算powerset({1,2,3}) 。 该算法将以这种方式对其进行计数:

powerset({1,2,3}) = powerset({2,3}) + powerset({2,3}).map { it + 1 }

powerset({1,2,3}) = powerset({2,3}) + powerset({2,3}).map { it + 1 }

powerset({2,3}) = powerset({3}) + powerset({3}).map { it + 2}

powerset({2,3}) = powerset({3}) + powerset({3}).map { it + 2}

powerset({3}) = powerset({}) + powerset({}).map { it + 3}

powerset({3}) = powerset({}) + powerset({}).map { it + 3}

powerset({}) = {{}}

powerset({}) = {{}}

powerset({3}) = {{}, {3}}

powerset({3}) = {{}, {3}}

powerset({2,3}) = {{}, {3}} + {{2}, {2, 3}} = {{}, {2}, {3}, {2, 3}}

powerset({2,3}) = {{}, {3}} + {{2}, {2, 3}} = {{}, {2}, {3}, {2, 3}}

powerset({1,2,3}) = {{}, {2}, {3}, {2, 3}} + {{1}, {1, 2}, {1, 3}, {1, 2, 3}} = {{}, {1}, {2}, {3}, {1,2}, {1,3}, {2,3}, {1,2,3}}

powerset({1,2,3}) = {{}, {2}, {3}, {2, 3}} + {{1}, {1, 2}, {1, 3}, {1, 2, 3}} = {{}, {1}, {2}, {3}, {1,2}, {1,3}, {2,3}, {1,2,3}}

The above function can be improved. We can use the let function to make the notation shorter and more compact:

可以改善上述功能。 我们可以使用let函数使符号更短,更紧凑:

fun <T> powerset(set: Set<T>): Set<Set<T>> =
    if (set.isEmpty()) setOf(emptySet())
    else powerset(set.drop(1))
           .let { it+ it.map { it + set.first() }

We can also define this function as an extension function to Collection so we can use this function as if it is the method of Set (setOf(1,2,3).powerset() instead of powerset(setOf(1,2,3))):

我们还可以将此函数定义为Collection的扩展函数,因此可以像使用Set ( setOf(1,2,3).powerset()而不是powerset(setOf(1,2,3)) ):

fun <T> Collection<T>.powerset(): Set<Set<T>> =
    if (isEmpty()) setOf(emptySet())
    else drop(1)
           .powerset()
           .let { it+ it.map { it + first() }

One big improvement is to make the powerset tail recursive. In the above implementation, the state of powerset is growing with every iteration (recurrent call), because the state of the previous iteration needs to be kept in the memory.

一项重大改进是使powerset尾递归。 在上面的实现中, powerset的状态随着每次迭代(循环调用)而增长,因为前一个迭代的状态需要保留在内存中。

Instead, we could use an imperative loop or the tailrec modifier. We will use the second option to maintain the readability of the function. The tailrec modifier allows only a single recursive call in the last statement. This is how we can change our function to use it effectively:

相反,我们可以使用命令式循环或tailrec修饰符。 我们将使用第二个选项来保持功能的可读性。 tailrec修饰符仅允许在最后一条语句中进行单个递归调用。 这是我们可以更改功能以有效使用它的方法:

fun <T> Collection<T>.powerset(): Set<Set<T>> = 
    powerset(this, setOf(emptySet()))

private tailrec fun <T> powerset(left: Collection<T>, acc: Set<Set<T>>): Set<Set<T>> =
    if (left.isEmpty()) acc
    else powerset(left.drop(1), acc + acc.map { it + left.first() })

The above implementation is part of the KotlinDiscreteMathToolkit library, which defines a lot of other functions used in discrete math.

上面的实现是KotlinDiscreteMathToolkit库的一部分,该库定义了离散数学中使用的许多其他函数。

快速排序 (Quicksort)

Time for my favorite example. We’ll see how a difficult problem can be simplified and made highly readable using a functional programming style and tools.

现在是我最喜欢的例子。 我们将看到如何使用功能性编程风格和工具简化难题并使其具有更高的可读性。

We will implement the Quicksort algorithm. The algorithm is simple: we choose some element (pivot) and we distribute all other elements to the list with bigger and smaller elements than the pivot. Then we recursively sort these sub-arrays. Finally, we add the sorted list of smaller elements, the pivot, and the sorted list of bigger elements. For simplification, we will take the first element as a pivot. Here is the full implementation:

我们将实现Quicksort算法。 该算法很简单:我们选择一些元素(数据透视),然后将所有其他元素分布到列表中,其中元素的大小大于数据透视。 然后,我们对这些子数组进行递归排序。 最后,我们添加较小元素的排序列表,数据透视表和较大元素的排序列表。 为了简化,我们将第一个元素作为枢轴。 这是完整的实现:

fun <T : Comparable<T>> List<T>.quickSort(): List<T> = 
    if(size < 2) this
    else {
        val pivot = first()
        val (smaller, greater) = drop(1).partition { it <= pivot}
        smaller.quickSort() + pivot + greater.quickSort()
    }
// Usage
listOf(2,5,1).quickSort() // [1,2,5]

Looks great, doesn’t it? This is the beauty of functional programming.

看起来不错,不是吗? 这就是函数式编程的美。

The first concern of such a function is its execution time. It is not optimized for performance at all. Instead, it is short and highly readable.

这种功能首先要考虑的是它的执行时间。 根本没有针对性能进行优化。 相反,它很简短并且可读性强。

If you need a highly optimized function, then you can use one from the Java standard library. It is based on different algorithms depending on some conditions, and it has actual implementations written naively. It should be much more efficient. But how much exactly? Let’s compare these two functions. Let’s sort a few different arrays with random elements and compare execution times. Here is the code I’ve used for this purpose:

如果需要高度优化的功能,则可以使用Java标准库中的一种。 它基于某些条件基于不同的算法,并且天真的编写了实际的实现。 它应该更加有效。 但是多少呢? 让我们比较这两个函数。 让我们用随机元素对几个不同的数组进行排序,并比较执行时间。 这是我用于此目的的代码:

val r = Random()
listOf(100_000, 1_000_000, 10_000_000)
    .asSequence()
    .map { (1..it).map { r.nextInt(1000000000) } }
    .forEach { list: List<Int> ->
        println("Java stdlib sorting of ${list.size} elements took ${measureTimeMillis { list.sorted() }}")
        println("quickSort sorting of ${list.size} elements took ${measureTimeMillis { list.quickSort() }}")
    }

On my machine I got the following result:

在我的机器上,我得到以下结果:

Java stdlib sorting of 100000 elements took 83quickSort sorting of 100000 elements took 163Java stdlib sorting of 1000000 elements took 558quickSort sorting of 1000000 elements took 859Java stdlib sorting of 10000000 elements took 6182quickSort sorting of 10000000 elements took 12133`

Java stdlib排序100000个元素花费83quickSort排序100000个元素花费163Java stdlib排序1000000个元素花费558quickSort排序1000000个元素花费859Java stdlib排序10000000个元素花费6181quickSort排序10000000个元素花费12133`

As we can see, the quickSort function is generally 2 times slower. Even for huge lists. It has the same scalability. In normal cases, the difference will generally be between 0.1ms vs 0.2ms. Note that it is much simpler and more readable. This explains why in some cases we can use a function that’s a bit less optimized, but readable and simple.

如我们所见, quickSort 功能通常要慢2倍。 即使是巨大的清单。 它具有相同的可伸缩性。 在正常情况下,差异通常在0.1ms与0.2ms之间。 请注意,它更加简单易读。 这就解释了为什么在某些情况下我们可以使用优化程度略低但可读性和简单性强的函数。

If you are interested in Kotlin, check out Kotlin Academy. It is great publication and community dedicated for Kotlin.

如果您对Kotlin感兴趣,请访问Kotlin Academy 。 这是Kotlin的重要出版物和社区。

I am also publishing great resources on my Twitter. To mention me there use @marcinmoskala. If you can use my help, remember that I am open for consultations.

我还在Twitter上发布了大量资源。 要在这里提及我,请使用@marcinmoskala 。 如果可以使用我的帮助,请记住我愿意接受咨询

翻译自: https://www.freecodecamp.org/news/my-favorite-examples-of-functional-programming-in-kotlin-e69217b39112/

kotlin函数式编程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值