It’s time to continue our learning path in Kotlin. The subject covered in this new post is represented by Collections and data operations applied to them.
Collections are actually a set of classes and interfaces that provides high quality implementations of useful data structures and algorithms that help developers to reduce the programming effort and time.
🕵️♀️Collections
In Kotlin there are different flavors of collections. Just like Java, all collection interfaces are originated from the Iterable interface. The main difference between Java and Kotlin is that in Kotlin we have mutable and immutable collections. An important thing to mention here is that immutable collections in Kotlin are simply read-only.
So in terms of collections these are the main ones:
- Pair – a tuple of two values and Triple – a tuple of three values.
- Array – indexed fixed-sized collection of objects and primitives.
- List – ordered collection of objects.
- Set – unordered collection of objects.
- Map – associative dictionary or map of keys and values.
1️⃣ Pair and Triple
Very helpful if we want to quickly create two (Pair) or three objects (Triple) as a collection
🔗Pair
📝Class definition
data class Pair<out A, out B> : Serializable
👩💻Code examples
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// combine different data types | |
val testName = "Kotlin Test" | |
val grade = 10 | |
val resultTest = Pair (testName, grade) | |
println(resultTest) | |
// get the parts of Pair | |
val book = Pair("Learn Kotlin from GDE", 20) | |
val (title , price) = book | |
println("Title = ${book.first} , Price = ${book.second}") | |
// infix function | |
val yellowScarf = "yellow" to "scarf" | |
println(yellowScarf.first) // => yellow | |
println(yellowScarf.second) // => scarf | |
// nesting with parentheses | |
val clothes = ("yellow" to "scarf") to ("blue" to "blouse") | |
println(clothes.second.first) // => blue | |
🔗Triple
📝Class definition
data class Triple<out A, out B, out C> : Serializable
👩💻Code examples
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// triple | |
var kotlinVersions = Triple(1.1, 1.2, 1.3) | |
var firstKotlinVersion = kotlinVersions.first | |
var secondKotlinVersion = kotlinVersions.second | |
var thirdKotlinVersion = kotlinVersions.third | |
// combine different data types | |
var kotlinBook = Triple(1289, "Learn Kotlin from GDE", 21.99) | |
val (isbn, title, price) = kotlinBook | |
println("ISBN = ${kotlinBook.first} , Title = ${kotlinBook.second} , Price = ${kotlinBook.third}") | |
val addressBook = Triple("Kotlin", 789456123, "Saint Petersburg") | |
val (name, phone, address) = addressBook | |
println("Name = ${addressBook.first} , Phone = ${addressBook.second} , Address = ${addressBook.third}") |
2️⃣ Array
A memory block that keeps multiple values in a sequence; very helpful for low-level optimizations.
🔗Array
📝Class definition
class Array<T>
👩💻Code examples
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// defining arrays | |
val intArray = arrayOf(9, 8, 7, 6, 5, 4, 3) | |
val stringArray = arrayOf("one", "two", "three", "four") | |
val charArray = arrayOf('a', 'b', 'c', 'd', 'e') | |
// initialization | |
val ids = IntArray(5) | |
// defining specific arrays | |
var numbers = intArrayOf(1, 2, 3) | |
var chars = charArrayOf('a', 'b', 'c') | |
val programmingLanguages = arrayOf("kotlin", "java", "scala", "python") | |
println(programmingLanguages::class) //class kotlin.Array | |
println(programmingLanguages.javaClass) //class [Ljava.lang.String; | |
// index of the array element | |
println("${programmingLanguages[0]} and ${programmingLanguages[1]}") //kotlin and java | |
// get function | |
println(programmingLanguages.get(3)) //python | |
// size | |
println(programmingLanguages.size) //4 | |
// array and for loop | |
for (element in stringArray) { | |
println(element) | |
} | |
// set function | |
ids[0] = 10 | |
ids[1] = 20 | |
ids.set(2, 30) | |
ids.set(3, 40) | |
for (element in ids) { | |
println(element) | |
} | |
// mutable vs immutable | |
val immutableArray = arrayOf(1, 2, 3) | |
immutableArray.set(0, 10) | |
immutableArray[1] = 20 | |
// compilation error if we remove the comments from the next line | |
// immutableArray = arrayOf(5,6,7,8,9,10) | |
var mutableArray = arrayOf(1, 2, 3) | |
mutableArray.set(0, 10) | |
mutableArray[1] = 20 | |
// arrays with different data types | |
val array = arrayOf(1, "hello", 'd', 19.99) | |
for (i in array) { | |
println(i) | |
} | |
println(array.contains(2)) | |
println(array.contains('d')) | |
// arrayOfNulls | |
val nullArray = arrayOfNulls<String>(5) | |
nullArray.set(0, "learn") | |
nullArray.set(3, "kotlin") | |
for (element in nullArray) { | |
println(element) | |
} | |
/* | |
learn | |
null | |
null | |
kotlin | |
null*/ |
Both the immutableArray and mutableArray arrays are fixed in size, but the elements of each array are mutable and can be updated. The only difference is that immutableArray is declared with the val keyword, so this array cannot be reassigned.
3️⃣ List
Data structures that hold a number of items in a sequence. Kotlin provides two different types of lists:
- immutable lists (does not have add function)
- mutable lists
🔗List
📝Class definition
interface List<out E> :Collection<E>
🔗MutableList
📝Class definition
interface MutableList<E> : List<E>, MutableCollection<E>
👩💻Code examples
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// immutable list and mutable list | |
val numbersList = listOf("one", "two", "three") | |
val mutableNumbersList = mutableListOf("one", "two", "three") | |
// alter the list using add function | |
mutableNumbersList.add("five") | |
listOf(1, 5, 3).sum() // => 9 | |
listOf("a", "b", "cc").sumBy { it.length } // => 4 | |
// operators | |
val numberList2 = numbersList + "four" | |
println(numberList2) | |
val numbersListNoTwo = numberList2 – "two" | |
println(numbersListNoTwo) | |
println(numbersListNoTwo::class) //class java.util.ArrayList | |
println(numbersListNoTwo.javaClass) //class java.util.ArrayList | |
// listOfNotNull | |
val notNulls = listOfNotNull(32, null, "one", null, 'd') | |
println("Size = ${notNulls.size}") | |
for (element in notNulls) { | |
println(element) | |
} |
4️⃣ Set
Unordered collections of elements. Other than using setOf() / Set<T> and mutableSetOf() / MutableSet<T> we could also use hashSetOf() / java.util.HashSet<T>
🔗Set
📝Class definition
interface Set<out E> : Collection<E>
🔗MutableSet
📝Class definition
interface MutableSet<E> : Set<E>, MutableCollection<E>
👩💻Code examples
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// immutable set and mutable set | |
val colors = setOf("red", "blue", "yellow", "white") | |
var miniColors = listOf("red", "blue") | |
var result = colors.containsAll(miniColors) | |
println(result) | |
val mutableColors = mutableSetOf("red", "blue", "yellow") | |
mutableColors.add("pink") | |
var mutableSetIteratorColors = mutableColors.iterator() | |
while (mutableSetIteratorColors.hasNext()) { | |
print(mutableSetIteratorColors.next()) | |
} |
📝Class definition
interface Map<K, out V>
🔗MutableMap
📝Class definition
interface MutableMap<K, V> : Map<K, V>
👩💻Code examples
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// immutable map and mutable map | |
val desserts = hashMapOf("whipped cream" to "cake", "chocolate" to "cookie") | |
println(desserts["chocolate"]) | |
if( desserts.isNotEmpty()) { | |
println("desserts size is ${desserts.size}" ) | |
} | |
// contains key | |
println(desserts.containsKey("chocolate")) //true | |
// contains value | |
println(desserts.containsValue("cake")) //true | |
// contains key | |
println(desserts.contains("chocolate")) //true | |
println("chocolate" in desserts) //true | |
val inventory = mutableMapOf("pancake" to 1) | |
inventory.put("cake", 3) | |
inventory.remove("pancake") | |
if( inventory.isNotEmpty()) { | |
println("inventory size is ${inventory.size}" ) | |
} |
Collections vs Sequences
- Collections are eagerly evaluated
- Sequences are lazily evaluated
- The difference between eager and lazy evaluation lies in when each transformation on the collection is performed.
- In collection operation chains, each operation is applied to all elements before moving onto the next operation. In other words, one operation at a time. And it translates in creating a copy of the collection.
- In sequence operation chains, each element receives all applicable operations before moving onto the next element. In other words, one element at a time, so no copy of the sequence (it keeps a reference to the current sequence)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var someNumbers = listOf(1, 4, 5, 6, 7, 8, 2323, 5456, 343, 2) | |
someNumbers | |
.filter { it % 2 == 0 } | |
.map { it * 3 } | |
.sum() | |
someNumbers | |
.asSequence() | |
.filter { it % 2 == 0 } | |
.map { it * 2 } | |
.sum() | |
// collection | |
interface Iterable<out T> { | |
operator fun iterator(): Iterator<T> | |
} | |
// sequence | |
interface Sequence<out T> { | |
operator fun iterator(): Iterator<T> | |
} | |
// collection | |
public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> { | |
return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform) | |
} | |
// sequence | |
public fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R> { | |
return TransformingSequence(this, transform) | |
} | |
var someNumbers = listOf(1, 4, 5, 6, 7, 8, 2323, 5456, 343, 2) | |
someNumbers | |
.filter { it % 2 == 0 } // 1 collection created | |
.map { it * 3 } // 1 collection created | |
.sum() | |
// total collections created = 2 | |
someNumbers | |
.asSequence() | |
.filter { it % 2 == 0 } | |
.map { it * 2 } | |
.sum() | |
// total collections created = 0 |
Common properties and methods for collections
- size – returns the size of your collection.
- contains(element) – checks if the element is in your collection.
- containsAll(elements)-checks if all elements of the collection elements are in your collection.
- isEmpty() – shows whether the collection is empty or not.
- joinToString() – converts the collection to a string.
- indexOf(element) – returns the index of the first entry of the element, or -1 if the element is not in the collection.
- clear() – removes all elements from the collection.
- remove(element) – removes the first occurrence of an element from your collection.
- removeAll(elements)- removes from your collection all elements contained in the collection elements.
🕵️♀️Data operations in a collection
📌Input data
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//input data | |
var kotlin = Language("kotlin") | |
var java = Language("java") | |
java.town = "San Francisco" | |
var php = Language("php") | |
var scala = Language("scala") | |
scala.town = "New York" | |
var javascript = Language("javascript") | |
var languagesList = listOf(kotlin, java, php, scala, javascript) |
📌 Aggregate functions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// count | |
println(languagesList.count()) | |
//maxBy | |
println(languagesList.maxBy { it.name.length }) | |
//minBy | |
println(languagesList.minBy { it.name.length }) | |
//average | |
println(arrayOf(8, 8, 10, 9, 9).average()) | |
//sum | |
println(arrayOf(7.3, 7.6, 7.4, 7.6, 8.1, 7.7).sum()) | |
println(languagesList.sumBy { it.releaseYear }) |
📌 Search functions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//first | |
//NoSuchElementException could be thrown | |
println(languagesList.first()) | |
println(languagesList.first { it.name.length == 3 }) | |
println(languagesList.firstOrNull { it.name.length == 2 }) | |
//find | |
println(languagesList.find { it.town.equals("San Francisco") }) | |
println(languagesList.findLast { it.town.equals("Bucharest") }) | |
//last | |
println(languagesList.last()) | |
println(languagesList.last { it.town.contains(' ') }) | |
//single | |
//IllegalArgumentException if list has more than one element | |
println(languagesList.single{ it.town.equals("San Francisco")}) | |
println(languagesList.singleOrNull{ it.town.equals("Pitesti")}) |
📌 Filter functions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//filter | |
println(languagesList.filter { it.name.contains('k') }) | |
//filterNot(), filterNotNull() | |
println(languagesList.filterNot { it.name.contains('k') }) | |
println(languagesList.filterNotNull()) | |
//filterTo(), filterNotTo() | |
var listToFilterInto = mutableListOf<Language>() | |
languagesList.filterTo(listToFilterInto) { it.town.contains('o') } | |
languagesList.filterNotTo(listToFilterInto) { it.town.contains('o') } | |
languagesList.filterNotNullTo(listToFilterInto) | |
//filterIndexed() | |
println(languagesList.filterIndexed { index, _ -> index % 3 == 0}) |
📌 Transform functions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//map | |
println(languagesList.map { it.town }) | |
println(languagesList.mapIndexed { index, language -> index to language.town }) | |
//distinct | |
println(languagesList.map { it.town }.distinct()) | |
//associate(), associateBy() | |
println(languagesList.associate { it.name to it.town }) | |
println(languagesList.associateBy({ it.name }, { it.company })) | |
//groupBy() | |
println(languagesList.groupBy { it.company }) |
Check here my previous articles about Kotlin:
Enjoy and feel free to leave a comment if something is not clear or if you have questions. And if you like it please share!
Thank you for reading! 🙌🙏😍✌
Follow me on: