Kotlin Collections — II → Collection Operations(2/3)

Muhammet Küdür
8 min readOct 13, 2023

--

Hi folks! I’m back with the second part of my “Kotlin Collections” series. Before we get started, if you are unfamiliar with the topic and haven’t read the first article yet, I recommend you to take a look at the first one.

“Kotlin Collection Operations” refers to a set of functions provided in the Kotlin standard library that make working with collections (List, Set, Map, etc.) easier. These functions help us perform various operations such as filtering, transforming, sorting, and more on collection elements without the need for loops or iterative processes. This provides an opportunity to write simpler, more readable code instead of complex code blocks that span multiple lines.

There are numerous functions in the Kotlin Standard Library specifically designed for collections. I will try to cover as many examples as possible with various use cases.

Transformation

“Transformation” operations in Kotlin collections allow you to process collection elements to create new collections. For example, you can create a new collection by applying a specific operation to the elements. These operations are used for transforming or processing data.

Map:

It processes each element of the collection through the specified operation and returns the result of the operation as a new collection.

-mapNotNull:If the processed element is null, it will not include it in the operation and, as a result, returns the non-null elements.

-mapIndexed: If you want to process elements in the collection along with their indices, you can use this function.

fun main() {
val names = listOf("Muhammet", "Umutcan", "Sevban", "Mehmet")
println(names.map { it.take(1) }) // [M, U, S, M]
println(names) // "Muhammet", "Umutcan", "Sevban", "Mehmet"

class Human(val name: String) {
override fun toString(): String {
return "Humann"
}
}

val random = listOf(8, null, "Umutcan", 14.5f, Human("Muhammet"), false, 52)
println(random.mapNotNull { if (it is Human) it else null }) // Humann

val colors = listOf("Red", "Green", "Blue")
println(colors.mapIndexed { index, s -> "${index.plus(1)} -> ${s.take(1)}" })
// [1 -> R, 2 -> G, 3 -> B]
}

Zip:

It’s a transformation operation that combines two different collections to create pairs. The first element of the first collection is paired with the first element of the second collection, and the other elements at the same positions are similarly paired. As a result, the elements of both collections are merged based on matching positions. The return type is List<Pair<Type, Type>>.

val kitchenTools = listOf("fork", "spoon", "knife")
val quantities = listOf(100, 60, 35)
val result: List<Pair<String, Int>> = kitchenTools.zip(quantities)
println(result) // [(fork, 100), (spoon, 60), (knife, 35)]

If one of the two collections is smaller in size than the other, the zip operation is performed based on the smaller one.

val colors = listOf("Red", "Green", "Blue", "Yellow")
val items = listOf("Hat", "Car")
println(colors zip items) // [(Red, Hat), (Green, Car)]

The zip function provides us with elements from two lists through a lambda expression, and we can perform the desired operation on them.

val secondItems = listOf("Phone", "Book")
val prices = listOf(9000, 120)
println(secondItems.zip(prices) { item, price ->
"$item's Price is $price TL"
})
// [Phone's Price is 9000 TL, Book's Price is 120 TL]
  • unzip: We can split a list given as pairs using ‘unzip.’
val itemsPair = listOf("Red" to "Hat", "Green" to "Car", "Blue" to "Toy")
val (itemColor, item) = itemsPair.unzip()
println(itemColor) // [Red, Green, Blue]
println(item) // [Hat, Car, Toy]

Associate:

It allows you to match elements within the collection according to a specific rule, creating a new map. As a result of this operation, you obtain a ‘map’ containing key-value pairs for each item within the collection. The return value is of type Map<Type, Type>.

-associateWith: It takes the value within the collection as the key and performs the ‘map’ operation based on the operation within the lambda expression.

val animals = listOf("Elephant", "Giraffe", "Lion", "Tiger")
println(animals.associateWith { "${it.first()}${it.length}" })
// {Elephant=E8, Giraffe=G7, Lion=L4, Tiger=T5}

-associateBy: For the ‘key-value’ mapping operation, mapping is done on the ‘key’ part. If mapping is desired for both the ‘key’ and ‘value,’ a lambda expression is used.

val fruits = listOf("Apple", "Banana", "Cherry", "Apple")
println(fruits.associateBy { it.lowercase() })
// {apple=Apple, banana=Banana, cherry=Cherry}
val countries = listOf("Turkey", "Greece", "Italy", "Spain")
println(countries.associateBy(
keySelector = { it.first() },
valueTransform = { it.length }
)) // {T=6, G=6, I=5, S=5}

Flatten

It is used for flattening nested collections. In other words, if there are collections within a collection, this operation allows you to obtain a single flat collection by removing the nested collections.

val data = listOf(
listOf(100, 200),
listOf(true, false),
listOf("John", "Mary", "Mike")
)
println(data.flatten())
// [100, 200, true, false, "John", "Mary", "Mike"]

-flatMap:By using flatMap, it is possible to process elements within nested collections and then obtain a single collection.

data class Product(
val name: String,
val price: Double
)

val products = listOf(
Product("Apple", 10.0),
Product("Banana", 5.0),
Product("Orange", 4.0)
)

println(products.flatMap { listOf("${it.name} -> ${it.price * 0.5}") })
// [Apple -> 5.0, Banana -> 2.5, Orange -> 2.0]

JoinToString

It is used to represent elements within a collection in a String format, separating the elements with a comma.

val fruits = listOf("Apple", "Banana", "Orange", "Grapes")
println(fruits) // [Apple, Banana, Orange, Grapes]
println(fruits.joinToString()) // Apple, Banana, Orange, Grapes

You can make additions to the String expression to be transformed using a StringBuffer.

val fruits = listOf("Apple", "Banana", "Orange", "Grapes")
val listString = StringBuffer("Fruit Basket: ")
println(fruits.joinTo(listString))
// Fruit Basket: Apple, Banana, Orange, Grapes

Additionally, you can change the separator, and add prefix and postfix.

val names = listOf("John", "Mary", "Mike")
println(
names.joinToString(
separator = " / ",
prefix = "Hello, ",
postfix = " nice to see you guys again."
)
) // Hello, John / Mary / Mike nice to see you guys again.

To show a specific number of elements, you can use ‘limit,’ to process the elements, ‘transform,’ and to specify the String expression to be written at the end, you can use ‘truncated’.

val alphabet = 'A'..'Z'
println(alphabet.joinToString(
limit = 3,
truncated = "more....",
transform = {
it.lowercase()
})) // a, b, c, more....

Filter

As the name suggests, it is used to filter elements within a collection based on a desired operation. As a result, it returns the filtered list.

Attention: The ‘filter’ function does not affect the original list. It returns a new filtered list. To get the returned list, you should create a new list and assign it.

val prices = listOf(150.5, 160.6, 170.2, 180.4, 190.3, 200.7)

val filteredPrices = prices.filter { it < prices.average() }
println(filteredPrices) // [150.5, 160.6, 170.2]

If you have a ‘Map’, you can filter it as per your needs for both ‘key’ and ‘value’ pairs.

val words = mapOf(
"Car" to "Araba", "Table" to "Masa", "Phone" to "Telefon" , "eight" to "8")
println(words.filter { (key, value) -> key.length < 4 || value.startsWith('M')
}) // {Car=Araba, Table=Masa}

-filterIndexed: If you need the indices of the elements within the collection when filtering, ‘filterIndexed’ allows you to access both the index and value.

val fruits = listOf("apple", "banana", "pear", "kiwi")
val filteredFruits = fruits.filterIndexed { index, value ->
index < 1 || value.contains('p')
}
println(filteredFruits) // [apple, pear]

-filterNot: This function is used to filter elements that do not satisfy a specific condition.

val nums = listOf(7, 51, 12, 1, 5, 16)
val filteredProfessions = nums.filterNot { it >= 7 }
println(filteredProfessions) // [1, 5]

filterNotNull: It is used to remove nullable elements from the list.

val mixedData = listOf(25, null, 63, 45, null)
val filteredNotNullValues = mixedData.filterNotNull()
println(filteredNotNullValues) // [25, 63, 45]

Partition

This function divides the elements within the collection into two different groups based on a specific condition.

val temperatures = listOf(32, 75, 20, 100, 27)
val (hot, cold) = temperatures.partition { it >= 50 }
println(hot) // [75, 100]
println(cold) // [32, 20, 27]

Predicates

These are functions used to select elements within the collection that meet or do not meet a specific condition.

  • any: It returns true if at least one of the elements satisfies the condition.
  • none: It returns true if there are no elements that meet the specified condition.
  • all: It returns true if all elements satisfy the specified condition. If the collection is empty, it will return true.
val countries = listOf("Canada", "Japan", "Australia", "Germany")
println(countries.any { it.startsWith("C") }) // true
println(countries.none { it.startsWith("C") }) // false
println(countries.all { it.contains("a") }) // true

println(emptyList<String>().all { it.length % 4 == 0 }) // true
println(emptyList<Boolean>().all { it }) // true
println(emptyList<Int>().all { it > 10 }) // true

Plus/Minus operators

No need to say much about these :)

val fruits = listOf("apple", "banana", "cherry")
val newFruits = fruits + listOf("grape", "kiwi")
val removedFruits = fruits - "apple"

println(fruits) // [apple, banana, cherry]
println(newFruits) // [apple, banana, cherry, grape, kiwi]
println(removedFruits) // [banana, cherry]

Grouping:

It divides the elements within the collection into different groups based on our operation, it returns a ‘map’ in key-value format.

-groupBy: It groups the collection based on our operation. If we want to perform operations on the elements, we can access key-value pairs using ‘keySelector’ and ‘valueTransform’.

-groupingBy: We can define operations that we want to occur within all groups.

val cities = listOf("New York", "London", "Berlin", "Los Angeles", "Boston")
println(cities.groupBy {
it.first().lowercase(Locale.getDefault())
}) // {n=[New York], l=[London, Los Angeles], b=[Berlin, Boston]}
println(cities.groupBy(
keySelector = { it.first() },
valueTransform = { it.lowercase(Locale.getDefault()) }
)) // {N=[new york], L=[london, los angeles], B=[berlin, boston]}

println(cities.groupingBy { it.first() }.eachCount()) // {N=1, L=2, B=2}

Thank you for reading. The last part of the Kotlin Collection Operations will be in my next article. Don’t forget to follow to not miss the last article in the series :).

If you have any feedback about this, please feel free to message me on LinkedIn.

resources:

--

--