How trolls count in Kotlin

Backend Kotlin Systemutveckling

Uzi Landsmann

Systemutvecklare

‘Everyone knows trolls can’t even count up to four!’*

 

*In fact, trolls traditionally count like this: one, two, three, many, and people assume this means they can have no grasp of higher numbers. They don’t realise that many can BE a number. As in: one, two, three, many, many-one, many-two, many-three, many many, many-many-one, many-many-two, many-many-three, many many many, many-many-many-one, many-many-many-two, many-many-three, LOTS.

I’ve read these wonderful lines in Terry Pratchett’s Men at Arms many years ago and while trying to learn different programming languages, I started using them as a challenge and as a way to investigate a programming language’s different aspects by trying to implement them in the language I’m currently learning. If you want to see how I implemented the troll numeric system in Kotlin, please read on.

 

Requirements

In order to implement the troll numeric system I needed some requirements. So I made a list which I will try to use later on when writing the code. And here it is — the complete set of requirements:

  1. The whole numeric system is consisted of five different words. These are: one, two, three, many and lots

  2. Simple troll numbers use one word, such as one, many, or lots

  3. Complex troll numbers use several words, such as many-two or many-many-three

  4. Troll numbers should have a getter to retrieve their numeric value:
    three.value // 3
    many-two.value // 6

  5. Also, they have a toString() implementation:
    many-many.toString() // many-many
    three.toString // three

  6. `Troll numbers can be created from strings, e g:
    "two".toTrollNumber() // two
    "many-many-three".toTrollNumber() // many-many-three

  7. Also, they can be created from numbers, e g:
    3.toTrollNumber() // three
    15.toTrollNumber() // many-many-many-three

  8. Troll numbers should obviously be able to be created programmatically:
    val five = many-one // many-one
    val eleven many-many-three // many-many-three
    val fourteen = many-many-many-two // many-many-many-two

  9. Lastly, they can be added to each other, like that:
    many-one + three // many-many
    two + three // many-one

 

The TrollNumber interface

I’ve decided to use an interface called TrollNumber, which will be implemented later by the simple and complex classes. I’ve also decided to use the numeric value of the troll number as the state. It all looks like that:

interface TrollNumber {
    val value: Int
}

 

Simple troll numbers

These are quite easy to implement. I’ve created a class named SimpleTrollNumber which implements the interface. Its toString() method translates the numeric value into a word:

 

data class SimpleTrollNumber(override val value: Int) : TrollNumber {
    override fun toString(): String {
        return when (value) {
            1 -> "one"
            2 -> "two"
            3 -> "three"
            4 -> "many"
            16 -> "lots"
            else -> "unknown"
        }
    }
}

 

I’ve also included constants for the five simple troll numbers:

 

val one = SimpleTrollNumber(1)
val two = SimpleTrollNumber(2)
val three = SimpleTrollNumber(3)
val many = SimpleTrollNumber(4)
val lots = SimpleTrollNumber(16)


Complex troll numbers

A complex troll number is simply a bunch of simple troll numbers combined. So the simple solution I used is to construct it using a list of TrollNumbers. The overridden value property will add the simple troll number values together and the toString() method will join their individual names using a minus sign:

 

data class ComplexTrollNumber(
  private val trollNumbers: List<TrollNumber> = listOf()) 
  : TrollNumber {

    override val value: Int
        get() = trollNumbers.sumBy { tn -> tn.value }

    override fun toString(): String {
        return trollNumbers.joinToString("-")
    }
}


Constructing troll numbers from strings

Creating troll numbers from strings is simple: if the string does not contain a minus sign, then parse it into a simple troll number. If it does, then split it into a list of words, parse them and create a complex troll number using them. This is all implemented using Kotlin’s amazing extension function ability, which allows you to add functionality to existing classes, like in this case, String. This means you could then create troll number using strings, as specified in requirement #6:

fun String.toTrollNumber(): TrollNumber {
    if (this.contains("-")) {
        val split = this.split("-")
        val trollNumbers = split.map { s -> s.toTrollNumber() }
        return ComplexTrollNumber(trollNumbers)
    }

    return when (this) {
        "one" -> one
        "two" -> two
        "three" -> three
        "many" -> many
        "lots" -> lots
        else -> throw IllegalArgumentException("Unknown troll number")
    }
}

 

Constructing troll numbers from numbers

When constructing a troll number from an integer, we need to break it into manys (that is, the troll number many) by dividing the number by four and using the reminder (if there is one) as a simple troll number, then combining them together. Here we also use an extension method - in this case for extending the Int class to implement requirement #7:

fun Int.toTrollNumber(): TrollNumber {
    return when (this) {
        1 -> one
        2 -> two
        3 -> three
        4 -> many
        16 -> lots
        else -> {
            val noOfManys = this / 4
            val manys = MutableList(noOfManys) { many }

            val rest = this % 4
            if (rest > 0) {
                val simple = SimpleTrollNumber(rest)
                manys.add(simple)
            }
            return ComplexTrollNumber(manys)
        }
    }
}

 

Creating troll numbers programmatically

How to create a troll number programmatically? In this case we use (or rather, abuse) another Kotlin feature, namely operator overloading. Operator overloading allows you to redefine how operators like +, -, * and / behave when used in your own classes. We can redefine the minus operator to create a complex troll number, given two troll numbers (rather than subtracting them from each other, as it was meant to be). That way, writing val six = many-two will give us the complex troll number many-two, as asked for in requirement #8:

operator fun TrollNumber.minus(other: TrollNumber): TrollNumber =
    ComplexTrollNumber(listOf(this, other))

 

Adding two troll numbers

In order to be able to add two troll numbers we again use operation overloading, this time using it the way it was supposed to be, by adding the values of those two numbers and creating a new troll number using the Int.toTrollNumber() extension function mentioned above, thus completing requirement #9:

 

operator fun TrollNumber.plus(other: TrollNumber): TrollNumber =  
    (this.value + other.value).toTrollNumber()


Testing

I’m using the kotest library (previously known as Kotlintest) for testing. I like it a lot as it gives me a simple syntax and and easy way to run parameterised tests. For example, here’s a test that creates simple and complex troll numbers from strings, and verifies that their values are correct:

"String parsing should give correct value" {
    val table = table(
            headers("string", "expected value"),
            row("many-two", 6),
            row("many-many-three", 11),
            row("many", many.value),
            row("many-many-many-many", lots.value),
            row("lots", lots.value)
    )

    forAll(table) { string, expectedValue ->
        string.toTrollNumber().value shouldBe expectedValue
    }
}


Final words

The code is available in github. You can find it here: https://github.com/uzilan/trollnumbers/tree/master/kotlin

 

I find Kotlin to be very suitable for this project, as I also did with Scala earlier. The ability to use extension functions (in Kotlin) or implicit conversions (in Scala) to convert strings and integers into troll number instances, plus the way I could use operator overloading to join simple troll numbers into complex ones as well as adding two troll numbers using the overloaded plus operator makes it very fitting to use these languages.

 

If you’d like to implement troll numbers using a different programming language you’re welcome to add a PR.

Fler inspirerande inlägg du inte får missa