Kotlin
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Null Safety
-
Section 4: Object-Oriented Kotlin
-
Section 5: Functional Kotlin
-
Section 6: Coroutines
-
Section 7: Collections Deep Dive
-
Section 8: Type System Deep Dive
-
Section 9: Interop and Tooling
-
Section 10: Kotlin DSLs and Patterns
-
Section 11: Testing and Quality
-
Section 12: Server-Side Kotlin
-
Section 13: Practical Projects
-
Section 14: Interview Practice
-
Section 15: More Practice Exercises
-
Section 16: More Standard Library
-
Section 17: Multiplatform Kotlin
-
Section 18: kotlin.collections In Depth
-
Section 19: kotlin.text In Depth
-
Section 20: kotlin.ranges and kotlin.sequences
-
Section 21: kotlin.io and File Handling
-
Section 22: kotlinx.coroutines Deep Dive
-
Section 23: kotlin.reflect
-
Section 24: Android Development with Kotlin Overview
-
Section 25: Kotlin Multiplatform Deep Dive
-
Section 26: Kotlin for Backend Deep Dive
-
Section 27: Kotlin Design Patterns
-
Section 28: Advanced Language Features
-
Section 29: More Practice Exercises
-
Section 30: More Interview Practice
-
Section 31: Kotlin Type System Deep Dive
-
Section 32: Kotlin Null Safety Advanced
-
Section 33: Kotlin Testing Deep Dive
-
Section 34: Kotlin Build Tooling Deep Dive
-
Section 35: Kotlin Serialization
-
Section 36: Kotlin Performance Considerations
-
Section 37: Kotlin Native Overview
-
Section 38: Kotlin for Data and Scripting
-
Section 39: More Coroutines Practice
-
Section 40: More Android-Adjacent Patterns
-
Section 41: More Practical Projects
-
Section 42: More Design and Architecture Practice
-
Section 43: Kotlin Language Evolution
-
Section 44: More Interview and Review
-
Section 45: Kotlin Delegation Patterns Deep Dive
-
Section 46: Kotlin Annotations Deep Dive
-
Section 47: Kotlin for Gradle Plugin Development
-
Section 48: Kotlin Concurrency Beyond Coroutines
-
Section 49: Kotlin Compiler Internals
-
Section 50: Real-World Kotlin Case Studies
-
Section 51: Final Practice Projects
-
Section 52: Kotlin for Server-Side Reactive Programming
-
Section 53: More Practice and Drills
-
Section 54: Kotlin Security Practices
41: Grouping, Chunking, and Windowed Operations
I've seen this happen more times than I can count: a developer needs to analyze a sequence of data—like stock prices or sensor readings—and they reach for chunked() because it sounds like exactly what they need. They want to compare one value to the next, so they "chunk" the data into pairs. But then the logic fails on every second transition. Let's look at a piece of code that tries to detect "spikes" in temperature readings.
val temperatures = listOf(20, 21, 20, 25, 30, 28, 27)
// Goal: Find all instances where temp jumped by more than 2 degrees
val spikes = temperatures.chunked(2).filter { pair ->
pair[1] - pair[0] > 2
}
println(spikes) // Output: [[20, 25]]
The Gap in the Chunks
If you run that code, you'll notice it only found one spike (20 to 25). But look at the data again: there's a jump from 25 to 30 right there in the middle! Why did we miss it? Because chunked(2) splits the list into non-overlapping segments: [20, 21], [20, 25], [30, 28], [27]. The transition from 25 to 30 was split across two different chunks. The "seam" of the chunk swallowed our data point.
Using Windowed for Overlapping Analysis
When you need to analyze a sequence where the end of one period is the start of the next, you don't want chunks; you want a sliding window. This is where windowed() comes in. Unlike chunked(), windowed() allows you to define a step. By default, the step is 1, meaning the window slides forward by one element at a time.
val temperatures = listOf(20, 21, 20, 25, 30, 28, 27)
val spikes = temperatures.windowed(size = 2).filter { pair ->
pair[1] - pair[0] > 2
}
println(spikes) // Output: [[20, 25], [25, 30]]
Now we've captured both spikes. I usually recommend windowed whenever you're doing "delta" calculations or moving averages. If you actually wanted to skip elements (say, analyzing every 3rd window), you'd just set step = 3.
Categorizing Data with groupBy
Now, let's pivot to a different problem: organization. Sometimes you don't care about the sequence, but you care about the category. I've found that groupBy is one of the most powerful tools in the Kotlin stdlib for cleaning up messy API responses. It transforms a List<T> into a Map<K, List<T>>.
Imagine you have a list of transactions and you want to see them organized by currency:
data class Transaction(val id: Int, val amount: Double, val currency: String)
val txns = listOf(
Transaction(1, 10.0, "USD"),
Transaction(2, 15.0, "EUR"),
Transaction(3, 20.0, "USD"),
Transaction(4, 5.0, "GBP")
)
val grouped = txns.groupBy { it.currency }
// Result: {USD=[Transaction(1...), Transaction(3...)], EUR=[Transaction(2...)], GBP=[Transaction(4...)]}
A pro tip here: groupBy also has an overload that lets you transform the values while grouping. If you only wanted the amounts and not the whole Transaction object, you could do txns.groupBy({ it.currency }, { it.amount }). It saves you from having to map over the resulting lists later.
When to Actually Use Chunked
So, if windowed is so great, when do we actually use chunked? I use chunked primarily for batch processing. If you're writing a script to upload 10,000 records to an API that only accepts 100 records per request, chunked(100) is your best friend. It's designed specifically for breaking a large workload into discrete, non-overlapping batches.
val allIds = (1..1000).toList()
allIds.chunked(100).forEach { batch ->
println("Uploading batch of ${batch.size} IDs...")
// api.upload(batch)
}
To sum it up: use windowed for analysis/trends, groupBy for categorization, and chunked for batching.
📋 Practical Task
Processing a Server Log File
You are building a log analyzer. You have a list of log entries, where each entry contains a severity level (INFO, WARN, ERROR) and a timestamp. You need to perform two specific tasks to help the ops team:
- Group by Severity: Create a map where the keys are the severity levels and the values are lists of the log messages associated with that level.
- Detect Error Bursts: An "Error Burst" is defined as any sequence of 3 consecutive log entries that are all "ERROR". Using
windowed, find all these bursts.
Starter Code:
data class LogEntry(val level: String, val message: String)
val logs = listOf(
LogEntry("INFO", "System started"),
LogEntry("ERROR", "Database connection failed"),
LogEntry("ERROR", "Retry attempt 1 failed"),
LogEntry("ERROR", "Retry attempt 2 failed"),
LogEntry("INFO", "Connection established"),
LogEntry("WARN", "High memory usage"),
LogEntry("ERROR", "Disk full"),
LogEntry("ERROR", "Write failed"),
LogEntry("ERROR", "Critical shutdown"),
LogEntry("INFO", "Cleanup complete")
)
// Your code here
There are no comments for now.