Imagine you’re writing universal domain logic for your program. All I/O is provided via an interface (like UI, persistent storage, networking, etc.), you don’t have to think about it, and you almost don’t care about the platform, on which you run. Your program is fairly simple, only a few straightforward tasks. This chapter describes all essentials you need to write it. The foundational basics that you can build upon: data, processing primitives, and functions to reuse code.

Data

At the core of any program, lies the data. The sole purpose is to often receive and transform, sometimes create, but always output the data. A program that doesn’t output anything is useless. Take away variables (or functions that act as variables) and there’s nothing to process. In this section, cornerstone data representations are discussed: primitive data types, variables, collections, ranges, text, optionals, and function types.

Note: Custom data types (structs, classes) are showcased later, in the Abstractions chapter. They help a lot and make your program much easier to reason about, but you don’t absolutely need them. You can get away by structuring your data using collections, Map a.k.a. Dictionary a.k.a. Associative Array in particular. This has been demonstrated by Lua scripting language, for example.

Collections

List

Random-access, iterable sequence, read-only or mutable. List and MutableList are interfaces that can have different underlying implementations (e.g., linked list, array, stack). The default implementation is a resizable array (ArrayList). It is recommended to use these general interfaces over more specific types like Array, for example. Rationale is the same as for Java Collections.

var readOnlyList: List<Int> = emptyList()
readOnlyList = listOf(1, 2, 3)

val listSize = "List has ${readOnlyList.size} elements"
val firstListElement = "The first element is ${readOnlyList[0]}"
val alsoFirstListElement = "The first element is still ${readOnlyList.get(0)}"
val isFourOnTheList = 4 in readOnlyList
val isFourOnTheListAlternative = readOnlyList.contains(4)

val mutableList = mutableListOf<Int>()
mutableList.add(2)
mutableList.add(0, 1)
mutableList.addAll(arrayOf(3, 4))
mutableList.removeAt(2)
mutableList[mutableList.lastIndex] = 3

mutableList == readOnlyList
mutableList != readOnlyList

Stack and queue

Implemented by double-ended queue called ArrayDeque in Kotlin. It’s a special case of a MutableList.

val queue = ArrayDeque(listOf(1, 2, 3))
val peekFirstElement = "First element: ${queue.first()}"
val firstQueueElement = queue.removeFirstOrNull()
queue.addLast(4)
queue.addFirst(1)
queue[1] = 5

val stack = ArrayDeque<Int>()
stack.addLast(1)
stack.addLast(2)
val peekTopElement = "Last element: ${queue.last()}"
val topStackElement = stack.removeLastOrNull()

Map

Key-value store that is also known as “dictionary” or “associative array”.

val readOnlyMap: Map<String, Int> = mapOf("one" to 1, "two" to 2, "twoAgain" to 2)
val mapValueOne = readOnlyMap["one"]
"three" in readOnlyMap
3 in readOnlyMap.values
readOnlyMap.containsKey("three")
readOnlyMap.containsValue(3)

var mutableMap: MutableMap<String, Int> = mutableMapOf()
mutableMap["first"] = 1
mutableMap.put("second", 2)
mutableMap.put("first", 2)
mutableMap["second"] = 1
mutableMap.remove("first")

mapOf("one" to 1, "two" to 2) == mutableMapOf("two" to 2, "one" to 1)
mapOf("one" to 1) != mapOf("two" to 2)

Set

A collection of unique elements with undefined order. However, a Set is also iterable and provides random access via indices! The default implementation of MutableSet is LinkedHashSet, which stores elements in the order they were inserted. An alternative, HashSet, is more efficient, but doesn’t preserve the order.

var readOnlySet: Set<Int> = emptySet()
readOnlySet = setOf(1, 2, 3)
readOnlySet = arrayOf(1, 2, 3, 3, 2, 1).toSet()
val setSize = "Set has ${readOnlySet.size} elements"
val isFourInTheSet = readOnlySet.contains(4)
val isFourInTheSetAlternative = 4 in readOnlySet

val mutableSet: MutableSet<Int> = mutableSetOf()
mutableSet.add(1)
mutableSet.addAll(readOnlySet)
mutableSet.remove(4)

mutableSet == readOnlySet
mutableSet != readOnlySet

val unionSet: Set<Int> = mutableSet.union(readOnlySet)
val intersectionSet: Set<Int> = unionSet.intersect(mutableSet)
val differenceSet: Set<Int> = unionSet.subtract(intersectionSet)

Array

Array is a more efficient, lower-level implementation of a random-access, fixed size, iterable sequence. It is always fixed size, mutable, and occupies a contiguous memory region. There are only very specific situations that require it; otherwise the more flexible List should be preferred.

To compare array contents, special functions contentEquals and contentDeepEquals should be used, as opposed to Lists and other Collections where you should rely on operators == and !=. Kotlin Docs

val arrayIsAlwaysFixedSize = emptyArray<String>()

val arrayIsAlwaysMutable = arrayOf(1, 2, 3)
arrayIsAlwaysMutable[0] = 10

val jaggedTwoDArray = Array(2) { Array(it + 1) { 0 } }
jaggedTwoDArray[0][0] = 1

arrayOf(1, 2, 3) contentEquals arrayOf(1, 2, 3)
arrayOf(1, 2, 3) contentDeepEquals jaggedTwoDArray

arrayOf(1, 2, 3).toList()
arrayOf(1, 2, 3).toSet()

val arrayOfPairs: Array<Pair<Int, String>> = 
  arrayOf(1 to "one", 2 to "two", 3 to "three")
arrayOfPairs.toMap()

Primitive-type arrays

For primitive types, Arrays can be even more optimized by using primitive-type arrays that don’t box their values into Objects.

// More efficient arrays for primitive types without boxing

val arrayOfInts: IntArray = arrayOf(1, 2, 3).toIntArray()
val arrayOfBooleans: BooleanArray = booleanArrayOf(true, false)
val arrayOfChars: CharArray = "Hello".toCharArray()

val boxedAgain: Array<Char> = arrayOfChars.toTypedArray()

[--]