· 9 min read

Functional Android (II): Collection operations in Kotlin

Lambdas are a great powerful tool to simplify code, but also to do things that were not possible before. We talked about them in the [first part of the series](Unleash functional power on Android (I): Kotlin lambdas).

In the end, lambdas are the basis to implement lots of functional features, such as the ones we are talking today: Collection operations. Kotlin provides an awesome set of operations that wouldn’t be possible (or really verbose) when using a language which doesn’t support lambdas.

This article is not Android specific, but it will boost Android Apps development in many different ways. Today I’ll be talking about the different types of collections Kotlin provides, as well as the available operations over them.

Collections

Although we can just use Java collections, Kotlin provides a good set of native interfaces you will want to use:

  • Iterable: The parent class. Any classes that inherit from this interface represent a sequence of elements we can iterate over.
  • MutableIterable: Iterables that support removing items during iteration.
  • Collection: This class represents a generic collection of elements. We get access to functions that return the size of the collection, whether the collection is empty, contains an item or a set of items. All the methods for this kind of collections are only to request data, because collections are immutable.
  • MutableCollection: a Collection that supports adding and removing elements. It provides extra functions such as add, remove or clear among others.
  • List: Probably the most used collection. It represents a generic ordered collection of elements. As it’s ordered, we can request an item by its position, using the get function.
  • MutableList: a List that supports adding and removing elements.
  • Set: an unordered collection of elements that doesn’t support duplicate elements.
  • MutableSet: a Set that supports adding and removing elements.
  • Map: a collection of key-value pairs. The keys in a map are unique, which means we cannot have two pairs with the same key in a map.
  • MutableMap: a Map that supports adding and removing elements.

Collection Operations

This is the set of functional operations we have available over the different collections. I want to show you a little definition and example. It is useful to know what the options are, because that way it’s easier to identify where these functions can be used. Please let me know if you miss any function from the standard library.

All this content and much more can be found in Kotlin for Android Developers book.

Kotlin for Android Developers

18.1 Aggregate operations

any

Returns true if at least one element matches the given predicate.

val list = listOf(1, 2, 3, 4, 5, 6) 
assertTrue(list.any { it % 2 == 0 }) 
assertFalse(list.any { it > 10 }) 

all

Returns true if all the elements match the given predicate.

assertTrue(list.all { it < 10 }) 
assertFalse(list.all { it % 2 == 0 }) 

count

Returns the number of elements matching the given predicate.

assertEquals(3, list.count { it % 2 == 0 }) 

fold

Accumulates the value starting with an initial value and applying an operation from the first to the last element in a collection.

assertEquals(25, list.fold(4) { total, next -> total + next }) 

foldRight

Same as fold, but it goes from the last element to first.

assertEquals(25, list.foldRight(4) { total, next -> total + next }) 

forEach

Performs the given operation to each element.

list.forEach { println(it) } 

forEachIndexed

Same as forEach, though we also get the index of the element.

list.forEachIndexed { index, value -> println("position $index contains a $value") } 

max

Returns the largest element or null if there are no elements.

assertEquals(6, list.max()) 

maxBy

Returns the first element yielding the largest value of the given function or null if there are no elements.

// The element whose negative is greater 
assertEquals(1, list.maxBy { -it }) 

min

Returns the smallest element or null if there are no elements.

assertEquals(1, list.min()) 

minBy

Returns the first element yielding the smallest value of the given function or null if there are no elements.

// The element whose negative is smaller 
assertEquals(6, list.minBy { -it }) 

none

Returns true if no elements match the given predicate.

// No elements are divisible by 7 
assertTrue(list.none { it % 7 == 0 }) 

reduce

Same as fold, but it doesn’t use an initial value. It accumulates the value applying an operation from the first to the last element in a collection.

assertEquals(21, list.reduce { total, next -> total + next }) 

reduceRight

Same as reduce, but it goes from the last element to first.

assertEquals(21, list.reduceRight { total, next -> total + next }) 

sumBy

Returns the sum of all values produced by the transform function from the elements in the collection.

assertEquals(3, list.sumBy { it % 2 }) 

18.2 Filtering operations

drop

Returns a list containing all elements except first n elements.

assertEquals(listOf(5, 6), list.drop(4)) 

dropWhile

Returns a list containing all elements except first elements that satisfy the given predicate.

assertEquals(listOf(3, 4, 5, 6), list.dropWhile { it < 3 }) 

dropLastWhile

Returns a list containing all elements except last elements that satisfy the given predicate.

assertEquals(listOf(1, 2, 3, 4), list.dropLastWhile { it > 4 }) 

filter

Returns a list containing all elements matching the given predicate.

assertEquals(listOf(2, 4, 6), list.filter { it % 2 == 0 }) 

filterNot

Returns a list containing all elements not matching the given predicate.

assertEquals(listOf(1, 3, 5), list.filterNot { it % 2 == 0 }) 

filterNotNull

Returns a list containing all elements that are not null.

assertEquals(listOf(1, 2, 3, 4), listWithNull.filterNotNull()) 

slice

Returns a list containing elements at specified indices.

assertEquals(listOf(2, 4, 5), list.slice(listOf(1, 3, 4))) 

take

Returns a list containing first n elements.

assertEquals(listOf(1, 2), list.take(2)) 

takeLast

Returns a list containing last n elements.

assertEquals(listOf(5, 6), list.takeLast(2)) 

takeWhile

Returns a list containing first elements satisfying the given predicate.

assertEquals(listOf(1, 2), list.takeWhile { it < 3 }) 

18.3 Mapping operations

flatMap

Iterates over the elements creating a new collection for each one, and finally flattens all the collections into a unique list containing all the elements.

assertEquals(listOf(1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7), list.flatMap { listOf(it, it + 1) }) 

groupBy

Returns a map of the elements in original collection grouped by the result of given function

assertEquals(mapOf("odd" to listOf(1, 3, 5), "even" to listOf(2, 4, 6)), list.groupBy { if (it % 2 == 0) "even" else "odd" }) 

map

Returns a list containing the results of applying the given transform function to each element of the original collection.

assertEquals(listOf(2, 4, 6, 8, 10, 12), list.map { it * 2 }) 

mapIndexed

Returns a list containing the results of applying the given transform function to each element and its index of the original collection.

assertEquals(listOf (0, 2, 6, 12, 20, 30), list.mapIndexed { index, it -> index * it }) 

mapNotNull

Returns a list containing the non-null results of applying the given transform function to each element of the original collection.

assertEquals(listOf(2, 4, 6, 8), listWithNull.mapNotNull { it * 2 }) 

18.4 Elements operations

contains

Returns true if the element is found in the collection.

assertTrue(list.contains(2)) 

elementAt

Returns an element at the given index or throws an IndexOutOfBoundsException if the index is out of bounds of this collection.

assertEquals(2, list.elementAt(1)) 

elementAtOrElse

Returns an element at the given index or the result of calling the default function if the index is out of bounds of this collection.

assertEquals(20, list.elementAtOrElse(10, { 2 * it })) 

elementAtOrNull

Returns an element at the given index or null if the index is out of bounds of this collection.

assertNull(list.elementAtOrNull(10)) 

first

Returns the first element matching the given predicate

assertEquals(2, list.first { it % 2 == 0 }) 

firstOrNull

Returns the first element matching the given predicate, or null if no element was found.

assertNull(list.firstOrNull { it % 7 == 0 }) 

indexOf

Returns the first index of element, or -1 if the collection does not contain element.

assertEquals(3, list.indexOf(4)) 

indexOfFirst

Returns index of the first element matching the given predicate, or -1 if the collection does not contain such element.

assertEquals(1, list.indexOfFirst { it % 2 == 0 }) 

indexOfLast

Returns index of the last element matching the given predicate, or -1 if the collection does not contain such element.

assertEquals(5, list.indexOfLast { it % 2 == 0 }) 

last

Returns the last element matching the given predicate.

assertEquals(6, list.last { it % 2 == 0 }) 

lastIndexOf

Returns last index of element, or -1 if the collection does not contain element.

val listRepeated = listOf(2, 2, 3, 4, 5, 5, 6) 
assertEquals(5, listRepeated.lastIndexOf(5)) 

lastOrNull

Returns the last element matching the given predicate, or null if no such element was found.

val list = listOf(1, 2, 3, 4, 5, 6) 
assertNull(list.lastOrNull { it % 7 == 0 }) 

single

Returns the single element matching the given predicate, or throws exception if there is no or more than one matching element.

assertEquals(5, list.single { it % 5 == 0 }) 

singleOrNull

Returns the single element matching the given predicate, or null if element was not found or more than one element was found.

assertNull(list.singleOrNull { it % 7 == 0 }) 

18.5 Generation operations

merge

Returns a list of values built from elements of both collections with same indexes using the provided transform function. The list has the length of shortest collection.

val list = listOf(1, 2, 3, 4, 5, 6) 
val listRepeated = listOf(2, 2, 3, 4, 5, 5, 6) 
assertEquals(listOf(3, 4, 6, 8, 10, 11), list.merge(listRepeated) { it1, it2 -> it1 + it2 }) 

partition

Splits original collection into pair of collections, where the first collection contains elements for which the predicate returned true, while the second collection contains elements for which the predicate returned false.

assertEquals(Pair(listOf(2, 4, 6), listOf(1, 3, 5)), list.partition { it % 2 == 0 }) 

plus

Returns a list containing all elements of the original collection and then all elements of the given collection. Because of the name of the function, we can use the ’+’ operator with it.

assertEquals(listOf(1, 2, 3, 4, 5, 6, 7, 8), list + listOf(7, 8)) 

zip

Returns a list of pairs built from the elements of both collections with the same indexes. The list has the length of the shortest collection.

assertEquals(listOf(Pair(1, 7), Pair(2, 8)), list.zip(listOf(7, 8)) ) 

18.6 Ordering operations

reverse

Returns a list with elements in reversed order.

val unsortedList = listOf(3, 2, 7, 5) 
assertEquals(listOf(5, 7, 2, 3), unsortedList.reverse()) 

sort

Returns a sorted list of all elements.

assertEquals(listOf(2, 3, 5, 7), unsortedList.sort()) 

sortBy

Returns a list of all elements, sorted by the specified comparator.

assertEquals(listOf(3, 7, 2, 5), unsortedList.sortBy { it % 3 }) 

sortDescending

Returns a sorted list of all elements, in descending order.

assertEquals(listOf(7, 5, 3, 2), unsortedList.sortDescending()) 

sortDescendingBy

Returns a sorted list of all elements, in descending order by the results of the specified order function.

assertEquals(listOf(2, 5, 7, 3), unsortedList.sortDescendingBy { it % 3 }) 
    Share:
    Back to Blog