Skip to Content
Course content

41: Grouping, Chunking, and Windowed Operations

Click on the "Edit" button in the top corner of the screen to edit your slide content.

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:

  1. 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.
  2. 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
Rating
0 0

There are no comments for now.

to be the first to leave a comment.