Step 6 of 13
List, Set, Map, common operations (filter, map, sortedBy), fold/reduce, lazy Sequences
List, Set, Map — read-only vs mutable, และ lazy Sequences สำหรับ collection ใหญ่ ๆ
Kotlin collections come in two flavors: read-only and mutable.
// Read-only (default — use this whenever possible)
val names: List<String> = listOf("Alice", "Bob", "Charlie")
val unique: Set<Int> = setOf(1, 2, 3, 2, 1) // {1, 2, 3}
val scores: Map<String, Int> = mapOf("Alice" to 90, "Bob" to 85)
// Mutable (only when you need to add/remove)
val mutableNames: MutableList<String> = mutableListOf("Alice", "Bob")
val mutableScores: MutableMap<String, Int> = mutableMapOf("Alice" to 90)
mutableNames.add("Charlie")
mutableScores["Charlie"] = 78
println(mutableNames) // [Alice, Bob, Charlie]
println(mutableScores) // {Alice=90, Charlie=78}
Diagram: Read-only interfaces (List, Set, Map) vs mutable subinterfaces (MutableList, MutableSet, MutableMap). Mutable extends read-only.
Loading diagram...
data class Person(val name: String, val age: Int)
val people = listOf(
Person("Alice", 30),
Person("Bob", 25),
Person("Charlie", 35),
Person("Diana", 28)
)
// filter — keep items that match a condition
val adults = people.filter { it.age >= 30 }
println(adults) // [Person(name=Alice, age=30), Person(name=Charlie, age=35)]
// map — transform each item
val names = people.map { it.name }
println(names) // [Alice, Bob, Charlie, Diana]
// sortedBy — sort by a property
val byAge = people.sortedBy { it.age }
println(byAge.map { "${it.name}:${it.age}" })
// [Bob:25, Diana:28, Alice:30, Charlie:35]
// find — first match or null
val first30 = people.find { it.age == 30 }
println(first30) // Person(name=Alice, age=30)
// groupBy — group items by a key
val byDecade = people.groupBy { it.age / 10 * 10 }
println(byDecade[20]?.map { it.name }) // [Bob, Diana]
println(byDecade[30]?.map { it.name }) // [Alice, Charlie]
You can chain multiple operations:
val result = people
.filter { it.age >= 28 }
.sortedByDescending { it.age }
.map { "${it.name} (${it.age})" }
println(result)
// [Charlie (35), Alice (30), Diana (28)]
Combine all items into a single value:
val numbers = listOf(1, 2, 3, 4, 5)
// fold — starts with an initial value
val sum = numbers.fold(0) { acc, num -> acc + num }
println(sum) // 15
// reduce — starts with the first element
val product = numbers.reduce { acc, num -> acc * num }
println(product) // 120
// Practical: build a summary string
val summary = people.fold(StringBuilder()) { sb, person ->
sb.appendLine("${person.name}: ${person.age}")
}.toString()
println(summary)
// Alice: 30
// Bob: 25
// Charlie: 35
// Diana: 28
List processes every step eagerly. Sequence is lazy — it processes items one at a time through the entire chain:
val numbers = (1..1_000_000).toList()
// List: creates intermediate lists at each step
val listResult = numbers
.filter { println("filter $it"); it % 2 == 0 }
.map { println(" map $it"); it * 2 }
.take(3)
.toList()
// Sequence: processes one item through the full chain at a time
val seqResult = numbers.asSequence()
.filter { println("filter $it"); it % 2 == 0 }
.map { println(" map $it"); it * 2 }
.take(3)
.toList()
Diagram: List processes all items per step (eager). Sequence processes one item through all steps (lazy). Fewer intermediate allocations.
Loading diagram...
take(3))Process a list of orders:
data class Order(val customer: String, val amount: Double, val paid: Boolean)
fun main() {
val orders = listOf(
Order("Alice", 100.0, true),
Order("Bob", 50.0, false),
Order("Alice", 200.0, true),
Order("Charlie", 75.0, true),
Order("Bob", 150.0, true),
)
val totalByCustomer = orders
.filter { it.paid }
.groupBy { it.customer }
.mapValues { (_, orders) -> orders.sumOf { it.amount } }
.toList()
.sortedByDescending { it.second }
totalByCustomer.forEach { (customer, total) ->
println("$customer: \$$total")
}
// Alice: $300.0
// Bob: $150.0
// Charlie: $75.0
}