Collections

Overview

A collection is a finite group of some variable number of items (possibly zero) of the same type. Objects in a collection are called elements.

Collections in Kotlin are contained in the kotlin.collections package, which is part of the Kotlin Standard Library.

These collection classes exists as generic containers for a group of elements of the same type e.g. List would be an ordered list of integers. Collections have a finite size, and are eagerly evaluated.

Kotlin offers functional processing operations (e.g. filter, map and so on) on each of these collections.

fun main() {
  val list = (1..10).toList() // generate list of 1..10
  println( list.take(5).map{it * it} ) // square the first 5 elements
}

Under-the-hood, Kotlin uses Java collection classes, but provides mutable and immutable interfaces to these classes. Kotlin best-practice is to use immutable for read-only collections whenever possible (since mutating collections is often very costly in performance).

Collection Classes

Collection Class Description
Pair A tuple1 of two values.
Triple A tuple of three values.
List An ordered collection of objects.
Set An unordered collection of objects.
Map An associative dictionary of keys and values.
Array An indexed, fixed-size collection of objects.

Pair

A Pair is a tuple of two values. Use var or val to indicate mutability. Theto keyword can be used to indicate a Pair.

fun main() {
  // mutable 
  var nova_scotia = "Halifax Airport" to "YHZ" 
  var newfoundland = Pair("Gander Airport", "YQX") 
  var ontario = Pair("Toronto Pearson", "YYZ") 
  ontario = Pair("Billy Bishop", "YTZ") // reassignment is ok

  // immutable, mixed types
  val canadian_exchange = Pair("CDN", 1.38) 

  // accessing elements
  val characters = Pair("Tom", "Jerry") 
  println(characters.first) 
  println(characters.second) 

  // destructuring
  val (first, second) = Pair("Calvin", "Hobbes") // split a Pair
  println(first)
  println(second)
}

Pairs are extremely useful when working with data that is logically grouped into tuples, but where you don’t need the overhead of a custom class. e.g. Pair for 2D points.

List

A List is an ordered collection of objects.

fun main() {
  // define an immutable list
  var fruits = listOf( "advocado", "banana") 
  println(fruits.get(0))
  // advocado

  // add elements
  var mfruits = mutableListOf( "advocado", "banana") 
  mfruits.add("cantaloupe")
  mfruits.forEach { println(it) }

  // sorted/sortedBy returns ordered collection 
  val list = listOf(2,3,1,4).sorted() // [1, 2, 3, 4] 
  list.sortedBy { it % 2 } // [2, 4, 1, 3] 

  // groupBy groups elements on collection by key 
  list.groupBy { it % 2 } // Map: {1=[1, 3], 0=[2, 4]} 

  // distinct/distinctBy returns unique elements 
  listOf(1,1,2,2).distinct() // [1, 2] 
}

Set

A Set is a generic unordered collection of unique elements (i.e. it does not support duplicates, unlike a List which does). Sets are commonly constructed with helper functions:

val numbersSet = setOf("one", "two", "three", "four")
val emptySet = mutableSetOf<String>()

A Map is an associative dictionary containing Pairs of keys and values.

fun main() {
  // immutable reference, immutable map
  val imap = mapOf(Pair(1, "a"), Pair(2, "b"), Pair(3, "c")) 
  println(imap)
  // {1=a, 2=b, 3=c}

  // immutable reference, mutable map (so contents can change)
  val mmap = mutableMapOf(5 to "d", 6 to "e") 
  mmap.put(7,"f")
  println(mmap) 
  // {5=d, 6=e, 7=f}

  // lookup a value 
  println(mmap.get(5)) 
  // d

  // iterate over key and value
  for ((k, v) in imap) { 
    print("$k=$v ") 
  } 
  // 1=a 2=b 3=c

  // alternate syntax
  imap.forEach { k, v -> print("$k=$v ") }
  // 1=a 2=b 3=c

  // `it` represents an implicit iterator
  imap.forEach {
    print("${it.key}=${it.value} ")
  }
  // 1=a 2=b 3=c
}

Array

Arrays are indexed, fixed-sized collection of objects and primitives. We prefer other collections, but these are offered for legacy and compatibility with Java.

fun main() {
  // Create using the `arrayOf()` library function
  arrayOf(1, 2, 3)

	// Create using the Array class constructor 
	// Array<String> ["0", "1", "4", "9", "16"] 
	val asc = Array(5) { 
		i -> (i*i).toString()
	} 
	asc.forEach { println(it) } 
}

You can access array elements through using the [] operators, or the get() and set() methods.

Collection Functions

Collection classes (e.g. List, Set, Map, Array) have built-in functions for working with the data that they contain. These functions frequently accept other functions as parameters.

Filter

filter produces a new list of those elements that return true from a predicate function.

val list = (1..100).toList()
val filtered = list.filter { it % 5 == 0 }
// 5 10 15 20 ... 100

val below50 = filtered.filter { it in 0..49 }
// [5, 10, 15, 20]

Map

map produces a new list that is the results of applying a function to every element that it contains.

val list = (1..100).toList()
val doubled = list.map { it * 2 }
// 2 4 6 8 ... 200

Reduce

reduce accumulates values starting with the first element and applying an operation to each element from left to right.

val strings = listOf("a", "b", "c", "d")
println(strings.reduce { acc, string -> acc + string }) // abcd

Zip

zip combines two collections together, associating their respective pairwise elements.

val foods = listOf("apple", "kiwi", "broccoli", "carrots")
val fruit = listOf(true, true, false, false)

// List<Pair<String, Boolean>>
val results = foods.zip(fruit)
// [(apple, true), (kiwi, true), (broccoli, false), (carrots, false)]

A more realistic scenario might be where you want to generate a pair based on the results of the list elements:

val list = listOf("123", "", "456", "def")
val exists = list.zip(list.map { !it.isBlank() })
// [(123, true), (, false), (456, true), (def, true)]

val numeric = list.zip(list.map { !it.isEmpty() && it[0] in ('0'..'9') })
[(123, true), (, false), (456, true), (def, false)]

ForEach

forEach calls a function for every element in the collection.

val fruits = listOf("advocado", "banana", "cantaloupe" )
fruits.forEach { print("$it ") }
// advocado banana cantaloupe 

We also have helper functions to extract specific elements from a list.

Take

take returns a collection containing just the first n elements. drop returns a new collection with the first n elements removed.

val list = (1..50)
val first10 = list.take(10) 
// 1 2 3 ... 10
val last40 = list.drop(10) 
// 11 12 13 ... 50

First, Last, Slice

first and last return those respective elements. slice allows us to extract a range of elements into a new collection.

val list = (1..50)
val even = list.filter { it % 2 == 0 }
// 2 4 6 8 10 ... 50

even.first()	// 2
even.last() // 50
even.slice(1..3) // 4 6 8

  1. A tuple is a data structure representing a sequence of n elements. ↩︎