SlideShare a Scribd company logo
Chapter 8: Efficient collection processing
Effective otlin 讀書會
范聖佑
JetBrains
Developer Advocate
2022/12/08 導讀
第八章:有效率的 Collection 操作
—
• Collection 幾乎無所不在
• Collection 操作是 Functional Programming 裡最重要的功能
• Collection 操作最佳化在⼤型且在意效能的系統裡特別重要
• 好消息!Collection 操作最佳化並不難,只需掌握幾個原則即可!
討論主題
—
• 主題 51:傾向使⽤ Sequences 來取代巨量且有多次操作⾏為的 Collection
• 主題 52:考慮使⽤ Map 來儲存關聯的元素
• 主題 53:考慮使⽤ groupingBy 來取代 groupBy
• 主題 54:限制操作的數量
• 主題 55:(有效能考量時) 使⽤ Primitive Array 來操作資料
• 主題 56:考慮使⽤ Mutable Collection
主題 51
傾向使⽤ Sequences 來取代巨量且有多次
操作⾏為的 Collection
Iterable 與 Sequence 的差異
—
• Iterable
- 每⼀步都回傳⼀個新的 Collection
- 積極的 (Eager),每⼀步都會觸發運算
• Sequence
- 每⼀步都回傳⼀個新的 Sequence
- 懶惰的 (Lazy),除非有結束型操作才會開始運算
[Effective Kotlin 讀書會] 第八章 Efficient collection processing 導讀
Sequence 的惰性帶來的好處
—
• 操作時依⾃然順序執⾏
• 只做最少量的操作
• 可以無限⼤
• 不需要在每⼀步都產⽣新的 Collection
操作時依⾃然順序執⾏
—
• Sequence 的運算順序符合⾃然邏輯
預測⼀下兩者間的
操作順序?
—
sequenceOf(1, 2, 3)
.filter { print("F$it, "); it % 2 !" 1 }
.map { print("M$it, "); it * 2 }
.forEach { print("E$it, ") }
listOf(1, 2, 3)
.filter { print("F$it, "); it % 2 !" 1 }
.map { print("M$it, "); it * 2 }
.forEach { print("E$it, ") }
sequenceOf(1, 2, 3)
.filter { print("F$it, "); it % 2 !" 1 }
.map { print("M$it, "); it * 2 }
.forEach { print("E$it, ") }
!" Prints: F1, M1, E2, F2, F3, M3, E6,
listOf(1, 2, 3)
.filter { print("F$it, "); it % 2 !" 1 }
.map { print("M$it, "); it * 2 }
.forEach { print("E$it, ") }
!" Prints: F1, F2, F3, M1, M3, E2, E6,
預測⼀下兩者間的
操作順序?
—
[Effective Kotlin 讀書會] 第八章 Efficient collection processing 導讀
不⽤ Collection 時
—
for (e in listOf(1, 2, 3)) {
print("F$e, ")
if (e % 2 !" 1) {
print("M$e, ")
val mapped = e * 2
print ("E$mapped, ")
}
}
!" Prints: F1, M1, E2, F2, F3, M3, E6,
只做最少量的操作
—
• 不做多餘事 (懶惰⾄上)
誰的動作比較少?
—
(1!#10)
.filter { print("F$it, "); it % 2 !" 1 }
.map { print("M$it, "); it * 2 }
.find { it > 5 }
(1!#10).asSequence()
.filter { print("F$it, "); it % 2 !" 1 }
.map { print("M$it, "); it * 2 }
.find { it > 5 }
誰的動作比較少?
—
(1!#10)
.filter { print("F$it, "); it % 2 !" 1 }
.map { print("M$it, "); it * 2 }
.find { it > 5 }
!" Prints: F1, F2, F3, F4, F5, F6, F7, F8,
!" F9, F10, M1, M3, M5, M7, M9,
(1!#10).asSequence()
.filter { print("F$it, "); it % 2 !" 1 }
.map { print("M$it, "); it * 2 }
.find { it > 5 }
!" Prints: F1, M1, F2, F3, M3,
[Effective Kotlin 讀書會] 第八章 Efficient collection processing 導讀
官網 Iterable 範例
—
val words = "The quick brown fox jumps over the lazy dog"
.split(" ")
val lengthsList =
words.filter {
it.length > 3
}.map {
it.length
}.take(4)
println("Lengths of first 4 words longer than 3 chars:")
println(lengthsList)
[Effective Kotlin 讀書會] 第八章 Efficient collection processing 導讀
官網 Sequence 範例
—
val words = "The quick brown fox jumps over the lazy dog"
.split(" ")
val wordsSequence = words.asSequence()
val lengthsSequence =
wordsSequence.filter {
it.length > 3
}.map {
it.length
}.take(4)
println("Lengths of first 4 words longer than 3 chars")
println(lengthsSequence.toList())
[Effective Kotlin 讀書會] 第八章 Efficient collection processing 導讀
可以無限⼤
—
• 可⽤ generateSequence() 或 sequence() 產⽣無限⼤
的 Sequence
• ⼀定要⽤結束型操作 (如 take()、first()、find()、
indexOf()…等) 才可取回
• 注意!⽤ any()、all()、none() 前⼀定要有結束型操
作,以免進入無窮迴圈
產⽣ Sequence
opt.1
—
generateSequence(1) { it + 1 }
.map { it * 2 }
.take(10)
.forEach { print("$it, ") }
!" Prints: 2, 4, 6, 8, 10, 12, 14, 16, 18, 20,
產⽣ Sequence
opt.2
—
val fibonacci: Sequence<BigDecimal> = sequence {
var current = 1.toBigDecimal()
var prev = 1.toBigDecimal()
yield(prev)
while (true) {
yield(current)
val temp = prev
prev = current
current += temp
}
}
print(fibonacci.take(10).toList())
!" Prints: [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
不需要在每⼀步都產⽣新的 Collection
—
• ⼩量的時候感覺不出來,處理⼤型檔案時就很有感
什麼時候會產⽣
新的 Collection?
—
numbers
.filter { it % 10 !" 0 } !" 1 collection here
.map { it * 2 } !" 1 collection here
.sum()
!" In total, 2 collections created under the hood
numbers
.asSequence()
.filter { it % 10 !" 0 }
.map { it * 2 }
.sum()
!" No collections created
⼤檔案比較
—
File("!!$").readLines()
.drop(1) !" Drop descriptions of the columns
.mapNotNull { it.split(",").getOrNull(6) }
!" Find description
.filter { "CANNABIS" in it }
.count()
.let(!%println)
File("!!$").useLines { lines: Sequence<String> !"
lines.drop(1) !" Drop descriptions of the columns
.mapNotNull { it.split(",").getOrNull(6) }
!" Find description
.filter { "CANNABIS" in it }
.count()
.let { println(it) }
}
「傾向使⽤ Sequences 來取代巨量且有多次操作⾏為的 Collection 」
這句話到底是什麼意思?
—
• 多⼤叫巨量?
- 上萬筆以上的資料
- 元素本⾝就好幾 MB
• 多少次叫多次?
- 超過⼀次
- 依據數量級的不同才會有感
什麼時候 Sequence 會不快?
—
• 當使⽤ sorted() 的時候
- 還是得把整個 Collection 跑⼀遍 (底層有做⼀些轉換)
- 無法⽤在無限⼤的 Sequence 上
- 但在⼤部份的情況下還是可能比 Collection 快⼀點
怎麼看 Java Stream?
—
• Kotlin 的函式比較多且語法比較好⽤
• Kotlin 的 Sequence 可以多平台使⽤
• Java 的 Stream 可以啟動 Parallel 模式,可以有很⼤幅度的
效能強化,但有些陷阱要注意
• 不使⽤ Parallel 模式時,很難說 Stream 的效能比
Sequence 好
主題 52
考慮使⽤ Map 來儲存關聯的元素
需要在 Collection 裡找東⻄的情境
—
• 從⼀個或多個檔案載入設定值的 Class
• 儲存下載資料的網路 Repository
• (在測試裡很常⽤的) 記憶體 Repository
從 Collection 裡取值時的考量
—
• 透過唯⼀值 (identifier、name、unique key) 在
Collection 裡搜尋取值
• 需要對每⼀個元素做比對
• 需考慮線性複雜度,愈⼤愈慢
Map 是個好解⽅
—
• Kotlin 的 Map 預設是⽤ LinkedHashMap 實作
• 從 HashMap 裡找東⻄快多了
• 但只有找東⻄快,假如是修改或是迭代元素的話,List 和
Map 的差異不⼤
⽤ List 實作
—
class InMemoryUserRepoWithList : UserRepo {
private val users: MutableList<User> = mutableListOf()
override fun getUser(id: Int): User? = users
.firstOrNull { it.id !" id }
fun addUser(user: User) {
users.add(user)
}
}
⽤ Map 實作
—
class InMemoryUserRepoWithMap : UserRepo {
private val users: MutableMap<Int, User> = mutableMapOf()
override fun getUser(id: Int): User? = users[id]
fun addUser(user: User) {
users[user.id] = user
}
}
轉換 List 成 Map
—
val users = listOf(
User(1, "Michal"),
User(2, "Mark"),
User(3, "Mark"),
)
val byId: Map<Int, User> = users.associateBy { it.id }
println(byId)
!" { 1=User(id=1, name=Michal),
!" 2=User(id=2, name=Mark),
!" 3=User(id=3, name=Mark) }
val byName: Map<String, User> = users.associateBy { it.name }
println(byName)
!" { Michal=User(id=1, name=Michal),
!" Mark=User(id=3, name=Mark) }
‼注意 Key 要唯⼀,不
然會被覆蓋
再從 Map 轉回 List
—
val originalList = byId.values
println(originalList)
!" [ User(id=1, name=Michal),
!" User(id=2, name=Mark),
!" User(id=3, name=Mark) ]
實務範例 pt.1
—
class ConfigurationsRepository(
configurations: List<Configuration>,
) {
private val configurations: Map<String, Configuration> =
configurations.associateBy { it.name }
fun getByName(name: String) = configurations[name]
}
實務範例 pt.2
—
class NetworkUserRepo(
private val userService: UserService,
) : UserRepo {
private var users: Map<Int, User>? = null
suspend fun loadUsers() {
users = userService.getUsers().associateBy { it.id }
}
override fun getUser(id: Int): User? = users!&get(id)
}
使⽤時機
—
• 很常需要從 Collection 裡找東⻄時
• ⼀般來說後端 (Server-Side) 比較常⽤,前端 (Mobile) 比較少⽤
主題 53
考慮使⽤ groupingBy 來取代 groupBy
模擬情境
—
• 在⼀個使⽤者清單裡,依照其所在城市來計算⼈數
• 在⼀個球員清單裡,依照其團隊來計算總分
• 在⼀個選項清單裡,依照其分類來找出最好選擇
情境實作
—
val usersCount: Map<City, Int> =
users.groupBy { it.city }
.mapValues { (_, users) !" users.size }
val pointsPerTeam: Map<Team, Int> =
players.groupBy { it.team }
.mapValues { (_, players) !"
players.sumOf { it.points }
}
val bestFormatPerQuality: Map<Quality, Resolution> =
formats.groupBy { it.quality }
.mapValues { (_, list) !"
list.maxOfOrNull { it.resolution }!'
}
groupBy 和 groupingBy
—
• 使⽤ groupBy()
- 中間多了幾層操作
- ⽅便、好懂
• 使⽤ groupingBy()
- 去掉中間的操作,但要搭配 eachCount()、fold()、
reduce()、aggregate() 使⽤
- 不好理解
圖解 groupingBy() + eachCount()
—
"
"
"
#
# ⭐ ⭐ ⭐
#
# ⭐
⭐ ⭐ " "
"
groupingBy()
eachCount()
= 2,
# ⭐ = 3, " = 3
情境實作 pt.1
—
val usersCount: Map<City, Int> =
users.groupingBy { it.city }
.eachCount()
情境實作 pt.2-1
—
val pointsPerTeam: Map<Team, Int> =
players.groupingBy { it.team }
.fold(0) { accumulator, player !"
accumulator + player.points
}
情境實作 pt.2-2
—
val pointsPerTeamWithExtension: Map<Team, Int> =
players.groupingBy { it.team }
.eachSumBy{ it.points }
!( Extension Function
fun <T, K> Grouping<T, K>.eachSumBy(
selector: (T) !) Int
): Map<K, Int> = this.fold(0) { accumulator, element !"
accumulator + selector(element)
}
主題 54
限制操作的數量
什麼時候會有效能考量?
—
• 使⽤ Collection 的時候
- 對元素有額外的迭代
- 在迭代時有額外的 Collection 被建立
• 使⽤ Sequence 的時候
- 有額外的物件包裏整個 Sequence 時
- 建立 Lambda 表達式時
總之就是
愈少操作愈好
— !" Works
fun List<User>.getNames(): List<String> =
this.map { it.name }
.filter { it !* null }
.map { it!' }
!" Better
fun List<User>.getNames(): List<String> =
this.map { it.name }
.filterNotNull()
!" Best
fun List<User>.getNames(): List<String> =
this.mapNotNull { it.name }
要怎麼才能辦到?
—
• 依賴 IDE 提⽰
• 把表格背起來
[Effective Kotlin 讀書會] 第八章 Efficient collection processing 導讀
[Effective Kotlin 讀書會] 第八章 Efficient collection processing 導讀
主題 55
(有效能考量時) 使⽤ Primitive Array 來操作資料
Primitive Type 的優勢
—
• 輕量 (多⼀層物件就多⼀層重量)
• 快 (透過 accessor 取值會有額外的成本)
Kotlin 的天性
—
• 在底層封裝了 Primitive Type (⾃動最佳化)
• Collection 裡放的是 Generic
• 但還是有⽅法可以⽤ Primitive Type…
對照表
—
差異比較 pt.1
—
val ints: List<Int> = List(1_000_000) { it }
val array: Array<Int> = ints.toTypedArray()
val intArray: IntArray = ints.toIntArray()
println(getObjectSize(ints)) !" 20 000 040
println(getObjectSize(array)) !" 20 000 016
println(getObjectSize(intArray)) !" 4 000 016
差異比較 pt.2
—
open class PrimitiveArrayBenchmark {
lateinit var list: List<Int>
lateinit var array: IntArray
@Setup
fun init() {
list = List(1_000_000) { it }
array = IntArray (1_000_000) { it }
}
@Benchmark
!" On average 1 260 593 ns
fun averageOnIntList(): Double {
return list.average()
}
@Benchmark
!" On average 868 509 ns
fun averageOnIntArray(): Double {
return array.average()
}
}
後話
—
• 對比於 List,使⽤ Primitive 在⼤多數的情境下並沒有⾜夠明顯
的效能優勢
• ⽽使⽤ List 是更為直覺、⽅便的選擇
• 總之,應優先使⽤ List,有效能考量時再改⽤ Primitive
主題 56
考慮使⽤ Mutable Collection
為什麼要⽤ Mutable Collection?
—
• 使⽤ Immutable 是為了安全,給我們更多控制
• 使⽤ Mutable 是為了效能
• 取個平衡:
- 只在 Local Processing 的時候⽤ Mutable Collection
- 像是在寫 Utils 的時候,常常修改 Collection 內容時
底層實作
—
public operator fun <T> Iterable<T>.plus(
elements: Array<out T>
): List<T> {
if (this is Collection) return this.plus(elements)
val result = ArrayList<T>()
result.addAll(this)
result.addAll(elements)
return result
}
Kotlin Collection
全⽅位解析攻略
—
collection.kotlin.tips
• 詳解 200+ Collection ⽅法
• 解析標準函式庫原始碼
• 實務範例
• 免費下載速查地圖
內容三⼤組成
—
技法 ⼼法 實戰
快速充實腦中的
Collection ⼯具箱
追溯 Collection 原始碼
了解其語法設計與應⽤
透過情境解題
將 Collection 應⽤於⽇常任務
拆分成九⼤分類
—
繪製成速查⼼智圖
—
與本章有關的章節
—
• 主題 51:傾向使⽤ Sequences 來取代 Collection
- 2-3-3 提升處理⼤量資料時的效能 (p.2-78)
- 4-4 關於效能與效率的反思 (p.4-6)
• 主題 52:考慮使⽤ Map 來儲存關聯的元素
- 1-9-6 Associate 系列⽅法 (p.1-164)
與本章有關的章節
—
• 主題 53:考慮使⽤ groupingBy 來取代 groupBy
- 1-10-5 With-grouping 系列⽅法 (p.1-199)
- 3-2 資料統計運算 (p.3-18)
• 主題 54:限制操作的數量
- 4-3 條條⼤路通羅⾺ (p.4-3)
與本章有關的章節
—
• 主題 55:(有效能考量時) 使⽤ Primitive Array 來操作資料
- 1-11-1 toArray 系列⽅法 (p.1-203)
• 主題 56:考慮使⽤ Mutable Collection
-
關注粉絲⾴及頻道
—
Coding 職⼈塾
Kraftsman

More Related Content

PDF
Domain Modeling with FP (DDD Europe 2020)
PDF
31b - JUnit and Mockito.pdf
PDF
Asynchronous javascript
PDF
オブジェクト指向アンチパターンを考えてみた
PPT
Asynchronous Programming in C# - Part 1
PDF
Introducing Clean Architecture
PDF
뱅크샐러드 파이썬맛 레시피
PDF
Clean Architecture
Domain Modeling with FP (DDD Europe 2020)
31b - JUnit and Mockito.pdf
Asynchronous javascript
オブジェクト指向アンチパターンを考えてみた
Asynchronous Programming in C# - Part 1
Introducing Clean Architecture
뱅크샐러드 파이썬맛 레시피
Clean Architecture

What's hot (20)

PDF
Domain Driven Design with the F# type System -- NDC London 2013
PDF
Atomic design
PDF
The Functional Programming Triad of Map, Filter and Fold
PDF
Unit Testing with Jest
PDF
Introduction to kotlin
ODP
Dependency Injection in Spring in 10min
PDF
[MOPCON 2022] 以 Kotlin Multiplatform 制霸全平台
PDF
Atomicity In Redis: Thomas Hunter
PPT
PDF
Ruin your life using robot framework
PDF
Clean code
PPTX
Java - Collections framework
PDF
The lazy programmer's guide to writing thousands of tests
PPTX
Jena Programming
PDF
徹底解説!Project Lambdaのすべて リターンズ[祝Java8Launch #jjug]
PDF
A Separation of Concerns: Clean Architecture on Android
PDF
Real Life Clean Architecture
PDF
Spring Boot
PDF
REST APIs with Spring
PDF
Serialization & De-serialization in Java
Domain Driven Design with the F# type System -- NDC London 2013
Atomic design
The Functional Programming Triad of Map, Filter and Fold
Unit Testing with Jest
Introduction to kotlin
Dependency Injection in Spring in 10min
[MOPCON 2022] 以 Kotlin Multiplatform 制霸全平台
Atomicity In Redis: Thomas Hunter
Ruin your life using robot framework
Clean code
Java - Collections framework
The lazy programmer's guide to writing thousands of tests
Jena Programming
徹底解説!Project Lambdaのすべて リターンズ[祝Java8Launch #jjug]
A Separation of Concerns: Clean Architecture on Android
Real Life Clean Architecture
Spring Boot
REST APIs with Spring
Serialization & De-serialization in Java
Ad

Similar to [Effective Kotlin 讀書會] 第八章 Efficient collection processing 導讀 (20)

PDF
Java 開發者的函數式程式設計
PPTX
Python入門:5大概念初心者必備
PDF
Python learn guide
PDF
Python 温故
PDF
functional-scala
PDF
Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫
PDF
JavaScript 快速複習 2017Q1
PPTX
Scala+spark 2nd
PPTX
Python入門:5大概念初心者必備 2021/11/18
KEY
Scala
PDF
lambda/closure – JavaScript、Python、Scala 到 Java SE 7
PPTX
Scala+RDD
PDF
Eloquent ORM
PPTX
CKAN : 資料開放平台技術介紹 (CAKN : Technical Introduction to Open Data Portal)
PPT
ios分享
DOC
Free Marker中文文档
PDF
Python速成指南
PDF
Pytables
PDF
I os 02
PDF
Eloquent ORM
Java 開發者的函數式程式設計
Python入門:5大概念初心者必備
Python learn guide
Python 温故
functional-scala
Java SE 8 的 Lambda 連鎖效應 - 語法、風格與程式庫
JavaScript 快速複習 2017Q1
Scala+spark 2nd
Python入門:5大概念初心者必備 2021/11/18
Scala
lambda/closure – JavaScript、Python、Scala 到 Java SE 7
Scala+RDD
Eloquent ORM
CKAN : 資料開放平台技術介紹 (CAKN : Technical Introduction to Open Data Portal)
ios分享
Free Marker中文文档
Python速成指南
Pytables
I os 02
Eloquent ORM
Ad

More from Shengyou Fan (20)

PDF
[JCConf 2024] Kotlin/Wasm:為 Kotlin 多平台帶來更多可能性
PDF
[GDG Kaohsiung DevFest 2023] 以 Compose 及 Kotlin Multiplatform 打造多平台應用程式
PDF
[JCConf 2023] 從 Kotlin Multiplatform 到 Compose Multiplatform:在多平台間輕鬆共用業務邏輯與 U...
PDF
[Kotlin 讀書會第五梯次] 深入淺出 Kotlin 第一章導讀
PDF
[WebConf Taiwan 2023] 一份 Zend Engine 外帶!透過 Micro 讓一次打包、多處運行變得可能
PDF
How I make a podcast website using serverless technology in 2023
PDF
[JCConf 2022] Compose for Desktop - 開發桌面軟體的新選擇
PDF
Using the Exposed SQL Framework to Manage Your Database
PDF
[COSCUP 2022] 讓黑畫面再次偉大 - 用 PHP 寫 CLI 工具
PDF
[COSCUP 2022] Kotlin Collection 遊樂園
PDF
初探 Kotlin Multiplatform
PDF
簡化 JVM 上雲 - 透過 Azure Spring Cloud 提升開發、發佈及服務監控效率
PDF
[PHP 也有 Day #64] PHP 升級指南
PDF
以 Kotlin Multiplatform Mobile (KMM) 開發跨平台行動應用
PDF
Composer 經典食譜
PDF
老派浪漫:用 Kotlin 寫 Command Line 工具
PDF
[Kotlin Serverless 工作坊] 單元 4 - 實作 RSS Aggregator
PDF
[Kotlin Serverless 工作坊] 單元 3 - 實作 JSON API
PDF
[Kotlin Serverless 工作坊] 單元 2 - 簡介 Kotlin Serverless
PDF
[Kotlin Serverless 工作坊] 單元 1 - 開發環境建置
[JCConf 2024] Kotlin/Wasm:為 Kotlin 多平台帶來更多可能性
[GDG Kaohsiung DevFest 2023] 以 Compose 及 Kotlin Multiplatform 打造多平台應用程式
[JCConf 2023] 從 Kotlin Multiplatform 到 Compose Multiplatform:在多平台間輕鬆共用業務邏輯與 U...
[Kotlin 讀書會第五梯次] 深入淺出 Kotlin 第一章導讀
[WebConf Taiwan 2023] 一份 Zend Engine 外帶!透過 Micro 讓一次打包、多處運行變得可能
How I make a podcast website using serverless technology in 2023
[JCConf 2022] Compose for Desktop - 開發桌面軟體的新選擇
Using the Exposed SQL Framework to Manage Your Database
[COSCUP 2022] 讓黑畫面再次偉大 - 用 PHP 寫 CLI 工具
[COSCUP 2022] Kotlin Collection 遊樂園
初探 Kotlin Multiplatform
簡化 JVM 上雲 - 透過 Azure Spring Cloud 提升開發、發佈及服務監控效率
[PHP 也有 Day #64] PHP 升級指南
以 Kotlin Multiplatform Mobile (KMM) 開發跨平台行動應用
Composer 經典食譜
老派浪漫:用 Kotlin 寫 Command Line 工具
[Kotlin Serverless 工作坊] 單元 4 - 實作 RSS Aggregator
[Kotlin Serverless 工作坊] 單元 3 - 實作 JSON API
[Kotlin Serverless 工作坊] 單元 2 - 簡介 Kotlin Serverless
[Kotlin Serverless 工作坊] 單元 1 - 開發環境建置

[Effective Kotlin 讀書會] 第八章 Efficient collection processing 導讀