Search

Dark theme | Light theme

December 16, 2022

Kotlin Kandy: Create Fix Sized Sublists From Iterables With chunked

The chunked extension method is added to the Iterable Java class and makes it possible to split an iterable into fixed sized lists. We define the size of the lists as argument to the chunked method. The return result is a list of lists. Each of the lists will have the number of elements we have specified as argument. The last list can have less elements if the total number of elements cannot be divided exactly by the size we specified as argument. We can specify a lambda transformation function as second argument. The lambda function has the new sublist as argument and we can write code to transform that sublist.

In the following example we use the chunked method with different arguments and see what the results look like:

// Sample list of letters.
val letters = listOf('a', 'B', 'c', 'D', 'e', 'F', 'g')

// With chunked we specify the number of items
// that should be grouped together into a new list.
// The return type is a List of List instances.
assert(letters.chunked(3) == listOf(
    listOf('a', 'B', 'c'),
    listOf('D', 'e', 'F'),
    listOf('g')))

assert(letters.chunked(4) == listOf(
    listOf('a', 'B', 'c', 'D'),
    listOf('e', 'F', 'g')))


// If the number of items left is less than the given
// size it is a "remainder".
// In the following helper function we can include or
// exclude the remainder List.
fun chunckedRemainder(chars: List<Char> , size: Int, remainder: Boolean) =
    if (remainder) chars.chunked(size)
    else chars.chunked(size).filter { it.size == size }

// We can skip the remainder in the end result.
assert(chunckedRemainder(letters, 4, false) == listOf(
    listOf('a', 'B', 'c', 'D')))

assert(chunckedRemainder(letters, 4, true) == listOf(
    listOf('a', 'B', 'c', 'D'),
    listOf('e', 'F', 'g')))


// We can use a second argument and provide a transformation
// lambda function to transform the lists that are returned
// from chunking the original collection.
val chunkedJoined = letters.chunked(3) { list -> list.joinToString(separator = "") }

assert(chunkedJoined == listOf("aBc", "DeF", "g"))

Written with Kotlin 1.7.21.

December 15, 2022

Kotlin Kandy: Split Collection Or String With Partition

The method partition is available in Kotlin for arrays and iterable objects to split it into two lists. We pass a predicate lambda function to the partition method. The predicate should return either true or false based on a condition for each element from the array or iterable. The return result is a Pair instance where the first element is a List object with all elements that returned true from the predicate. The second element in the Pair object contains all elements for which the predicate returned false. As a String can be seen as an iterable of characters we can also use partition on a String instance.

In the following example code we use partition on different objects:

// Create an infinite sequence of increasing numbers.
val numbers = generateSequence(0) { i -> i + 1 }

// We take the first 20 numbers from our sequence and
// partition it to two pairs.
// First element of the pair is a list of all even numbers,
// second element is the list of all odd numbers.
// We use destructurizing to assign the pair values to
// variables even and odd.
val (even, odd) = numbers.take(20)
    .partition { n -> n % 2 == 0}

assert(even == listOf(0, 2, 4, 6, 8, 10, 12, 14, 16, 18))
assert(odd == listOf(1, 3, 5, 7, 9, 11, 13, 15, 17, 19))


// Sample map with data.
val data = mapOf("language" to "Java", "username" to "mrhaki", "age" to 49)

// We can also use partition on the entries of the map.
val (stringValues, nonStringValues) = data.entries.partition { entry -> entry.value is String }

assert(stringValues.associate { it.toPair() } == mapOf("language" to "Java", "username" to "mrhaki"))
assert(nonStringValues.associate { it.toPair() } == mapOf("age" to 49))


// Sample string to use with partition.
val s = "Kotlin kandy!"

// We can also use partition on a string where
// the predicate is applied for each character.
val (letters, others) = s.partition(Char::isLetter)

assert(letters == "Kotlinkandy")
assert(others == " !")

Written with Kotlin 1.7.20.

December 14, 2022

Kotlin Kandy: Taking Or Dropping Characters From A String

Kotlin adds a lot of extension methods to the String class. For example we can use the take method to get a certain number of characters from the start of a string value. With the drop method where we remove a given number of characters from the start of the string to get a new string. We can also take and drop a certain number of characters from the end of a string using the methods takeLast and dropLast.

Instead of using the number of characters we want to take or drop we can also use a condition defined with a predicate lambda function. We take or drop characters as long as the lambda returns true. The names of the methods for taking characters are takeWhile and takeLastWhile and for dropping characters dropWhile and dropLastWhile.

In the following example we use different methods to take and drop characters from a string value to get a new string value:

val s = "Kotlin kandy!"

// Take the first 6 characters.
assert(s.take(6) == "Kotlin")

// Take the last 6 characters.
assert(s.takeLast(6) == "kandy!")

// Take characters until lowercase k is encountered.
assert(s.takeWhile { it != 'k'} == "Kotlin ")

// Take characters from the end of the string
// to the beginning until a space is encountered.
assert(s.takeLastWhile { it != ' '} == "kandy!")


// Drop the first 7 characters.
assert(s.drop(7) == "kandy!")

// Drop the last 7 characters.
assert(s.dropLast(7) == "Kotlin")

// Drop characters until a lowercase k is encountered.
assert(s.dropWhile { it != 'k'} == "kandy!")

// Drop characters starting at the end of the string
// until a space is encountered.
assert(s.dropLastWhile { it != ' '} == "Kotlin ")

Written with Kotlin 1.7.20.

December 13, 2022

Kotlin Kandy: Find Common Prefix Or Suffix In Strings

If we want to find the longest shared prefix or suffix for two string values we can use the String extension methods commonPrefixWith and commonSuffixWith. The result is the prefix or suffix value that is common for both values. We can pass a second argument to the method to indicate if we want to ignore the casing of the letters. The default value for this argument is false, so if we don’t set it explicitly the casing of the letters should also match.

In the following example we use the commonPrefixWith and commonSuffixWith methods:

// Find the common prefix of 2 strings.
assert("Sample text".commonPrefixWith("Sampler") == "Sample")

// The second argument can be used to ignore the case
// of letters. By default this is false.
assert("sample text".commonPrefixWith("Sampler", true) == "sample")
assert("sample text".commonSuffixWith("Sampler") == "")


// Find the common suffix of 2 strings.
assert("Sample string".commonSuffixWith("Example thing") == "ing")


// Sample list of string values with a common prefix.
// We want to find the common prefix for these string values.
val values = listOf("Sample value", "Salt", "Sample string", "Sampler")

val commonPrefix = values
    // Combine each value with the next in a pair
    .zipWithNext()
    // Transform each pair into the common prefix of the
    // first and second element from the pair.
    .map { pair -> pair.first.commonPrefixWith(pair.second) }
    // The shortest common prefix is the winner.
    .minBy { common -> common.length }

assert(commonPrefix == "Sa")

Written with Kotlin 1.7.2.0.

December 11, 2022

Kotlin Kandy: Transform Items In A Collection To A Map With associate

Kotlin gives us the associate method for collection objects, like lists, iterables and arrays. With this method we can convert the items in the collection to a new Map instance. The associate method accepts a lambda function as argument and we must return a Pair from the lambda. The first item of the pair will be the key and the second element is the value of the key/value pair in the resulting map.

If we want to use the elements in our collection as key, but want to transform the value we must use associateWith. The lambda for this method must return the value part of our key/value pair. Alternatively if we only want to transform the key value we can use associateBy with one lambda function. The lambda function must return the result for the key in the key/value pair of the map. The method associateBy is overloaded where we can pass two lambda functions. The first lambda function is for transforming the key and the second lambda function is for transforming the value.

In the following example we use all variants of the associate methods:

// Sample list of string vlaues.
val languages = listOf("Kotlin", "Groovy", "Java", "Clojure")

// With associate we use a lambda to return a Pair.
// The Pair is the key/value for the resulting map.
// In this example we use the item in lowercase as key and
// the length of the item as value.
assert(languages.associate { s -> s.lowercase() to s.length } ==
         mapOf("kotlin" to 6, "groovy" to 6,
               "java" to 4, "clojure" to 7))

// associateBy accepts a lambda to return the key value
// of a pair and the value is the element from the collection.
// Here we want to use the lowercase value of the item as key.
assert(languages.associateBy(String::lowercase) ==
         mapOf("kotlin" to "Kotlin", "groovy" to "Groovy",
               "java" to "Java", "clojure" to "Clojure"))

// associateBy accepts a second lambda to also transform
// the value part of the key/value pair.
// With a second lambda we transform the item to it's length as value.
assert(languages.associateBy(String::lowercase, String::length) ==
         mapOf("kotlin" to 6, "groovy" to 6,
                "java" to 4, "clojure" to 7))

// associateWith accepts a lambda to transform the value part
// of the key/value pair. The key is then the element
// from the collection.
// We use the item of the collectio as key, but use the
// length of the string value as value for our key.
assert(languages.associateWith(String::length) ==
         mapOf("Kotlin" to 6, "Groovy" to 6,
                "Java" to 4, "Clojure" to 7))

Each of the assiocate methods return a Map with key/value pairs. If we want to add the result to an existing, mutable Map instance we must use the methods associateTo, associateByTo and associateWithTo. The first argument is a mutable Map and the rest of the arguments is the same as for the associate methods without To.

In the following example we want to add new key/value pairs to an existing Map using associate method variants:

// Example list of some numbers.
val numbers = listOf(2, 3, 4)

// Helper function to return the square value of a given number.
fun square(n: Int) = n * n

// For each of the associate/associateBy/associateWith methods there is
// an equivalent associateTo/associateByTo/associateWithTo to add the result
// to an existing mutable map instance.
assert(numbers.associateTo(mutableMapOf(0 to 0, 1 to 1)) { n -> n to square(n) } ==
         mapOf(0 to 0, 1 to 1, 2 to 4, 3 to 9, 4 to 16))
         
assert(numbers.associateByTo(mutableMapOf(0 to 0, 1 to 1)) { n -> n * 10 } ==
        mapOf(0 to 0, 1 to 1, 20 to 2, 30 to 3, 40 to 4))

assert(numbers.associateByTo(mutableMapOf(0 to 0, 1 to 1), { n -> n }, ::square) ==
         mapOf(0 to 0, 1 to 1, 2 to 4, 3 to 9, 4 to 16))

assert(numbers.associateWithTo(mutableMapOf(0 to 0, 1 to 1), ::square) ==
         mapOf(0 to 0, 1 to 1, 2 to 4, 3 to 9, 4 to 16))

Written with Kotlin 1.7.20.

December 10, 2022

Kotlin Kandy: Padding Strings

Kotlin extends the String class with a couple of padding methods. These methods allows us to define a fixed width a string value must occupy. If the string itself is less than the fixed width then the space is padded with spaces or any other character we define. We can pad to the left or the right of the string using the padStart and padEnd methods. When we don’t define an argument a space character is used for padding, but we can also add our own custom character as argument that will be used as padding character.

In the following example code we use the padEnd and padStart methods with and without arguments:

assert("Kotlin".padEnd(12) == "Kotlin      ")
assert("Kotlin".padStart(12) == "      Kotlin")

assert("Kotlin".padEnd(12, '-') == "Kotlin------")
assert("Kotlin".padStart(12, '.') == "......Kotlin")

val table = listOf(
    Triple("page1.html", 200, 1201),
    Triple("page2.html", 42, 8853),
    Triple("page3.html", 98, 3432),
    Triple("page4.html", 432, 900)
)

val output = table.map { data: Triple<String, Int, Int> ->
    data.first.padEnd(14, '.') +
            data.second.toString().padStart(5, '.') +
            data.third.toString().padStart(8)
}.joinToString(System.lineSeparator())

print(output)

assert(output == """
page1.html......200    1201
page2.html.......42    8853
page3.html.......98    3432
page4.html......432     900
""".trimIndent())

Written with Kotlin 1.7.20.

December 9, 2022

Kotlin Kandy: Strip Leading Spaces From Multiline Strings

Multiline strings are very useful. But sometimes we want use the multiline string without the leading spaces that are added because of code formatting. To remove leading spaces we can use the trimIndent method. This method will find the least amount of leading spaces and removes that amount of spaces from each line. Also a first and last empty line are removed.

If we want a bit more control we can also add a character to the start of each line to show where the line starts. And then we use the method trimMargin and all spaces before that character are removed. The default character is the pipe symbol, |, but we can also define our own and pass it as argument to the trimMargin method.

In the following example code we use the trimIndent and trimMargin methods:

// trimIndent will remove spaces from the beginning
// of the line based on the least number of spaces.
// The first and last empty line are also removed
// from the string.
fun createText(): String {
    return """
        Multiline string
          with simple 2 spaces
        indentation.
    """.trimIndent()
}

assert(createText() == """Multiline string
  with simple 2 spaces
indentation.""")

// trimMargin will trim all spaces before
// the default margin character |.
val languages = """
    |Kotlin
    |Groovy
    |Clojure
      |Java
""".trimMargin()

assert(languages == """Kotlin
Groovy
Clojure
Java""")

// We can use our own margin character by
// specifying the character as argument
// to the trimMargin method.
val buildTools = """
    >Gradle
    >Maven
      >SBT
    >Leiningen
""".trimMargin(">")

assert(buildTools == """Gradle
Maven
SBT
Leiningen""")

Written with Kotlin 1.7.20.

Kotlin Kandy: Transforming Collection Items With Index

If we want to transform items in a collection we can use the map method. If we also want to use the index of the element in the collection in the transformation we must use the mapIndexed method. We must provide a lambda function with 2 arguments, where the first argument is the index of the element in the collection and the second argument is the element in the collection.

In the following examples we use the mapIndexed and the related mapIndexedNotNull and mapIndexedTo methods:

// With mapIndexed we can use a lambda with 2 arguments:
// the first argument is the index,
// the second argument is the value.
assert(listOf(3, 20, 10, 2, 1).mapIndexed { index, n -> n * index } ==
        listOf(0, 20, 20, 6, 4))

// Instead of using the indices property we can use
// mapIndexed to get all the indices.
assert(listOf(3, 20, 10, 2, 1).mapIndexed { index, _ -> index } ==
        listOf(0, 1, 2, 3, 4))
assert(listOf(3, 20, 10, 2, 1).indices == 0..4)

// mapIndexed also works on ranges.
val lettersIndex = ('a'..'z').mapIndexed { index, letter -> letter to (index + 1) }
assert(lettersIndex.take(3) == listOf('a' to 1, 'b' to 2, 'c' to 3))

// Using toMap we get a nice lookup map to find the position
// of a letter in the alphabet.
assert("kotlin".map { c -> lettersIndex.toMap().get(c) } == listOf(11, 15, 20, 12, 9, 14))


// The mapIndexedNotNull method only returns non-null results.
val others = listOf("Kotlin", "Groovy", "Java", "Clojure")
    .mapIndexedNotNull { index, language -> if (index == 2) null else language }
assert(others == listOf("Kotlin", "Groovy", "Clojure"))


// With mapIndexTo we can add the output of the
// transform lambda function to an existing
// mutable collection.
val storage = mutableListOf(90, 10, 3)
assert(listOf(7, 42, 100)
        .mapIndexedTo(storage) { index, n -> index * n } ==
            listOf(90, 10, 3, 0, 42, 200))

Written with Kotlin 1.7.20.

Kotlin Kandy: Getting The Indices Of A Collection

Kotlin adds a lot of useful extensions to the collection classes. One of them is the indices property. The indices property returns the indices of the elements in the collection as an IntRange.

// With the indices property we get back the
// index values for each element, starting with 0
// as an IntRange.
val list = listOf(3, 20, 10, 2, 1)
assert(list.indices == 0..4)


// Helper function to return the position in the alphabet
// of a given letter.
fun findLetterIndex(c: Char): Int {
    // Range of all letters.
    val alphabet = 'a'..'z'

    // Combine letters in alphabet with their position (zero-based).
    // Result: [(a, 0), (b, 1), (c, 2), ...]
    val alphabetIndices = alphabet.zip(alphabet.toList().indices)

    // Find position, if not found return -1.
    val position = alphabetIndices.find { p -> p.first == c}?.second ?: -1

    // Fix the zero based index values.
    return position + 1
}

val positionInAlphabet = "kotlin".map(::findLetterIndex)
assert(positionInAlphabet == listOf(11, 15, 20, 12, 9, 14))

Written with Kotlin 1.7.20.

December 5, 2022

Gradle Goodness: Configure Test Task With JVM Test Suite

The JVM Test Suite plugin is part of the Java plugin and provides a nice way to configure multiple test types in our build file. Even if we don't have multiple test types we have a default test type, which is used when we run the Gradle test task. Using the test suite DSL we can configure the task of type Test that belongs to a test suite type. The current release of the JVM Test Suite plugin provides a single target for a test suite type with a single Test task. This will probably change in future releases of the plugin so more task of type Test can be created and configured.

We can reference the Test task using the syntax within a JvmTestSuite configuration block:

...
targets {
    all {
        testTask
    }
}
...

Once we have the reference to the Test task we can configure it using all the methods and properties available for this class.

In the following example build script we configure the logging and set a system property for our default Test task:

plugins {
    java
}
    
repositories {
    mavenCentral()
}

testing {
    suites {
        val test by getting(JvmTestSuite::class) {
            useJUnitJupiter()  // We want to use Jupiter engine
            
            targets {
                all {
                    // Here can access the test task for this 
                    // test suite type (we use the default in this example).
                    // The task can be referenced as testTask.
                    // The task is of type Test and we can use all methods
                    // and properties of the Test class.
                    testTask.configure {
                        // We define a system property with key greeting
                        // and value Hello, which can be used in our test code.
                        systemProperties(mapOf("greeting" to "Hello"))
                        
                        // We configure the logging for our tests.
                        testLogging {
                            exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL
                            showStandardStreams = true
                        }
                    }
                }
            }
        }
    }
}

Written with Gradle 7.6.