Lambdas Workbook (Part 2): The Collection API
Thirteen short exercises on lambdas at work — forEach, the trailing-lambda convention, map and filter, chaining, the any/all/count/find/sumOf toolkit, and writing your own higher-order function.
Practice problems for Lambdas at Work: Transforming Collections. Each takes a minute or two. Most exercises auto-check: implement the function in the editor and press Run — hidden tests go green when you’re right and red (with a hint) when you’re not, all on JetBrains’ Kotlin server. A few printing exercises stay attempt-then-reveal: press Run to try them, then click Show answer. Nothing here is a trick question, just direct practice of the syntax from the lesson.
passing a lambda to a function
1. forEach
Given val names = listOf("Ada", "Linus"), print each name with forEach.
fun main() {
val names = listOf("Ada", "Linus")
// TODO: print each name with forEach
} Show answer Hide answer
names.forEach { println(it) } 2. The trailing-lambda convention
Rewrite names.forEach({ n -> println(n) }) in idiomatic form (lambda outside the parentheses, using it).
fun main() {
val names = listOf("Ada", "Linus")
names.forEach({ n -> println(n) }) // TODO: rewrite with a trailing lambda and it
} Show answer Hide answer
names.forEach { println(it) } map and filter
3. map to squares
Implement squares so it maps each number to its square — turning [1, 2, 3] into [1, 4, 9].
import org.junit.Test
import org.junit.Assert
class Test {
@Test fun squares() {
Assert.assertEquals("map each element to its square",
listOf(1, 4, 9), squares(listOf(1, 2, 3)))
}
}
//sampleStart
fun squares(nums: List<Int>): List<Int> =
nums // TODO: map each element to its square
//sampleEnd Show answer Hide answer
fun squares(nums: List<Int>) = nums.map { it * it } 4. map to lengths
Implement lengths to turn a list of words into a list of their lengths.
import org.junit.Test
import org.junit.Assert
class Test {
@Test fun lengths() {
Assert.assertEquals("map each word to its length",
listOf(2, 5, 3), lengths(listOf("hi", "there", "you")))
}
}
//sampleStart
fun lengths(words: List<String>): List<Int> =
emptyList() // TODO: map each word to its length
//sampleEnd Show answer Hide answer
fun lengths(words: List<String>) = words.map { it.length } 5. filter
Implement evens to keep only the even numbers.
import org.junit.Test
import org.junit.Assert
class Test {
@Test fun evens() {
Assert.assertEquals("keep only the even numbers",
listOf(2, 4, 6), evens(listOf(1, 2, 3, 4, 5, 6)))
}
}
//sampleStart
fun evens(nums: List<Int>): List<Int> =
emptyList() // TODO: keep only the even numbers
//sampleEnd Show answer Hide answer
fun evens(nums: List<Int>) = nums.filter { it % 2 == 0 } 6. Chain them
Implement evenSquares to keep the evens and then square them.
import org.junit.Test
import org.junit.Assert
class Test {
@Test fun evenSquares() {
Assert.assertEquals("keep the evens, then square them",
listOf(4, 16, 36), evenSquares(listOf(1, 2, 3, 4, 5, 6)))
}
}
//sampleStart
fun evenSquares(nums: List<Int>): List<Int> =
emptyList() // TODO: keep the evens, then square them
//sampleEnd Show answer Hide answer
fun evenSquares(nums: List<Int>) = nums.filter { it % 2 == 0 }.map { it * it } the rest of the toolkit
7. any
Implement hasNegative — true when any element is negative.
import org.junit.Test
import org.junit.Assert
class Test {
@Test fun any() {
Assert.assertEquals("true when an element is negative",
true, hasNegative(listOf(3, -1, 4)))
Assert.assertEquals("false when none are negative",
false, hasNegative(listOf(3, 1, 4)))
}
}
//sampleStart
fun hasNegative(nums: List<Int>): Boolean =
false // TODO: true when any element is negative
//sampleEnd Show answer Hide answer
fun hasNegative(nums: List<Int>) = nums.any { it < 0 } 8. all
Implement allPositive — true when every element is positive.
import org.junit.Test
import org.junit.Assert
class Test {
@Test fun all() {
Assert.assertEquals("true when all are positive",
true, allPositive(listOf(3, 1, 4)))
Assert.assertEquals("false when one is not",
false, allPositive(listOf(3, -1, 4)))
}
}
//sampleStart
fun allPositive(nums: List<Int>): Boolean =
false // TODO: true when all elements are positive
//sampleEnd Show answer Hide answer
fun allPositive(nums: List<Int>) = nums.all { it > 0 } 9. count
Implement longNames — how many names are longer than three characters.
import org.junit.Test
import org.junit.Assert
class Test {
@Test fun count() {
Assert.assertEquals("count names longer than three characters",
2, longNames(listOf("Ada", "Grace", "Alan", "Bo")))
}
}
//sampleStart
fun longNames(names: List<String>): Int =
0 // TODO: count names longer than three characters
//sampleEnd Show answer Hide answer
fun longNames(names: List<String>) = names.count { it.length > 3 } 10. find
Implement firstBig — the first element greater than 10, or null if there’s none.
import org.junit.Test
import org.junit.Assert
class Test {
@Test fun find() {
Assert.assertEquals("first element greater than 10",
12, firstBig(listOf(3, 8, 12, 20)))
Assert.assertEquals("null when there is none",
null, firstBig(listOf(1, 2, 3)))
}
}
//sampleStart
fun firstBig(nums: List<Int>): Int? =
null // TODO: first element greater than 10, or null
//sampleEnd Show answer Hide answer
fun firstBig(nums: List<Int>) = nums.find { it > 10 } 11. sumOf
Implement totalLength — the sum of the lengths of all the words.
import org.junit.Test
import org.junit.Assert
class Test {
@Test fun sumOf() {
Assert.assertEquals("sum the lengths of all words",
10, totalLength(listOf("hi", "there", "you")))
}
}
//sampleStart
fun totalLength(words: List<String>): Int =
0 // TODO: sum the lengths of all words
//sampleEnd Show answer Hide answer
fun totalLength(words: List<String>) = words.sumOf { it.length } your own higher-order function
12. Take a function as a parameter
Implement repeatTimes(n, action) so it calls action with 0, 1, … n-1, in order.
import org.junit.Test
import org.junit.Assert
class Test {
@Test fun repeats() {
val seen = mutableListOf<Int>()
repeatTimes(3) { seen.add(it) }
Assert.assertEquals("call action with 0, 1, ... n-1",
listOf(0, 1, 2), seen)
}
}
//sampleStart
fun repeatTimes(n: Int, action: (Int) -> Unit) {
// TODO: call action with 0, 1, ... n-1
}
//sampleEnd Show answer Hide answer
fun repeatTimes(n: Int, action: (Int) -> Unit) {
for (i in 0 until n) action(i)
} 13. Call it with a trailing lambda
Call your repeatTimes to print tick 0, tick 1, tick 2.
fun main() {
fun repeatTimes(n: Int, action: (Int) -> Unit) {
for (i in 0 until n) action(i)
}
// TODO: call repeatTimes to print "tick 0", "tick 1", "tick 2"
repeatTimes(3) { }
} Show answer Hide answer
repeatTimes(3) { println("tick $it") } Going deeper: fold and lazy sequences
12. Collapse to one value
Use fold to sum a list starting from 0 — total(listOf(1, 2, 3, 4)) is 10.
import org.junit.Test
import org.junit.Assert
class Test {
@Test fun total() {
Assert.assertEquals(10, total(listOf(1, 2, 3, 4)))
Assert.assertEquals(0, total(emptyList()))
}
}
//sampleStart
fun total(xs: List<Int>): Int =
0 // TODO: fold from 0, adding each element
//sampleEnd Show answer Hide answer
fun total(xs: List<Int>) = xs.fold(0) { acc, n -> acc + n }fold takes a seed and an (accumulator, element) lambda. Prefer it over reduce when the list might be empty (reduce throws) — though for a plain sum, xs.sum() beats both.
13. Why go lazy?
You chain .map { }.filter { }.first { } over a million-element list but only need the first match. What does adding .asSequence() at the front change? Reveal to check.
Show answer Hide answer
Without it, map runs a million times and allocates a full list, then filter allocates another, before first looks at anything. With .asSequence(), the chain is lazy: elements flow through one at a time and evaluation stops the instant first is satisfied — a few evaluations instead of a million, and no intermediate lists. Use sequences for long chains, large sources, or an early exit; stick with eager operations for short collections.
Back to the lesson, Lambdas at Work, or on to part three: closures.
Comments