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()) accelse 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) thiselse {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函数式编程

kotlin函数式编程_我最喜欢的Kotlin函数式编程示例相关推荐

  1. 硬件趣学python编程_没有人比我更懂编程,慧编程'吮指编辑器',简单快乐学python...

    咳咳! 大家好,我是偶尔写文章的康康老师. 今天跟大家介绍的是慧编程家的,睡在Scratch上铺的兄弟--慧编程Python编辑器. 这是一款集才华和颜值为一体的'吮指'编辑器! 忘记肯德基,你的手指 ...

  2. 旅行 写作 编程_我最喜欢的在旅行或没有互联网时保持编程的方式

    旅行 写作 编程 This is a short guide on sharpening your skills and keeping productive when in transit. And ...

  3. 交换最大数与最小数java编程_善知教育笔记之JavaSE_Java编程基础

    1 Java编程基础 1.1 变量 1.1.1 变量 变量是什么?为什么为用变量? 变量就是系统为程序分配的一块内存单元,用来存储各种类型的数据.根据所存储的数据类型的不同,有各种不同类型的变量.变量 ...

  4. 结对编程_导航之前,即使不结对编程,也要学会驾驶

    结对编程 有一天,我18岁那年,我和另外29个男孩坐在一个小小的演讲厅里. 这是我们在墨尔本大学获得计算机科学学位的第一天早晨. 一个男人,节,有点ha,走进来,站在讲台上,扫视着我们的脸,叹了口气, ...

  5. java io 网络编程_[笔面] Java IO和网络编程相关面试

    1.网络编程时的同步.异步.阻塞.非阻塞? 同步:函数调用在没得到结果之前,没有调用结果,不返回任何结果. 异步:函数调用在没得到结果之前,没有调用结果,返回状态信息. 阻塞:函数调用在没得到结果之前 ...

  6. python socket编程_最基础的Python的socket编程入门教程

    本文介绍使用Python进行Socket网络编程,假设读者已经具备了基本的网络编程知识和Python的基本语法知识,本文中的代码如果没有说明则都是运行在Python 3.4下. Python的sock ...

  7. java和python混合编程_浅谈C++与Java混合编程

    在学习编程的过程中, 我觉得不止要获得课本的知识, 更多的是通过学习技术知识提高解决问题的能力, 这样我们才能走在最前方, 更 多 Java 学习,请登陆疯狂 java 官网. 现实的情况是, 真实的 ...

  8. python模块化编程_什么是模块,Python模块化编程(入门必读)

    Python 提供了强大的模块支持,主要体现在,不仅 Python 标准库中包含了大量的模块(称为标准模块),还有大量的第三方模块,开发者自己也可以开发自定义模块.通过这些强大的模块可以极大地提高开发 ...

  9. powershell编程_对Power BI PowerShell Commandlet的编程访问

    powershell编程 In this article, I am going to demonstrate how to access the Power BI PowerShell comman ...

最新文章

  1. linux下使用sort命令升序、降序、随机及组合方式排序方法
  2. 修改手机屏幕刷新率_到底高刷新率屏幕为智能手机带来什么?
  3. BZOJ2588 Count on a tree 【树上主席树】
  4. DefaultHashOperations multiget的一个坑
  5. Windows 技巧篇 - cmd的复制和粘贴功能
  6. mysql百万数据删除_【MySQL】删除大量数据的具体实现
  7. 深度学习笔记:LSTM
  8. 《DB2性能管理与实战》导读
  9. SAP Customer Experience Extensibility gold rule
  10. python 编译 pyc
  11. 更新字典 (Updating a Dictionary,UVa12504)
  12. python将字符串s和换行符写入文件fp_Python 文件操作
  13. ftp linux登录的命令行,linux登录ftp:lftp命令
  14. Go 编程语言官方文档中文版和官方教程中文版
  15. 出家12年,北大数学天才柳智宇下山还俗:从事心理咨询,主动要求降薪至2万...
  16. 20172305 结对编程项目-四则运算 第二周 阶段总结
  17. 域名 CN 被注册;上世纪最大的 BBS 论坛 | 历史上的今天
  18. 任务栏创建返回桌面快捷图标
  19. 深度学习需要的显卡配置
  20. python word2vec怎么用_小白看Word2Vec的正确打开姿势|全部理解和应用

热门文章

  1. 从新手到Flutter架构师,一篇就够!学习路线+知识点梳理
  2. 被面试官问的Android问题难倒了,成功入职字节跳动
  3. -wl是不是c语言的标识符,C语言基础知识考试
  4. JAVA List集合转Page(分页对象)
  5. AGC 022 B - GCD Sequence
  6. 文件2. 文件重命名
  7. 关于apache和tomcat集群,线程是否占用实验
  8. Java虚拟机内存溢出
  9. C++TCP和UDP属于传输层协议
  10. ROS project part 1: Ubuntu中安装opencv包以及相应的依赖