Kotlin Regex Pattern Matching

Matching Strings using regular expressions (REGEX) is a difficult topic. Regex strings are often difficult to understand and debug. They often require extensive testing to make sure that the regex is matching what it is supposed to match.

Kotlin goes out of its way to avoid making developers use regex. For example, the split() method of String does not require a regex (unlike its Java counterpart). Doing so reduces bugs and helps keep the code more readable in general. When we need to use a regex, Kotlin has an explicit Regex type.

One advantage of having a regex type is that code is immediately more readable.

val regex = """\d{5}""".toRegex()

Notice a few things about this String. First, we use the triple quoted, or raw, string to define the regular expression. This helps us avoid bugs caused by improper escaping of the regex string. Also, the string has a toRegex() method that converts the String to a Regex object.

The Regex object comes packed with its own methods that are used for pattern matching.

regex.containsMatchIn("My string 00000")
regex.findAll("00000, 000121, 23213")

Of course there are many other methods found on the Regex object, but see the Kotlin documentation for more details: http://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/-regex/index.html

Regex Tables

Below are some common regex symbols, meta symbols, and quantifiers as presented in Oracle Certified Professional Java SE 7 Programmer Exams 1Z0-804 and 1Z0-805 A Comprehensive OCPJP 7 Certification Guide by Ganesh and Sharma.

Common Symbols

Matches either x or y
Symbol Meaning
^expr Matches expr at beginning of the line
expr$ Matches expr at end of line
. Matches any single character (exception the newline character)
[xyz] Matches either x, y, or z
[p-z] Matches either any character from p to z or any digit from 1 to 9
[^p-z] ‘^’ as the first character negates the pattern. This will match anything outside of the range p-z
xy Matches x followed by y
x|y

Common Meta Symbols

\d Matches digits ([0-9])
\D Matches non-digits
\w Matches word characters
\W Matches non-word characters
\s Matches whitespaces [\t\r\f\n]
\S Matches non-whitespaces
\b Matches word boundary when outside of a bracket. Matches backslash when placed in a bracket
\B Matches non-word boundary
\A Matches beginning of string
\Z Matches end of String

Common Quantifiers

expr? Matches 0 or 1 occurrence of expr (expr{0,1})
expr* Matches 0 or more occurrences of expr (expr{0,})
expr+ Matches 1 or more occurrences of expr (expr{1,})
expr{x, y} Matches between x and y occurrences of expr
expr{x, } Matches x or more occurrences of expr

Putting it Together

Here is an example program that uses Regex in Kotlin.

fun main(args : Array<String>){
    val symbols = mapOf(
            "^expr" to "Matches expr at beginning of the line",
            "expr$" to "Matches expr at end of line",
            "." to "Matches any single character (exception the newline character)",
            "[xyz]" to "Matches either x, y, or z",
            "[p-z]" to "Specifies a range. Matches any character from p to z",
            "[p-z1-9]" to "Matches either any character from p to z or any digit from 1 to 9",
            "[^p-z]" to "'^' as the first character negates the pattern. This will match anything outside of the range p-z",
            "xy" to "Matches x followed by y",
            "x|y" to "Matches either x or y")

    val metaSymbols = mapOf(
            "\\d" to "Matches digits ([0-9])",
            "\\D" to "Matches non-digits",
            "\\w" to "Matches word characters",
            "\\W" to "Matches non-word characters",
            "\\s" to "Matches whitespaces [\\t\\r\\f\\n]",
            "\\S" to "Matches non-whitespaces",
            "\\b" to "Matches word boundary when outside of a bracket. Matches backslash when placed in a bracket",
            "\\B" to "Matches non-word boundary",
            "\\A" to "Matches beginning of string",
            "\\Z" to "Matches end of String")


    val quantifiers = mapOf(
            "expr?" to "Matches 0 or 1 occurrence of expr (expr{0,1})",
            "expr*" to "Matches 0 or more occurrences of expr (expr{0,})",
            "expr+" to "Matches 1 or more occurrences of expr (expr{1,})",
            "expr{x, y}" to "Matches between x and y occurrences of expr",
            "expr{x, }" to "Matches x or more occurrences of expr")

    val format = "%-10s\t%s"
    val func = {entry : Map.Entry<String, String> -> println(format.format(entry.key, entry.value)) }

    println("Symbols")
    symbols.entries.forEach(func)

    println("\nMeta Symbols")
    metaSymbols.entries.forEach(func)

    println("\nQuantifiers")
    quantifiers.entries.forEach(func)

    //Create a regex object
    println("\nTesting regex: ^Matches")
    val regex = "^Matches".toRegex()
    symbols.entries.forEach({it ->
        //The Regex Type has a Number of Pattern Matching Methods
        val matchResult = regex.containsMatchIn(it.value)
        println("$matchResult => ${it.value}")
    })
}

Output

Symbols
^expr     	Matches expr at beginning of the line
expr$     	Matches expr at end of line
.         	Matches any single character (exception the newline character)
[xyz]     	Matches either x, y, or z
[p-z]     	Specifies a range. Matches any character from p to z
[p-z1-9]  	Matches either any character from p to z or any digit from 1 to 9
[^p-z]    	'^' as the first character negates the pattern. This will match anything outside of the range p-z
xy        	Matches x followed by y
x|y       	Matches either x or y

Meta Symbols
\d        	Matches digits ([0-9])
\D        	Matches non-digits
\w        	Matches word characters
\W        	Matches non-word characters
\s        	Matches whitespaces [\t\r\f\n]
\S        	Matches non-whitespaces
\b        	Matches word boundary when outside of a bracket. Matches backslash when placed in a bracket
\B        	Matches non-word boundary
\A        	Matches beginning of string
\Z        	Matches end of String

Quantifiers
expr?     	Matches 0 or 1 occurrence of expr (expr{0,1})
expr*     	Matches 0 or more occurrences of expr (expr{0,})
expr+     	Matches 1 or more occurrences of expr (expr{1,})
expr{x, y}	Matches between x and y occurrences of expr
expr{x, } 	Matches x or more occurrences of expr

Testing regex: ^Matches
Disconnected from the target VM, address: '127.0.0.1:61983', transport: 'socket'
true => Matches expr at beginning of the line
true => Matches expr at end of line
true => Matches any single character (exception the newline character)
true => Matches either x, y, or z
false => Specifies a range. Matches any character from p to z
true => Matches either any character from p to z or any digit from 1 to 9
false => '^' as the first character negates the pattern. This will match anything outside of the range p-z
true => Matches x followed by y
true => Matches either x or y

Rerences

Ganesh, S G., and Tushar Sharma. Oracle Certified Professional Java SE 7 Programmer Exams 1Z0-804 and 1Z0-805 A Comprehensive OCPJP 7 Certification Guide. Apress, 2013.

Kotlin String Splitting

Most programming tasks require string splitting. For example, CSV files often separate data based on the comma character, which requires developers to split each line based on the comma in order to extract data. Extracting domain names from a web address is another common use case for String splitting. For example, we might have the address https://stonesoupprogramming.com and we wish to separate the https:// portion of the string. We can split the string into a list where the first part contains http:// and the second index contains stonesoupprogramming.com.

In Kotlin, we use the split() method defined in the String class. It comes in two flavors. One flavor takes the character to split the string on, and the other flavor takes a Regex. Both versions of the split method return a list that contains all potions of the String.

Non-Regex Splitting

The first version of split() takes a varargs parameter of delimiters, an optional boolean argument to ignoreCase and an optional limit argument that restricts how many times the split happens.

val str = "I smell fear on you"
val parts = str.split(" ")
val partsTwo = str.split("I", "fear", "you")
val partsThree = str.split("I", true)
val partsFour = str.split(delimiters = " ", limit = 2)

All versions of split return a list. It’s worth keeping in mind that the returned list will not contain any of the delimiters passed to the delimiters argument in split(). Normally, that isn’t a problem. For example, would you really want the ‘,’ character for all fields in a CSV file?

Regex Version

Most programming languages treat regular expressions, REGEX, as a String. Doing so often leads to unexpected bugs. Consider Java’s String.split() method.

String myString = "Green. Eggs. Ham.";
String [] parts = myString.split(".");

You may think that parts holds {“Green”, “Eggs”, “Ham”}. It doesn’t. The period character is treated as a regex expression that matches to any character. It’s a very common mistake.

Thankfully, Kotlin treats regular expressions as its own type. When we want to use a Regex in Kotlin, we need to create a Regex object. The Kotlin String class has a toRegex() function that performs the conversion from String to Regex.

val str = "Green. Eggs. Ham"
val partsNonRegex = str.split(".") //No Regex. This will split on the period character
val partsRegex = str.split(".".toRegex()) //Now using REGEX matching

Putting it together

As always, we will conclude with an example program that demonstrates the topic. Many of my students are given assignments where they need to track the number of unique words in a String. We will use String splitting and maps to accomplish the goal.

fun main(args : Array<String>){
    val paragraph = """
        |I am Sam.
        |Sam I am.

        |That Sam-I-am!
        |That Sam-I-am!
        |I do not like
        |That Sam-I-am!

        |Do you like
        |Green eggs and ham?

        |I do not like them,
        |Sam-I-Am
        |I do not like
        |Green eggs and ham.
        """.trimMargin()

    //Remove all end line characters and then split the string on the space character
    val parts = paragraph.replace('\n', ' ').split(" ")
    
    //Create an empty mutable map
    val uniqueWords = mutableMapOf<String, Int>()
    
    //Populate the map
    parts.forEach( { it -> uniqueWords[it] = uniqueWords.getOrDefault(it, 0) + 1 })
    
    //Print each word with it's count value
    println(uniqueWords)
}

Here is the output when run.

{I=5, am=1, Sam.=1, Sam=1, am.=1, =3, That=3, Sam-I-am!=3, do=3, not=3, like=4, Do=1, you=1, Green=2, eggs=2, and=2, ham?=1, them,=1, Sam-I-Am=1, ham.=1}

Kotlin String Methods

The Kotlin String class has an indexOf() method that allows developers to the position of a character or set of characters within a string. Such an operation is especially useful in situations where you may need to break a string into a substring or divide a string into different parts. Let’s go over the indexOf() method with a few examples.

indexOf

indexOf(Char)

The indexOf(Char) method is used to search for a single character within a string. It will return the index where the character was first found or -1 if the string doesn’t contain such a character.

val paragraph = 
    "I am Sam.\n" +
    "Sam I am.\n" +

    "That Sam-I-am!\n" +
    "That Sam-I-am!\n" +
    "I do not like\n" +
    "That Sam-I-am!\n" +

    "Do you like\n" +
    "Green eggs and ham?\n" +

    "I do not like them,\n" +
    "Sam-I-Am\n" +
    "I do not like\n" +
    "Green eggs and ham.\n"

//Index of letter a => 2
println("Index of letter a => " + paragraph.indexOf('a'))

The letter ‘a’ is the 3rd character in the example string. Since computers count starting at 0, the result is 2. This method also has an optional argument that allows it to ignore case.

indexOf(String)

If we want to find where a certain substring begins, we use the indexOf(String) method. It works just like its Char counterpart.

//Index of 'Green eggs and ham' => 91
println("Index of 'Green eggs and ham' => " + paragraph.indexOf("Green eggs and ham"))

The substring “Green eggs and ham” is found at position 91. Once again, this method returns -1 if the search string isn’t found. We can also use the ignoreCase optional argument when we do not care about case sensitivity.

indexOf(Char, Int), indexOf(String, Int)

The indexOf method has an optional startIndex parameter that takes an int value. By default, startIndex is 0, which is why indexOf starts at the beginning of the string. If we want to start looking in the middle of the string, we need to pass a value to startIndex. Let’s look at an example of where we can find all occurrences of the letter ‘I’.

var fromIndex = 0
while(paragraph.indexOf('I', fromIndex) > -1){
    fromIndex = paragraph.indexOf("I", fromIndex)
    println("Found at => " + fromIndex)
    fromIndex++
}

Output

Found at => 0
Found at => 14
Found at => 29
Found at => 44
Found at => 50
Found at => 73
Found at => 111
Found at => 135
Found at => 140

The code tracks each index with a fromIndex variable. We enter into a while loop that continues until indexOf returns -1. With each iteration of the loop, we update fromIndex using indexOf() and pass in the old value of fromIndex. That causes the search to keep moving forward. After we print the index, we need to increment fromIndex by 1 because indexOf is inclusive. Should we fail to increment fromIndex, we will enter into an infinite loop because indexOf will continue to return the same value.

lastIndexOf

Strings also have a lastIndexOf() method that is a cousin to the indexOf() method. It takes the same arguments as indexOf(), but rather than returning the first occurence of the search character or string, it returns the last occurence instead.

//Last index of 'eggs' => 160
println("Last index of 'eggs' => " + paragraph.lastIndexOf("eggs"))

startsWith(), endsWith()

The startsWith() and endsWith() methods are convience methods that are used to check if a string starts or ends with a supplied prefixString. It also has an optional offset parameter that allows for searching in the middle of the string.

//paragraph starts with 'I am Sam' => true
println("paragraph starts with 'I am Sam' => " + paragraph.startsWith("I am Sam"))
//paragraph ends with 'Green eggs and ham. => true
println("paragraph ends with 'Green eggs and ham. => " + paragraph.endsWith("Green eggs and ham.\n"))

Putting it Together

Here is an example program followed by the output.

fun main(args : Array<String>){
    val paragraph =
        "I am Sam.\n" +
        "Sam I am.\n" +

        "That Sam-I-am!\n" +
        "That Sam-I-am!\n" +
        "I do not like\n" +
        "That Sam-I-am!\n" +

        "Do you like\n" +
        "Green eggs and ham?\n" +

        "I do not like them,\n" +
        "Sam-I-Am\n" +
        "I do not like\n" +
        "Green eggs and ham.\n"


    println("Index of letter a => " + paragraph.indexOf('a'))
    println("Index of 'Green eggs and ham' => " + paragraph.indexOf("Green eggs and ham"))
    println("Finding all occurrences of 'I'...")
    var fromIndex = 0
    while(paragraph.indexOf('I', fromIndex) > -1){
        fromIndex = paragraph.indexOf("I", fromIndex)
        println("Found at => " + fromIndex)
        fromIndex++
    }
    println("Last index of 'eggs' => " + paragraph.lastIndexOf("eggs"))
    println("paragraph starts with 'I am Sam' => " + paragraph.startsWith("I am Sam"))
    println("paragraph ends with 'Green eggs and ham. => " + paragraph.endsWith("Green eggs and ham.\n"))
}

Output

Index of letter a => 2
Index of 'Green eggs and ham' => 91
Finding all occurrences of 'I'...
Found at => 0
Found at => 14
Found at => 29
Found at => 44
Found at => 50
Found at => 73
Found at => 111
Found at => 135
Found at => 140
Last index of 'eggs' => 160
paragraph starts with 'I am Sam' => true
paragraph ends with 'Green eggs and ham. => true

Kotlin Arrays

Although they are used less frequently than the collection class, Kotlin does have arrays. Kotlin arrays are declared as Array where T corresponds to the type of object the array holds. The array type is packed with factory methods and extension functions, all of which make working with arrays easy.

Creating Arrays

We normally use the arrayOf() method to create an array.

//Create an initialized array
val belchers = arrayOf("Bob", "Linda", "Tina", "Gene", "Louise")

//Create an empty array with five elements
val teddies = arrayOfNulls<String>(5)

//Create an array with a generator function
val morts = Array(5){ "mort" }

In the first example, we use arrayOf() and then pass any number of arguments to the function. The Kotlin compiler will determine the type of object based on the arguments and create an array initialized with arguments. The second example creates an object array of the specified size and initialized with nulls. There are overloaded versions for primitives. The final function creates an array with a specified number of elements and initializes it with a generator function.

Extension Functions

toList() and other conversion functions

True to Kotlin style, Arrays have methods that convert the array to a collection class. Here is an example of converting an array to an immutable list.

val belcherList = belchers.toList()

sort()

Arrays have a sort() method that makes it easy to sort an array. There is an overloaded version that takes a comparator object also.

belchers.sort()

binarySearch

Kotlin arrays have a binarySearch method that lets developers apply the binarySearch algorithm to the array. Note that the function will fail if the array isn’t sorted first.

belchers.sort()
belchers.binarySearch("Linda") //returns 2

Arrays Class

The Arrays class is part of JDK and provides additional methods that are useful for working with arrays.

toString()

Calling toString() on an array doesn’t yield the result that one might expect. That’s because an array does not override toString() and instead uses the implementation found in java.lang.Object. To pretty print an array, we need to use Arrays.toString().

Arrays.toString(belchers)

equals

Arrays also use the equals() implementation found in java.lang.Object. If we want to compare two arrays by their elements, we use Arrays.equals().

val pestos = arrayOf("Jimmy", "Jimmy JR", "Andy", "Ollie")
val isEqual = Arrays.equals(belchers, pestos) //returns false

Putting it Together

Below is a Kotlin program that demonstrates arrays.

import java.util.*

fun main(args : Array<String>){
    val belchers = arrayOf(
                    "Bob",
                    "Linda",
                    "Tina",
                    "Gene",
                    "Louise")

    //Pretty print arrays using Arrays.toString()
    println("Printing the array")
    println(Arrays.toString(belchers) + "\n")

    println("Converting to a list")
    val belcherList = belchers.toList()
    println("belcherList => " + belcherList + "\n")

    println("Sorting")
    Arrays.sort(belchers)
    println("Sorted belchers => " + Arrays.toString(belchers) + "\n")

    println("Binary Search")
    println("Linda found at index => " + belchers.binarySearch("Linda") + "\n")

    println("Equals")
    val pestos = arrayOf("Jimmy", "Jimmy JR", "Andy", "Ollie")
    println(Arrays.toString(belchers) + " is equal to " + Arrays.toString(pestos) + " => " + Arrays.equals(belchers, pestos) + "\n")

    println("Filling an empty array")
    val teddies = Array(5){ "teddy" }
    println("After filling teddies => " + Arrays.toString(teddies) + "\n")
}

Output

Printing the array
[Bob, Linda, Tina, Gene, Louise]

Converting to a list
belcherList => [Bob, Linda, Tina, Gene, Louise]

Sorting
Sorted belchers => [Bob, Gene, Linda, Louise, Tina]

Binary Search
Linda found at index => 2

Equals
[Bob, Gene, Linda, Louise, Tina] is equal to [Jimmy, Jimmy JR, Andy, Ollie] => false

Filling an empty array
After filling teddies => [teddy, teddy, teddy, teddy, teddy]

Kotlin Collections Class

Kotlin and JDK have a number of extension methods that make working with collection classes easier.

Extension Methods

sort()

The sort() method sorts elements by their natual order. It also has an overloaded version that takes a comparator that allows for custom sorting.

val belchers = mutableListOf(
            "Bob",
            "Linda",
            "Tina",
            "Gene",
            "Louise")
belchers.sort()

binarySearch(T) : Int

The binarySearch function applies the binary search algorithm to the collection and returns the index of the element or -1 if not found. The list has to be sorted first.

belchers.sort()
belchers.binarySearch("Gene") //returns 1

Conversion Methods

Kotlin provides methods that allow for each switching between collection types.

val setBelchers = belchers.toSet()
val listBelchers = belchers.toList()

Populating a List

Collections can be populated with a generator function. Here is an example for a list.

val teddies = List(10){ "Teddy" }
println(teddies) //prints: [Teddy, Teddy, Teddy, Teddy, Teddy, Teddy, Teddy, Teddy, Teddy, Teddy]

max()

The max() function returns the maximum element in the collection. There is an overloaded version that takes a comparator.

val max = belchers.max()

min()

The min() function returns teh minimum element in the collection. There is an overloaded version that takes a comparator.

val min = belchers.min()

replaceAll

The replaceAll takes a lambda expression and replaces all elements according to the lambda function. Here is an example that replaces all occurences of “Tina” with “Mort”.

belchers.replaceAll { it -> if(it == "Tina") "Mort" else it }

addAll

The addAll function takes a list and adds all elements in the list to the collection.

belchers.addAll(teddies)

removeAll

The removeAll takes a collection and removes all matches from the current collection.

belchers.removeAll(teddies)

reverse()

The revesere() method reverses the order of the elements in the collection.

belchers.reverse()

Note

The functions demonstrated above are on MutableList. They may or may not be present on other collection types such as List, Set, or MutableSet. See the Kotlin documentation for more details.

Collections

Oddly, not all of the methods found in the Collections class have been implemented as extension functions. Here are two common functions found in the Collections class.

shuffle

Randomizes the elements in the list.

Collections.shuffle(belchers)

swap

Swaps the elements at the specified indexes.

Collections.swap(belchers, 0, 3)

Putting it Together

Below is an example program that demonstrates the mentioned functions.

import java.util.*

fun main(args : Array<String>){
    val belchers = mutableListOf(
            "Bob",
            "Linda",
            "Tina",
            "Gene",
            "Louise")

    println("Soring the belchers")
    belchers.sort()
    println("After sorting => " + belchers + "\n")

    println("Using Binary Search")
    println("Gene Found at Index => " + belchers.binarySearch("Gene"))

    //Make a read-only copy of belchers
    val belchersCopy = belchers.toList()
    println("Read only copy => " + belchersCopy + "\n")

    //Filling a list
    val teddies = List(10) { "Teddy"}
    println("Printing teddies => " + teddies + "\n")

    println("Finding the max belcher => " + belchers.max() + "\n")

    println("Find the min belcher => " + belchers.min() + "\n")

    println("Replacing Tina")
    belchers.replaceAll { it -> if(it == "Tina") "Mort" else it }
    println("After replacing Tina => " + belchers + "\n")

    println("Adding teddies...")
    belchers.addAll(teddies)
    println("After adding teddies => " + belchers + "\n")

    println("Removing all teddies...")
    belchers.removeAll(teddies)
    println("After removing teddies => " + belchers + "\n")

    println("Reversing belchers")
    belchers.reverse()
    println("After reversing => " + belchers + "\n")

    //Methods in the Collections class that haven't been made
    //into extension functions
    println("Shuffling belchers...")
    Collections.shuffle(belchers)
    println("After shuffling => " + belchers + "\n")


    println("Swapping 0 and 3")
    Collections.swap(belchers, 0, 3)
    println("After swap => " + belchers)
}

Output

Soring the belchers
After sorting => [Bob, Gene, Linda, Louise, Tina]

Using Binary Search
Gene Found at Index => 1
Read only copy => [Bob, Gene, Linda, Louise, Tina]

Printing teddies => [Teddy, Teddy, Teddy, Teddy, Teddy, Teddy, Teddy, Teddy, Teddy, Teddy]

Finding the max belcher => Tina

Find the min belcher => Bob

Replacing Tina
After replacing Tina => [Bob, Gene, Linda, Louise, Mort]

Adding teddies...
After adding teddies => [Bob, Gene, Linda, Louise, Mort, Teddy, Teddy, Teddy, Teddy, Teddy, Teddy, Teddy, Teddy, Teddy, Teddy]

Removing all teddies...
After removing teddies => [Bob, Gene, Linda, Louise, Mort]

Reversing belchers
After reversing => [Mort, Louise, Linda, Gene, Bob]

Shuffling belchers...
After shuffling => [Linda, Gene, Louise, Mort, Bob]

Swapping 0 and 3
After swap => [Mort, Gene, Louise, Linda, Bob]

Kotlin Comparable and Comparator

Sorting objects by a certain property or field is a common task. However, as we begin to define our own classes, we need a framework that allows us to support sorting our objects. The Comparable and Comparator interfaces provide such a framework.

Both interfaces define a comparTo(t : T) : Int method. When one object is greater than another object, compareTo should return a positive number (1 or greator). When two objects are equal, compareTo should return zero. Finally, when one object is less than another object, compareTo should return a negative number (-1 or less).

Since Kotlin supports operator overloading, class that implement the Comparable interface have the added bonus of being able to support comparison operators such as <, <=, >, >=. Operator overloading was added to Kotlin to improve code readability and conciseness. After all, it’s much easier to read str1 < str2 as opposed to str1.compareTo(str2) == 1. Nevertheless, it’s still on the developer to decide how two classes are compared.

It’s typical to use field by field comparison. For example, if we are sorting people, we may choose to sort by last name followed by first name. In other cases, we may wish to sort by employee id number. When our classes implement Comparable, we are specifying a default or natural ordering for our classes. To help us define a compareTo method, we may wish to turn to 3rd party libraries such as Apache Commons or Google Guava which provide excellent tools to implement compareTo. Some IDES can even generate code to implement the compareTo method.

It’s not very difficult to implement compareTo in the absence of tools. All Kotlin primitives support compareTo, so implemeting compareTo can be a straightforward class. Let’s look at this example.

data class FamilyMember(val firstName : String,
                        val lastName : String,
                        val age : Int) : Comparable<FamilyMember> {

    /**
     * Having a compareTo method also overloads the comparison operators
     * >, >=, <, <=
     */
    override fun compareTo(other: FamilyMember): Int {
        val fName = firstName.compareTo(other.firstName)
        val lName = lastName.compareTo(other.lastName)
        return fName.compareTo(lName)
    }
}

In our compareTo example, we compare first names by calling compareTo on both names and store the result in a variable. We do the same for last names. Finally, we call compareTo on the fName and lName variables and return the result. At this point our family class has a natural ordering and may use the comparison operators.

When we wish to sort a collection class, we can use the sort() method. The sort() method calls compareTo on each object held in the collection and sorts the items.

val belchers = mutableListOf(
            FamilyMember("Bob", "Belcher", 45),
            FamilyMember("Linda", "Belcher", 44),
            FamilyMember("Tina", "Belcher", 13),
            FamilyMember("Gene", "Belcher", 11),
            FamilyMember("Louise", "Belcher", 9))
belchers.sort()
println(belchers)

After calling belchers.sort(), all family members are sorted by last name, first name. However, what if we want to sort by a different property, say age? That’s when we use Comparator.

Since Kotlin has both object expressions and lambda expressions, we don’t tend to define classes that implement Comparator. Instead, since Comparator is a single abstract method (SAM) interface, we typically use Comparator in a lambda expression. Let’s see how we can sort the belchers by their age.

belchers.sortBy { it.age } // This creates a Comparator that compares by age
println(belchers)

The FamilyMember.age property is an Int, which already implements Comparable. So when the compiler sees it.age, it has all the information it needs to create a Comparator that compares by age. Since it’s not completely known in advance how we are expected to sort objects, Comparators give developers extra flexibility when we need to sort objects differently from the natural order.

Putting it Together

This is an example program that shows how to use both Comparable and Comparator.

data class FamilyMember(val firstName : String,
                        val lastName : String,
                        val age : Int) : Comparable<FamilyMember> {

    /**
     * Having a compareTo method also overloads the comparison operators
     * >, >=, <, <=
     */
    override fun compareTo(other: FamilyMember): Int {
        val fName = firstName.compareTo(other.firstName)
        val lName = lastName.compareTo(other.lastName)
        return fName.compareTo(lName)
    }
}

fun main(args : Array<String>){
    val belchers = mutableListOf(
            FamilyMember("Bob", "Belcher", 45),
            FamilyMember("Linda", "Belcher", 44),
            FamilyMember("Tina", "Belcher", 13),
            FamilyMember("Gene", "Belcher", 11),
            FamilyMember("Louise", "Belcher", 9))
    val bob = belchers[0]
    val gene = belchers[3]

    println("Before sort")
    println(belchers)
    println()

    println("Sorting using natural ordering")
    belchers.sort()
    println(belchers)
    println()

    println("Sorting by age")
    belchers.sortBy { it.age }
    println(belchers)
    println()

    println("Testing operator overloading")
    println("Bob > Gene? " + (bob > gene))
}

Output

Before sort
[FamilyMember(firstName=Bob, lastName=Belcher, age=45), FamilyMember(firstName=Linda, lastName=Belcher, age=44), FamilyMember(firstName=Tina, lastName=Belcher, age=13), FamilyMember(firstName=Gene, lastName=Belcher, age=11), FamilyMember(firstName=Louise, lastName=Belcher, age=9)]

Sorting using natural ordering
[FamilyMember(firstName=Bob, lastName=Belcher, age=45), FamilyMember(firstName=Gene, lastName=Belcher, age=11), FamilyMember(firstName=Linda, lastName=Belcher, age=44), FamilyMember(firstName=Louise, lastName=Belcher, age=9), FamilyMember(firstName=Tina, lastName=Belcher, age=13)]

Sorting by age
[FamilyMember(firstName=Louise, lastName=Belcher, age=9), FamilyMember(firstName=Gene, lastName=Belcher, age=11), FamilyMember(firstName=Tina, lastName=Belcher, age=13), FamilyMember(firstName=Linda, lastName=Belcher, age=44), FamilyMember(firstName=Bob, lastName=Belcher, age=45)]

Testing operator overloading
Bob > Gene? false

Kotlin Deque Interface

The Deque interface extends the Queue interface and allows for items to be added and removed from both ends of the Deque. In other words, we are not only able to make FIFO (first in, first out) structures, but we may also make LIFO structures (last-in, first-out). One real-world use case for LIFO may be an accounting program where inventory is tracked on a LIFO basis to reduce a company’s cost. If we were writing a Kotlin application for such a task, we would use Deque.

The Deque has a variety of methods that are commonly used. Here is a list of the methods followed by a brief description.

Creating a Deque

The LinkedList class in an implementation of the Deque.

val deque : Deque<String> = LinkedList()

Operations with Excpetions

addFirst(T) : void

The addFirst method adds an item to the front of the Deque. The method can raise an exception if the operation fails.

deque.addFirst("Bob Belcher")

addLast(T) : void

The addLast method add an item to the end of the Deque. The method can raise an exception if the operation fails.

deque.addLast("Linda Belcher")

removeFirst() : T

The removeFirst() method removes the first item from the Deque and return it. A NoSuchElement exception is thrown if the Deque is empty.

val bob = deque.removeFirst()

removeLast() : T

The removeLast() method removes the last item from the Deque and returns it. A NoSuchElement exception is thrown if the Deque is empty.

val linda = deque.removeLast()

getFirst() : T

The getFirst() method returns the first item from the Deque, but it does not remove it from the Deque. A NoSuchElement exception is thrown if the Deque is empty.

val bob = deque.getFirst();
println( deque.contains("Bob") ) ///prints True

getLast() : T

The getLast() method returns the last item from the Deque, but it does not remove it from the deque. A NoSuchElement exception is thrown if the Deque is empty.

val linda = deque.getLast()
println( deque.contains("Linda") ) //prints True

Methods that Do Not Throw Exceptions

offerFirst(T) : Boolean

Attempts to add the item to the front of the Deque and returns true if successful otherwise false.

deque.offerFirst("Gene")

offerLast(T) : Boolean

Attempts to add the item to the end of the Deque and returns true if successful otherwise false.

deque.offerLast("Tina")

pollFirst() : T?

Removes and returns an element from the front of the Deque. It will return null if the operation fails.

val gene = deque.pollFirst()

pollLast() : T?

Removes and returns an element from the end of the Deque. It will return null if the operation fails.

val tina = deque.pollLast()

peekFirst() : T?

Returns either the element or null from the front of the Deque but does not remove it from the Deque.

val gene = deque.peekFirst()
println( deque.contains("Gene") )//prints true

peekLast() : T?

Returns either the element or null from the end of the Deque but does not remove it from the Deque.

val tina = deque.peekLast()
println( deque.contains("Tina") )//prints true

Putting it Together

Below is an example program that uses a Deque as a stack (a LIFO datastructure). It simulates layers of soils where the top most layer has to be dug up first prior to digging up older layers of soils.

import java.util.*

fun main(args : Array<String>){
    val soils : Deque<String> = LinkedList()

    with (soils){
        add("Bedrock")
        add("Clay")
        add("Sand")
        add("Top Soil")
    }

    while(soils.isNotEmpty()){
        println("Digging up layer => " + soils.removeLast())
    }
}

Output

Digging up layer => Top Soil
Digging up layer => Sand
Digging up layer => Clay
Digging up layer => Bedrock

Kotlin Queue Interface

The Queue interface is a first-in, first-out data structure. In other words, the first item that is added to the queue is the first item to be removed. We can picture the queue as a line for a roller coaster. The first person to enter the line is the first person who gets to ride on the roller coaster.

Example Program

We will use a Queue in this example program to simulate a music player. The Queue is filled with songs that we would like to hear and then it plays them one at a time in the order they were added. Here is the code.

package ch6.queue

import java.util.*

fun main(args : Array<String>){
    //Create the Queue. Note that LinkedList is one of the implementing classes
    val playQueue : Queue<String> = LinkedList()

    //Add songs to the Queue 
    with (playQueue){
        add("Swing Life Away")
        add("Give it All")
        add("Dancing for Rain")
        add("Hero of War")
        add("Black Masks and Gasoline")
    }

    //Loop until the Queue is empty
    while(!playQueue.isEmpty()){
        //The remove() method takes an item from the Queue and returns it
        println("Playing Song => " + playQueue.remove())
    }
}

The program uses LinkedList as the concrete class to implement the Queue interface. We add five songs to the Queue using the with function and Queue’s add() method. Then we loop until the queue is empty. When we call remove() on the Queue, the first item in the queue is removed and returned. In this way, we print all of the songs added to the Queue one at a time.

Playing Song => Swing Life Away
Playing Song => Give it All
Playing Song => Dancing for Rain
Playing Song => Hero of War
Playing Song => Black Masks and Gasoline

Kotlin Maps

Maps are data structures that associate keys with values. When we lookup values in a map, we specify a key and the map returns an associated value. It’s easy to think of a map being like a phone book. When we want to call somebody, and we only know their name but not their phone number, we find their name in the phone book and the phone book has their number associated with a name.

In the phone book example, we can think of a person’s name as the key and the phone number as the value. In Kotlin, we could make such a map like so:

val phoneBook = mapOf("Bob Belcher" to 8675309, "Linda Belcher" to 8675310)
println(phoneBook["Bob Belcher"]) //prints 8675309
println(phoneBook["Linda Belcher"]) //prints 8675310

We create a phoneBook object using the mapOf(), or mutableMap(), and then specifying any number of pair arguments. Each pair is written key to value so in our example, “Bob Belcher” to 8675309 or “Linda Belcher” to 8675309. When we need to access an item in the map, we use the index operator [] and put the key inside of the operator. In other words, to print Bob’s number, we would write phoneBook[“Bob Belcher”].

When we do not know all of the values we are placing in the map ahead of time, we can use mutableMapOf(). This is the read/write version of the map that has methods to add items and remove items from a map. Let’s look at a code example to see how we add items and remove them from a mutableMap.

//Type is needed when we do not specify arguments in mutableMapOf()
val phoneNumbers = mutableMapOf<String, Int>()

//Use the put method to add items
phoneNumbers.put("Bob Belcher", 8675309)
phoneNumbers.put("Linda Belcher", 8675310)

//Use the remove method to remove items
phoneNumbers.remove("Linda Belcher")

In this case, we use the put() method to add items to the map. The first argument is the key and the second argument is the value. The remove method is the opposite operation. It takes the key as the only argument and removes the entry from the map.

Keys in a map have to be unique. If we use put() with a key that already exists, we will overwrite the current value of the map. This normally isn’t a problem, but it can end up being a source of bugs, especially for new learners. We can protect ourselves from such accidents by converting a mutableMap into a an immutableMap.

//Change phoneNumbers into a read only map
val readOnly = phoneNumbers.toMap() 

Map Example Problem

Couting the frequency of words in text is a common use case for maps. This program counts the number of times certain words appear from an except of “Green Eggs and Ham” by Dr. Suess.

package ch6.maps

fun main(args : Array<String>){
    val paragraph = """
        I am Sam.
        Sam I am.

        That Sam-I-am!
        That Sam-I-am!
        I do not like
        That Sam-I-am!

        Do you like
        Green eggs and ham?

        I do not like them,
        Sam-I-Am
        I do not like
        Green eggs and ham.
        """
    //Remove punctuation and new line characters
    val cleanParagraph =
            paragraph.replace(".", "")
                    .replace(",", "")
                    .replace("?", "")
                    .replace("\n", "")

    //Split cleanParagraph into a List
    val words = cleanParagraph.split(" ")
    
    //Create an empty Map
    val wordCounts = mutableMapOf<String, Int>()
    
    //We have a few operations on the next line
    //1) Iterate through words
    //2) Check if the word has been found in wordCounts
    //3) Put an entry into word counts if the word wasn't found
    //4) Otherwise, update the entry found in wordCounts by 1
    words.forEach( { it -> wordCounts.put(it, wordCounts.getOrElse(it, { 0 }) + 1) })
    
    //Now print out the words with their frequencies
    println(wordCounts.toSortedMap())
}

Output

{=93, Do=1, Green=2, I=5, Sam=2, Sam-I-Am=1, Sam-I-am!=3, That=3, am=2, and=2, do=3, eggs=2, ham=2, like=4, not=3, them=1, you=1}

Kotlin Sets

Sets are a data structure that do not allow for duplicate entries. A common use case for sets is when we need to filter out duplicate elements from a list or an array. Like lists, Kotlin has both mutable sets and read-only sets. In addition, there is also a sorted set flavor that inserts elements in a specified order. The sorted set is read only.

Operations

Creation

We use setOf(), mutableSetOf(), sortedSetOf() to create sets. They take any number of parameters.

val belchers = setOf("Bob", "Linda", "Tina", "Gene", "Louise")
val mutableBelchers = mutableSetOf("Bob", "Linda", "Tina", "Gene", "Louise")

val smiths = sortedSetOf("Rick", "Jerry", "Beth", "Summer", "Morty")

Accessing Items

Sets do not overload the index operator. We can access an item with either the first() or last() method, find, or looping through the entire list.

val bob = belchers.first()
val linda = belchers.last()
val gene = belchers.find { it -> it == "Gene" }
words.forEach( {it -> println(it) } )

Testing Membership

We can use the in function to test if the list has an item.

println("Tina" in belchers) //prints true
println("Rick" in belchers) //prints false

Adding or Removing (Mutable Only)

We can use add or remove on mutableSet to change the elements in the set.

mutableBelchers.remove("Tina")
mutableBelchers.add("Tina")

mutableBelchers.add("Bob") //no effect because Bob is already in the set

Putting it Together

This is an example program that finds the unique words in an except from Dr. Suess’s Green Eggs and Ham.

fun main(args : Array<String>){
    val paragraph = """
        I am Sam.
        Sam I am.

        That Sam-I-am!
        That Sam-I-am!
        I do not like
        That Sam-I-am!

        Do you like
        Green eggs and ham?

        I do not like them,
        Sam-I-Am
        I do not like
        Green eggs and ham.
        """
    //Remove punctuation and new line characters
    val cleanParagraph =
            paragraph.replace(".", "")
                     .replace(",", "")
                     .replace("?", "")
                     .replace("\n", "")
    
    //Split the string by space character and convert it into a set
    val words = cleanParagraph.split(" ").toSet()
    
    //Split the string by space character and convert it to a sortedSet
    val sortedWords = cleanParagraph.split(" ").toSortedSet()


    println(words)
    println(sortedWords)
}

Output

[, I, am, Sam, That, Sam-I-am!, do, not, like, Do, you, Green, eggs, and, ham, them, Sam-I-Am]
[, Do, Green, I, Sam, Sam-I-Am, Sam-I-am!, That, am, and, do, eggs, ham, like, not, them, you]