翻译说明:

原标题: Kotlin : Slow List and Lazy Sequence

原文地址: https://medium.com/@elye.project/kotlin-slow-list-and-lazy-sequence-61691fc974c5

原文作者: Elye

自从Kotlin可以兼容Java7上后,很高兴的是我们可以轻松地在List上使用各种集合操作符并且可以链式调用它们。但是我们知道在某些情况下使用List的迭代器并不是最好的方式,那么还有另一种方式就是使用序列(sequence)

没有背景只能辛苦工作的List列表

在我们了解序列在某些情况下为什么更好之前,让我告诉你一些关于List的内容。

List内部使用Iterator进行操作。这是一个非常勤奋的群体,我链式调用它的每一个操作,它都能确保没有任何遗漏的完成。

val list = listOf(1, 2, 3, 4, 5, 6)
list.map{ it * 2 }.filter { it % 3 == 0 }.average()

正如你在上面的插图中看到的,对于每一步操作,List的每个元素都需要被处理。

为了证明这一点,让我们输出一些log日志:

val list = listOf(1, 2, 3, 4, 5, 6)
val result = list
.map{ println("In Map"); it * 2 }
.filter { println("In Filter");it % 3 == 0 }
println("Before Average")
println(result.average())

结果如下:

In Map
In Map
In Map
In Map
In Map
In Map
In Filter
In Filter
In Filter
In Filter
In Filter
In Filter
Before Average
9.0

很棒。勤奋努力地工作,并完成所有的过程。

懒惰的家伙,Sequence序列…

好的,现在让我们通过调用asSequence()扩展函数来将List转化成一个序列(Sequence)。

val list = listOf(1, 2, 3, 4, 5, 6)
val result = list.asSequence()
.map{ println("In Map"); it * 2 }
.filter { println("In Filter");it % 3 == 0 }
println("Before Average")
println(result.average())

结果如下:

Before Average
In Map
In Filter
In Map
In Filter
In Map
In Filter
In Map
In Filter
In Map
In Filter
In Map
In Filter
9.0

哇,有趣…,注意到 “Before Average” 是最先输出的,换句话说,如果我不调用 average() 函数,那么序列(sequence)就没有做任何操作。

它很懒,不想做任何工作,直到终端连接到它。终端就像是一种操作,实际上就是一个操作符扩展函数,会返回其他类型结果(除了Sequence\

那么,序列Sequence到底有什么好处呢?

如果你这样想,想象你想要拿到集合变换后的第一个元素。

让我们看下List处理方式:

val list = listOf(1, 2, 3, 4, 5, 6)
val result = list
.map{ println("In Map $it"); it * 2 }
.filter { println("In Filter $it");it % 3 == 0 }
println(result.first())
  • 1
  • 2
  • 3
  • 4
  • 5

结果如下:

In Map 1
In Map 2
In Map 3
In Map 4
In Map 5
In Map 6
In Filter 2
In Filter 4
In Filter 6
In Filter 8
In Filter 10
In Filter 12
6

所有在一起总共13行,这意味着13次操作。

让我们看下Sequence处理方式:

val sequence = sequenceOf(1, 2, 3, 4, 5, 6)
val result = sequence
.map{ println("In Map $it"); it * 2 }
.filter { println("In Filter $it");it % 3 == 0 }
println(result.first())

结果是:

In Filter 2
In Map 2
In Filter 4
In Map 3
In Filter 6
6

仅仅7行即7次操作。这意味着它只要找到第一个元素的那一刻,就会终止整个过程。

你可以想像,这会加快整个运行的过程。

加速仅仅只适用于first()操作吗?

让我们做一些试验。

试验Map操作

val sequence = generateSequence(1) { it + 1 }.take(50000000)
val list = sequence.toList() println("List Map Sum= "
+ measureNanoTime { list.map { it * 2 }.sum() })
println("Sequence Map Sum "
+ measureNanoTime { sequence.map { it * 2 }.sum() }) println("List Map Average "
+ measureNanoTime { list.map { it * 2 }.average() })
println("Sequence Map Average "
+ measureNanoTime { sequence.map { it * 2 }.average() })

结果是:

List Map Sum 14727907362
Sequence Map Sum 2074397969
List Map Average 11460520785
Sequence Map Average 3268960487
  • List: 在Map:Sum操作上花费了14.7s,在Map:Average操作上花费了11.5s
  • Sequence: 在Map:Sum操作上花费了2.1s, 在Map:Average操作上花费了3.3s

看上去像前面的有一个Map操作时,Sequence的性能会比List更快。也许它不需要像List那样存储map操作后的中间结果,从而会更快。

试验Filter操作

val sequence = generateSequence(1) { it + 1 }.take(50000000)
val list = sequence.toList() println("List Filter Sum "
+ measureNanoTime { list.filter { it % 3 == 0 }.sum() })
println("Sequence Filter Sum "
+ measureNanoTime { sequence.filter { it % 3 == 0 }.sum() }) println("List Filter Average "
+ measureNanoTime { list.filter { it % 3 == 0 }.average() })
println("Sequence Filter Average "
+ measureNanoTime { sequence.filter { it % 3 == 0 }.average() })

结果是:

List Filter Sum 506351694
Sequence Filter Sum 873175271
List Filter Average 391790033
Sequence Filter Average 838510968
  • List: 在Filter:Sum操作上花费了0.5s,在Filter:Average操作上花费了0.4s
  • Sequence: 在Filter:Sum操作上花费了0.9s, 在Filter:Average操作上花费了0.8s

对于前面的Filter操作,Sequence比List更慢。 深入了解函数,看起来像Sequence的Filter操作需要有更多的开销来检查某些状态,而List的Filter则是一个简单的检查并收集新的元素。

试验Map和Filter操作

val sequence = generateSequence(1) { it + 1 }.take(50000000)
val list = sequence.toList() println("List Map Filter Sum\t\t " + measureNanoTime {
list.map { it * 2 }.filter { it % 3 == 0 }.sum() })
println("Sequence Map Filter Sum\t " + measureNanoTime {
sequence.map { it * 2 }.filter { it % 3 == 0 }.sum() }) println("List Map Filter Average\t\t " + measureNanoTime {
list.map { it * 2 }.filter { it % 3 == 0 }.average() })
println("Sequence Map Filter Average\t " + measureNanoTime {
sequence.map { it * 2 }.filter { it % 3 == 0 }.average() })

结果是:

List Map Filter Sum 34845242323
Sequence Map Filter Sum 2820436086
List Map Filter Average 2328258876
Sequence Map Filter Average 18618444560
  • List: 在Map:Filter:Sum操作上花费了34.8s,在Map:Filter:Average操作上花费了2.3s
  • Sequence: 在Map:Filter:Sum操作上花费了2.8s, 在Map:Filter:Average操作上花费了18.6s

一个相对令人惊讶的结果,如Map:Filter:Sum,Sequence比List快得多,而Map:Filter:Average,List比Sequence要快得多。

试验直接使用Sequence和List

val sequence = generateSequence(1) { it + 1 }.take(50000000)
val list = sequence.toList() println("List Sum " + measureNanoTime { list.sum() })
println("Sequence Sum " + measureNanoTime { sequence.sum() }) println("List Average " + measureNanoTime { list.average() })
println("Sequence Average " + measureNanoTime { sequence.average() })

结果是:

List Sum 91726022
Sequence Sum 592771887
List Average 101141460
Sequence Average 622616340
  • List: 在Sum操作上花费了0.1s,在Average操作上花费了0.1s
  • Sequence: 在Sum操作上花费了0.5s, 在Average操作上花费了0.6s

没有任何中间操作,明显列表List比序列Sequence要快。

总结:

  • 1、当不需要中间操作时,使用List
  • 2、当仅仅只有map操作时,使用sequence
  • 3、当仅仅只有filter操作时,使用List
  • 4、如果末端操作是first时,使用sequence
  • 5、对于没有提及的其他操作符或者其他操作符的组合,请尝试使用例子去验证一下

译者有话说:

首先,说下为什么要翻译这篇博客?关于Kotlin中的Sequence和List的使用以及源码解析相关的文章我已经写过两篇了,这篇博客主要吸引我的一点就是以更多运行的例子试验和相关幽默的配图更加形象地描述了Sequence,List的区别以及各自的使用场景。

然而,这篇博客并没有深入源码去讲解Sequence的实现,这篇之前写的博客 浅谈Kotlin中的序列(Sequences)源码完全解析(十) 从源码角度带你一步步分析Sequence序列背后的原理,关于如何正确使用Sequence和List以及各自使用场景,之前翻译的一篇博客 [译]Kotlin中是应该使用序列(Sequences)还是集合(Lists)? 会有更加全面的介绍。

最后,有了这三篇文章应该更加全面理解了Sequence的原理和使用。

Sequence 加速的更多相关文章

  1. 【 CodeForces - 392C】 Yet Another Number Sequence (二项式展开+矩阵加速)

    Yet Another Number Sequence Description Everyone knows what the Fibonacci sequence is. This sequence ...

  2. POJ 2778 DNA Sequence(AC自动机+矩阵加速)

    DNA Sequence Time Limit: 1000MS   Memory Limit: 65536K Total Submissions: 9899   Accepted: 3717 Desc ...

  3. HDU 5950 - Recursive sequence - [矩阵快速幂加速递推][2016ACM/ICPC亚洲区沈阳站 Problem C]

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5950 Farmer John likes to play mathematics games with ...

  4. HDU5950 Recursive sequence (矩阵快速幂加速递推) (2016ACM/ICPC亚洲赛区沈阳站 Problem C)

    题目链接:传送门 题目: Recursive sequence Time Limit: / MS (Java/Others) Memory Limit: / K (Java/Others) Total ...

  5. jzoj6008. 【THUWC2019模拟2019.1.18】Sequence (矩阵加速)

    题面 茉优最近研究发现,一个人的想愿能力可以认为是字符串S的一个子串S[l,r],而连接值可以认为是这个子串的本质不同子序列个数.现在她想验证她的结论是否正确,于是她给了你Q个询问,希望你帮她来计算, ...

  6. 【距离GDOI:128天】【POJ2778】DNA Sequence(AC自动机+矩阵加速)

    已经128天了?怎么觉得上次倒计时150天的日子还很近啊 ....好吧为了把AC自动机搞透我也是蛮拼的..把1030和这道题对比了无数遍...最终结论是...无视时间复杂度,1030可以用这种写法解. ...

  7. cocos2d-x 动画加速与减速

    转自:http://novacreo.com/%E7%A8%8B%E5%BA%8F%E7%BB%84/cocos2d-x%E5%8A%A8%E7%94%BB%E5%8A%A0%E9%80%9F%E4% ...

  8. hdu_5950_Recursive sequence(矩阵快速幂)

    题目链接:hdu_5950_Recursive sequence 题意:递推求解:F(n) = 2*F(n-2) + F(n-1) + n4 和F(1) = a,F(2) = b: 题解: 一看数据范 ...

  9. 【论文阅读】Sequence to Sequence Learning with Neural Network

    Sequence to Sequence Learning with NN <基于神经网络的序列到序列学习>原文google scholar下载. @author: Ilya Sutske ...

随机推荐

  1. nvm-windows安装

    linux上的nvm太好用了,windows也出了,不过需要手动下载安装 地址: https://github.com/coreybutler/nvm-windows/releases 博主安装的是 ...

  2. gt,gte,lt,lte缩写的含义

    gt: greater than 大于 gte: greater than or equal 大于等于 lt: less than 小于 lte: less than or equal 小于等于

  3. 20165205 2017-2018-2 《Java程序设计》实验二 Java面向对象程序设计

    20165205 2016-2017-2 <Java程序设计>实验二 Java面向对象程序设计 实验内容 初步掌握单元测试和TDD 理解并掌握面向对象三要素:封装.继承.多态 初步掌握UM ...

  4. 《GPU高性能编程CUDA实战》第六章 常量内存

    ▶ 本章介绍了常量内存的使用,并给光线追踪的一个例子.介绍了结构cudaEvent_t及其在计时方面的使用. ● 章节代码,大意是有SPHERES个球分布在原点附近,其球心坐标在每个坐标轴方向上分量绝 ...

  5. Redis 主从复制, 读写分离

    1: 是什么? 2: 经常是配置从库, 不配置主库 3.1: 每次与 master 断开之后都要从连, 除非你配置了redis.conf 3.2: 获取当前redis 服务信息 => info ...

  6. compute by 的使用

    GROUP BY子句有个缺点,就是返回的结果集中只有合计数据,而没有原始的详细记录.如果想在SQL SERVER中完成这项工作,可以使用COMPUTE BY子句.COMPTE生成合计作为附加的汇总列出 ...

  7. flash builder的配色方案

    写代码的时候看着代码颜色不是特别好,于是研究了一下flash builder的配色方案. flash builder由eclipse开发,采用和eclipse相同的配置方法,这个网站上有很多配色模板: ...

  8. 使用linux的shell脚本实现在当前行重复动态显示时间等字符串信息(不另起新行)

    ###本脚本在Suse11sp2当中验证正确 #!/bin/sh )) do echo -ne "\r$(date)" sleep 0.3 done ###关键在 echo 的 & ...

  9. APP-9-文字识别-车牌识别

    1.获取Access Token APP-9.1-百度应用-文字识别 2.代码部分 <!DOCTYPE html> <html> <head> <meta c ...

  10. ACM__并查集

    并查集是树型的数据结构,处理不想交集合 主要解决查找和合并的问题 步骤: 初始化 把每个点所在的集合初始化为自身 复杂度为O(N) 查找 查找元素所在的集合,即根节点 合并 将两个元素所在的集合合并在 ...