Chapter 6: Working with collections and sequences
Introduces functional collection processing as a common use of lambdas and previews sequences as an alternative for applying collection operations.
After this chapter you will be able to
- Lambdas in collection processing. Explain that working with collections is a common use for lambdas, and that combining lambdas with standard library functions can make collection code more expressive and concise.
- Functional collection operation benefits. Explain how standard-library functions customized with lambdas provide a consistent way to express common collection operations instead of navigating and aggregating collections manually.
- Filtering versus mapping collections. Distinguish filter from map by describing what each lambda does and what the resulting collection contains.
- Reduce and fold accumulation. Explain how reduce and fold use a lambda and accumulator to aggregate a collection, and distinguish how each initializes the accumulator.
- Collection predicate checks. Distinguish how all, any, and none test a collection against a predicate, and explain how to express negated checks more clearly.
- Empty collection predicate results. Explain what any, none, and all return on an empty collection and why all can return true.
- Predicate Based Collection Partitioning. Explain how partition divides a collection into elements that satisfy a Boolean predicate and elements that do not, and why it is useful when both groups are needed.
- Grouping elements by a key. Explain how groupBy groups collection elements by a supplied quality and what its result contains, using people grouped by age as an example.
- Key-value pairs with associate. Explain how associate turns collection elements into map entries by applying a lambda that returns a key-value pair for each element. Describe the example that maps people’s names to their ages.
- Original elements as keys or values. Distinguish associateWith from associateBy by stating which uses the original collection elements as map keys and which uses them as values. Describe what each lambda generates.
- Mutable list replacement functions. Distinguish how replaceAll and fill change a MutableList: replaceAll computes each replacement with a lambda, while fill sets every element to the same value.
- Default for empty collections. Explain when to use ifEmpty and what its lambda provides when a collection has no elements.
- Sliding windows with windowed. Explain how windowed groups consecutive values into sliding windows and how a lambda can transform each window, such as by calculating its average.
- Nonoverlapping chunks with chunked. Describe how chunked divides a collection into distinct parts of a specified size, including what happens when items remain, and how a lambda can transform the output.
- Index-aligned collection merging. Explain how zip combines two collections by matching elements at the same index, producing pairs by default or transformed values when given a lambda. State what happens when the collections have different lengths.
- FlatMap for book authors. Explain why mapping books to their author lists produces a nested list, and how flatMap produces one flat list of authors. Describe why the example then calls toSet.
- Eager and lazy operation chains. Explain why a chain of map and filter calls on a collection creates intermediate lists, while the same operations on a sequence do not, and why that difference matters for large collections.
- Intermediate and terminal operations. Distinguish intermediate sequence operations from terminal operations and explain why a chain of map and filter calls does not perform its transformations until a terminal operation such as toList is called.
- Elementwise lazy processing. Trace how a sequence processes map and filter or find one element at a time, and contrast that with a collection's eager processing. Explain why find can leave later elements untransformed.
- Generated number sequences. Explain how generateSequence produces elements from a starting value and the previous element, using the natural-numbers example to describe how takeWhile limits the sequence and sum triggers its evaluation.
- Lazy parent directory traversal. Describe how a sequence of parent directories can be generated and checked for a hidden directory, including what any and find return and when traversal can stop.
What it builds on
- Lambdas and collections (chapter 5) · Introduces using lambdas with collections.
- Iterating over maps (chapter 2) · The distinction between a map's keys and values.
- Member references (chapter 5) · The syntax and idea of member references.
- Working with pairs: Infix calls and destructuring declarations (chapter 3) · The infix to function used to specify key-value pairs.
Section by section
Introduction
Introduces functional collection processing as a common use of lambdas and previews sequences as an alternative for applying collection operations.
- Lambdas in collection processing: Explain that working with collections is a common use for lambdas, and that combining lambdas with standard library functions can make collection code more expressive and concise.
Why it matters: This gives you the reason for using lambdas and library functions when working with collections.
Key terms: lambdas, collections, standard library functions
6.1 Functional APIs for collections
Introduces why Kotlin’s lambda-customized standard-library functions are useful for working with collections.
- Functional collection operation benefits: Explain how standard-library functions customized with lambdas provide a consistent way to express common collection operations instead of navigating and aggregating collections manually.
Why it matters: This explains why the chapter uses collection functions rather than starting with manual loops.
Key terms: standard library, lambdas, functional programming style
6.1.1 Removing and transforming elements: filter and map
This section explains how to select and transform collection elements with filter and map, combine those operations, and use indexed and map-specific variants.
- Filtering versus mapping collections: Distinguish filter from map by describing what each lambda does and what the resulting collection contains.
Why it matters: This distinction helps you choose whether to keep certain entries or change every entry. - Chained filtering and mapping: Describe how to combine filter and map to get the names of people older than 30.
Why it matters: Chaining expresses selection and transformation as one readable operation. - Repeated calculations inside lambdas: Explain why finding the maximum age inside a filter lambda is wasteful and how the section improves it.
Why it matters: A short lambda can hide repeated work, so its placement matters. - Indexed and map variants: Describe when to use filterIndexed or mapIndexed and distinguish the key and value variants for maps.
Why it matters: These variants let you choose the part of a collection that the operation needs to inspect or change.
Key terms: filter, map, predicate, lambda, Person, maximum age, filterIndexed, mapIndexed, filterKeys, mapKeys
6.1.2 Accumulating values for collections: reduce and fold
Explains how reduce and fold accumulate collection elements into one value, and how their running counterparts retain intermediate results.
- Reduce and fold accumulation: Explain how reduce and fold use a lambda and accumulator to aggregate a collection, and distinguish how each initializes the accumulator.
Why it matters: The starting value determines which operation fits an aggregation and whether an empty collection is a problem. - Running accumulator results: Describe what runningReduce and runningFold return compared with reduce and fold.
Why it matters: The running variants are useful when the values produced along the way matter, not just the final aggregate.
Key terms: reduce, fold, accumulator, lambda, runningReduce, runningFold, intermittent accumulator values
6.1.3 Applying a predicate to a collection: all, any, none, count, and find
This subsection explains how to test, count, and find collection elements with predicates, including the behavior of predicate checks on empty collections.
- Collection predicate checks: Distinguish how all, any, and none test a collection against a predicate, and explain how to express negated checks more clearly.
Why it matters: Choosing the right predicate check makes the condition easier to read. - Empty collection predicate results: Explain what any, none, and all return on an empty collection and why all can return true.
Why it matters: Empty collections are a common edge case, and these results determine whether a condition passes. - Counting matches without filtering: Explain why count is preferable to filtering a collection and taking its size when only the number of matches is needed.
Why it matters: Using count avoids storing matching elements when you only need their number. - First matching element lookup: Describe what find returns when a predicate matches one or more elements, and when it matches none.
Why it matters: The result tells you both which match was found first and whether any match exists.
Key terms: predicate, all, any, none, empty collection, vacuous truth, count, filter, size, intermediate collection
6.1.4 Splitting a list into a pair of lists: partition
This section shows how partition splits a collection into two lists based on a Boolean predicate, more concisely than using filter and filterNot.
- Predicate Based Collection Partitioning: Explain how partition divides a collection into elements that satisfy a Boolean predicate and elements that do not, and why it is useful when both groups are needed.
Why it matters: partition gives you both sides of a condition in one concise operation.
Key terms: partition, Boolean predicate, filter, filterNot, pair of lists
6.1.5 Converting a list to a map of groups: groupBy
This section shows how groupBy divides a collection into multiple groups and returns those groups as a map.
- Grouping elements by a key: Explain how groupBy groups collection elements by a supplied quality and what its result contains, using people grouped by age as an example.
Why it matters: It lets you organize elements into more than the two groups produced by partition. - Extension function member references: Describe how the section groups strings by their first character using a member reference, despite first being an extension rather than a String member.
Why it matters: It shows that a grouping key can be supplied through a reference to an extension.
Key terms: groupBy, map, key, Map<Int, List<Person>>, first, extension, member reference
6.1.6 Transforming collections into maps: associate, associateWith, and associateBy
This section explains how to turn a collection into a map with associate, associateWith, or associateBy, and what happens when generated keys repeat.
- Key-value pairs with associate: Explain how associate turns collection elements into map entries by applying a lambda that returns a key-value pair for each element. Describe the example that maps people’s names to their ages.
Why it matters: This lets you choose the keys and values when building a map from a collection. - Original elements as keys or values: Distinguish associateWith from associateBy by stating which uses the original collection elements as map keys and which uses them as values. Describe what each lambda generates.
Why it matters: The distinction determines which side of the resulting map contains the original elements. - Duplicate map key behavior: Explain what happens if associate, associateWith, or associateBy generates the same map key more than once.
Why it matters: A repeated key can cause an earlier map entry to be replaced.
Key terms: associate, key-value pair, to, associateWith, associateBy, keys, overwrites
6.1.7 Replacing elements in mutable collections: replaceAll and fill
This section explains two Kotlin functions for changing the elements of a mutable list: replaceAll and fill.
- Mutable list replacement functions: Distinguish how replaceAll and fill change a MutableList: replaceAll computes each replacement with a lambda, while fill sets every element to the same value.
Why it matters: The distinction helps you choose between computing a replacement for each element and assigning one value throughout a list.
Key terms: MutableList, replaceAll, fill, lambda
6.1.8 Handling special cases for collections: ifEmpty
Introduces defaults for empty collections and for strings that are empty or contain only whitespace.
- Default for empty collections: Explain when to use ifEmpty and what its lambda provides when a collection has no elements.
Why it matters: It gives your program a fallback when there are no collection elements to process. - Blank string fallback: Distinguish ifBlank from ifEmpty by describing why whitespace-only strings can also warrant a default value.
Why it matters: It helps you handle text that contains characters but still carries no useful content.
Key terms: ifEmpty, lambda, default value, ifBlank, whitespace
6.1.9 Splitting collections: chunked and windowed
This section shows how to process consecutive collection elements with overlapping windows or nonoverlapping chunks.
- Sliding windows with windowed: Explain how windowed groups consecutive values into sliding windows and how a lambda can transform each window, such as by calculating its average.
Why it matters: Sliding windows let you calculate a result for each consecutive group of values. - Nonoverlapping chunks with chunked: Describe how chunked divides a collection into distinct parts of a specified size, including what happens when items remain, and how a lambda can transform the output.
Why it matters: Chunks let you process distinct batches without discarding leftover items.
Key terms: windowed, sliding window, chunked, chunk
6.1.10 Merging collections: zip
Explains how zip combines corresponding elements of collections, including its output size, transformation option, and behavior when chained.
- Index-aligned collection merging: Explain how zip combines two collections by matching elements at the same index, producing pairs by default or transformed values when given a lambda. State what happens when the collections have different lengths.
Why it matters: This lets you combine related lists without assuming every element in the longer list has a match. - Infix and chained zip results: Describe how zip can be called with infix syntax and what changes when multiple zip calls are chained.
Why it matters: The call style and nested result shape affect how you use the combined data.
Key terms: zip, lambda, pairs, infix function, nested pairs
6.1.11 Processing elements in nested collections: flatMap and flatten
This section shows how to turn nested collections into a flat result with flatMap or flatten and encourages looking for standard library transformations.
- FlatMap for book authors: Explain why mapping books to their author lists produces a nested list, and how flatMap produces one flat list of authors. Describe why the example then calls toSet.
Why it matters: It helps you get one list of results when each input item contains multiple values. - Flatten without transformation: Distinguish flatten from flatMap when combining a collection of collections.
Why it matters: The distinction helps you choose the simpler operation when no transformation is needed. - Standard library transformation search: Describe the section’s advice for finding a collection operation instead of implementing one manually.
Why it matters: A suitable library function can solve a collection task faster than a manual implementation.
Key terms: map, flatMap, toSet, nested collection, flatten, general transformation, Kotlin standard library, IDE’s autocomplete suggestions
6.2 Lazy collection operations: Sequences
This section contrasts eager collection chains with lazy sequences and explains when to convert between sequences and lists.
- Eager and lazy operation chains: Explain why a chain of map and filter calls on a collection creates intermediate lists, while the same operations on a sequence do not, and why that difference matters for large collections.
Why it matters: For a large collection, avoiding intermediate lists can make a chain of operations more efficient. - Sequence conversion and use: Describe how to convert a collection to a sequence and back, and explain when a sequence can be used directly versus when a list is needed.
Why it matters: Choosing whether to keep a sequence or convert it to a list depends on how you need to use the result.
Key terms: eagerly, intermediate collections, Sequence, lazily, map, filter, iterator, asSequence, toList
6.2.1 Executing sequence operations: Intermediate and terminal operations
This section explains when sequence operations run, how they process elements, and why operation order can affect performance.
- Intermediate and terminal operations: Distinguish intermediate sequence operations from terminal operations and explain why a chain of map and filter calls does not perform its transformations until a terminal operation such as toList is called.
Why it matters: It explains why constructing a sequence pipeline does not immediately produce transformed values. - Elementwise lazy processing: Trace how a sequence processes map and filter or find one element at a time, and contrast that with a collection's eager processing. Explain why find can leave later elements untransformed.
Why it matters: Elementwise processing explains where a sequence can avoid unnecessary transformations. - Early filtering for performance: Explain why filtering people before mapping them to names can require fewer transformations than mapping first, provided both orders preserve the intended result.
Why it matters: Choosing an equivalent operation order can reduce unnecessary work.
Key terms: intermediate operation, terminal operation, sequence, toList, lazy evaluation, eager evaluation, map, find, filter, chain of operations
6.2.2 Creating sequences
This section shows how to create sequences from a starting element and a rule for producing the next one, then use them to process numbers or traverse parent directories lazily.
- Generated number sequences: Explain how generateSequence produces elements from a starting value and the previous element, using the natural-numbers example to describe how takeWhile limits the sequence and sum triggers its evaluation.
Why it matters: You can describe a potentially infinite series and calculate only the part needed for a result. - Lazy parent directory traversal: Describe how a sequence of parent directories can be generated and checked for a hidden directory, including what any and find return and when traversal can stop.
Why it matters: A parent chain can be searched without visiting directories beyond the first match.
Key terms: generateSequence, takeWhile, terminal operation, postponed computation, parent directories, any, find
6.3 Summary
Where this is used later
- Making possibly null variables explicit with nullable types (chapter 7) · The possible null result provides a concrete case for working with a nullable value
- Using inlined lambdas for resource management with withLock, use, and useLines (chapter 10) · Sequences as a way to work with values.
- Inlining collection operations (chapter 10) · Introduces intermediate and terminal sequence operations.
- Creating a cold flow with the flow builder function (chapter 16) · The distinction between defining lazy operations and executing them.
- Manipulating flows with flow operators (chapter 17) · The analogous distinction between intermediate and terminal sequence operations.
Concepts explained in this chapter
- sequenceA sequence is an ordered computation that produces elements, potentially lazily and without requiring all elements to be stored or computed immediately.
- lazy evaluationLazy evaluation postpones computation until a result is required and can stop processing once that result is obtained.
- data transformationmap is a Kotlin standard-library operation that transforms each element and collects the resulting values into a new collection.
- filteringFiltering is the operation of selecting or excluding items from data or a collection according to specified criteria.
- predicateA predicate function is a function that evaluates an input and returns a Boolean indicating whether it satisfies a condition.
- terminal operationA terminal operation is a sequence operation that produces a final result and triggers evaluation of the preceding transformations.
- anyany is a Kotlin standard-library operation that returns true when at least one element in a collection satisfies a predicate.
- lambdaA function literal is source syntax that directly expresses a function value, including its parameters and body.
- intermediate operationAn intermediate operator is a Flow transformation that collects values from an upstream flow and emits transformed or otherwise produced values to a downstream flow.
- MapHashMap is a hash-table-based Java map implementation that stores key-value associations and uses key hash codes for lookup.
- collectionA collection is a container or aggregate that holds multiple elements and supports operations such as membership testing.
- functional, stateless philosophyFunctional programming treats computation primarily as the evaluation and composition of functions, often emphasizing immutable data and limited side effects.
- Kotlin standard libraryA library containing runtime definitions required by compiled Kotlin programs, including fundamental Kotlin classes and extensions to Java APIs.
- listMutableList is a Kotlin mutable collection type whose elements can be read and modified through its invariant type parameter.
- immutable collectionImmutability is the property that an object's state cannot change after the object is created.
These guides are generated from the text of Manning books by Manning's knowledge graph and link to the sections they draw on. The full books are available on liveBook and at manning.com.
