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.

Try it — edit, then press Run 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).

Try it — edit, then press Run 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].

Implement it, then press Run to check 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.

Implement it, then press Run to check 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.

Implement it, then press Run to check 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.

Implement it, then press Run to check 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.

Implement it, then press Run to check 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.

Implement it, then press Run to check 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.

Implement it, then press Run to check 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.

Implement it, then press Run to check 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.

Implement it, then press Run to check 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.

Implement it, then press Run to check 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.

Try it — edit, then press Run 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 0total(listOf(1, 2, 3, 4)) is 10.

Implement it, then press Run to check 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