- Lab
-
Libraries: If you want this lab, consider one of these libraries.
- Core Tech
Analyze Weather Data with Kotlin
In this Code Lab, you'll turn a raw CSV of historical weather readings into a working Kotlin analysis tool. You'll model the data as idiomatic Kotlin data classes, filter and summarize temperature, humidity, and pressure trends across a date range, and use standard library collection operations to flag notable anomalies. By the end, you'll have a command-line program that turns raw weather data into a clear, exportable report.
Lab Info
Table of Contents
-
Challenge
Step 1: Model the weather data
Introduction
A folder full of comma-separated weather readings doesn't tell you much on its own. Before you can filter it, summarize it, or spot anything unusual in it, you need a way to treat each row as more than just text.
In this lab, you'll build a Kotlin command-line tool that turns a raw weather station export into a working analysis pipeline: parsing daily readings into typed objects, narrowing them to a reporting window, computing summary statistics, flagging temperature anomalies, and writing the results back out to CSV and text files.
To start, you'll give the CSV's rows a real shape by modeling each day's reading as a Kotlin type, then teach your program how to read that shape out of the file.
The
applicationdirectory already contains a Gradle-based Kotlin project withMain.ktready for your implementation.info> Feeling stuck? Check out the matching
solution/stepN/folder for the step you're on to see a working implementation. Give it a try on your own first. The solution folder is your safety net, not your starting point. ### Modeling a single weather readingKotlin's
data classmodifier is the idiomatic way to represent a type whose entire purpose is to carry a fixed set of values, rather than provide behavior.Adding
datato a class declaration gives you structural equality for free: two instances with the same property values are equal to each other, and the compiler generates readabletoString,hashCode, andcopyimplementations for you. Withoutdata, a class falls back to reference equality, so two instances with identical property values are still considered different objects.data class Point(val x: Int, val y: Int)Here,
Point(1, 2) == Point(1, 2)istruebecause the class is adata class. If thedatakeyword were removed, the same comparison would befalse.warning> The lab environment on the right may take up to a minute to finish loading. Continue after the terminal and project files appear. ### Turning CSV lines into typed objects
Kotlin's
Filetype exposes convenient extension functions for reading a file's contents as aList<String>, one entry per line, usingreadLines().Since the first line of a CSV file is usually a header rather than data, you typically want to drop it before processing the remaining lines. From there, splitting each line on its delimiter and converting the resulting string fields with functions like
LocalDate.parseandtoDouble()lets you turn a purely textual row into a fully typed object.fun loadItems(path: String): List<Item> = File(path).readLines() .drop(1) .map { line -> val fields = line.split(",") Item(fields[0], fields[1].toDouble()) }The shape above reads a file, skips its header row, and maps each remaining line into a typed instance by splitting on commas and converting the fields you need. info> This lab experience was developed by the Pluralsight team using an internally developed AI tool. All sections were verified by human experts for accuracy prior to publication. However, content may still contain errors or inaccuracies, and we recommend independent verification.
To report a problem or provide feedback, click here. Feedback may be used to improve accuracy in accordance with our Privacy Policy. -
Challenge
Step 2: Filter by date range
With a full list of typed
WeatherRecordreadings in hand, you can start narrowing that data down to the window a report actually needs. Here, you'll write a function that filters the parsed records down to a given start and end date.Because
LocalDateimplementsComparable, Kotlin lets you express an inclusive date range directly with the..range operator and check membership within, rather than chainingisAfterandisBeforecomparisons by hand.By the end of this step, you'll be able to hand any date range to your program and get back just the readings that fall inside it. ### Filtering with ranges and predicates
Kotlin lets you build a range directly from two
Comparablevalues using the..operator, and check whether a value falls inside that range with theinkeyword.Combined with the standard library's
filterfunction, this gives you a concise way to keep only the elements of a collection whose property falls within a given range, without writing manual comparison logic.val inRange = items.filter { it.value in lowerBound..upperBound }This reads naturally: for each item, check whether its value lies within the closed range from
lowerBoundtoupperBound, keeping only the ones that do. -
Challenge
Step 3: Calculate summary statistics
With the ability to narrow readings to a specific window, the next step is turning that filtered list into numbers that summarize the period.
Here, you'll model a summary result and compute the average, minimum, and maximum temperature alongside average humidity and pressure for a set of records, using Kotlin's standard library aggregate functions.
By the end of this step, your program will be able to turn any filtered list of readings into a concise statistical summary. ### Modeling a summary result
Just like an individual reading, a summary of many readings is a good candidate for a
data class: it exists purely to carry a fixed set of computed values, and its equality should be based on those values rather than on object identity.data class Range(val min: Int, val max: Int)Two
Rangeinstances built from the sameminandmaxvalues compare equal to each other because the type is declared as adata class. ### Aggregating a collection with the standard libraryKotlin's standard library provides ready-made aggregate functions for numeric collections, so you rarely need to write a manual loop to compute a sum, average, minimum, or maximum.
Calling
average(),min(), ormax()directly on aList<Double>, often after mapping to just the property you need, gives you these statistics in a single expression.val prices = items.map { it.price } val averagePrice = prices.average() val cheapest = prices.min() val priciest = prices.max()Mapping first to the specific property you want to aggregate, then calling the aggregate function, keeps each calculation focused and easy to read.
-
Challenge
Step 4: Detect trends and anomalies
With a way to summarize a set of readings, the program can now use that summary as a baseline for spotting what stands out.
Here, you'll write two functions: one that flags individual days whose temperature deviates sharply from the period average, and one that classifies whether the period as a whole is trending warmer or cooler by comparing its earlier and later halves.
By the end of this step, your program will be able to surface both isolated anomalies and a general temperature trend for any filtered range of readings. ### Measuring deviation from an average
Once you have an average value for a collection, you can flag individual elements that stray too far from it by comparing the absolute difference between each element's value and the average against a threshold.
Kotlin's
kotlin.math.absfunction returns the absolute value of a number, which is exactly what you need to measure deviation regardless of whether a value is above or below the average.val outliers = items.filter { abs(it.value - average) > threshold }Filtering on the absolute difference, rather than checking separately for values above and below the average, keeps the anomaly check to a single comparison. ### Comparing two halves of a sorted collection
To compare an earlier period against a later one, you first need the records in chronological order, which
sortedBygives you by sorting on a chosen property.From a sorted list,
take(n)returns its firstnelements anddrop(n)returns everything after them, letting you split one list into two halves without any manual indexing.val sorted = items.sortedBy { it.date } val half = sorted.size / 2 val earlier = sorted.take(half) val later = sorted.drop(half)Computing an aggregate like
average()separately onearlierandlatergives you two numbers you can compare to classify the overall direction of change. -
Challenge
Step 5: Export the results
With records filtered, summarized, and analyzed for trends and anomalies, the only piece left is turning those results into files the station's operators can actually read.
Here, you'll write the filtered records back out as a CSV file and write a separate plain-text report containing the summary statistics, trend classification, and any flagged anomalies, then wire every piece built so far together behind a runnable command-line entry point.
By the end of this step, running the program end to end will produce both output files from a real weather CSV export. ### Writing text to a file
Kotlin's
Filetype also provides extension functions for writing content back out, mirroring the ones used for reading.writeTexttakes a single string and writes it to the given path, replacing any existing content, which makes it a natural fit for output built up as a list of lines joined together with newline characters.val lines = listOf(header) + rows File(path).writeText(lines.joinToString("\n") + "\n")Joining a header and a list of row strings together before writing keeps the file-writing logic itself simple, independent of how each row is formatted. ### Building a multi-line report from parts
A plain-text report is often easiest to build as a list of individual lines, each one added as you go, then joined together at the end.
Kotlin's string templates let you embed a value directly inside a string using
$nameor${expression}, which keeps each line's formatting readable without manual string concatenation.val total = items.sumOf { it.amount } val lines = mutableListOf<String>() lines.add("Total: $total") lines.add(if (items.isEmpty()) "None" else items.joinToString("\n"))Building the report this way lets you add each piece, such as summary values, a classification, or a list of flagged entries, as its own line before writing the whole thing to disk. ### Reading flag/value command-line arguments
Command-line arguments arrive as a flat array, but a CLI that accepts
--flag valuepairs is easier to read from a map keyed by flag name.chunked(2)groups a list into sublists of two consecutive elements at a time, turning a flat sequence of alternating flags and values into a list of flag/value pairs. Callingassociateon that list, destructuring each pair into(flag, value), then builds a single map from all of them.val options = args.toList().chunked(2).associate { (flag, value) -> flag to value } val target = options["--target"]Given
arrayOf("--target", "foo"),chunked(2)first produces[["--target", "foo"]], andassociateturns that into the map{"--target" to "foo"}, sooptions["--target"]returns"foo", without a manual loop or if/else chain. ### Running the CLI with argumentsThe Gradle
applicationplugin'sruntask launches your program'smainfunction, and any arguments after--args="..."are passed straight through as the arraymainreceives. Wrapping the whole argument string in quotes lets it contain spaces and multiple--flag valuepairs../gradlew run --args="--target foo"This runs the program with
arrayOf("--target", "foo")passed tomain. Nice work. You built a complete Kotlin command-line tool that reads a weather station's CSV export, models each reading as a typed data class, and works through filtering, summarizing, and flagging anomalies before writing the results back out to disk.Along the way, you practiced idiomatic Kotlin patterns for working with real data:
data classtypes for structural equality, range-based filtering with..andin, standard library aggregate functions for computing statistics, and file I/O for both reading and writing. You also wired several independent functions together into a single, runnable pipeline driven by command-line arguments.When you ran the CLI against the sample weather data, you saw that pipeline in action: a filtered CSV file and a text report, both generated from a single command, summarizing a real, if fictional, month of weather readings.
About the author
Real skill practice before real-world application
Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.
Learn by doing
Engage hands-on with the tools and technologies you’re learning. You pick the skill, we provide the credentials and environment.
Follow your guide
All labs have detailed instructions and objectives, guiding you through the learning process and ensuring you understand every step.
Turn time into mastery
On average, you retain 75% more of your learning if you take time to practice. Hands-on labs set you up for success to make those skills stick.