5
\$\begingroup\$

I recently had the need to implement Luhn's Algorithm in a Java/Kotlin application. I needed a function that would add the check digit to the string of a number like a credit card or IMEI. It could be easily modified to check if the last digit is valid.

At first, I had a code of around 20 lines in Java, but by using every Kotlin quirk I could think of, I managed to do it in one line.

fun String.addLastImeiDigit(): String = "$this${10 - this.withIndex().map { if (it.index.and(1) == 0) it.value.toInt() - 48 else (2 * (it.value.toInt() - 48)).run { (this % 10) + (this / 10) } }.sum() % 10}"

Here it is in a more understandable fashion

fun String.addLastImeiDigit(): String {
    return "$this${10 - this.withIndex().map {
        if (it.index.and(1) == 0) /* if even */
            it.value.toInt() - 48 /* ascii to Int */
        else
            (2 * (it.value.toInt() - 48)).run {
                (this % 10) + (this / 10) /* sum both digits */
            }
    }.sum() % 10}"
}

I understand that obfuscating code like this in a shared code environment would be very bad, but still I think it's great that we can get to do this and that Kotlin avoids so much boilerplate. What do you think about using one-line functions like this one?

\$\endgroup\$
2
\$\begingroup\$

I personally like one-line functions as long as they are short but I also prefer breaking things out into smaller functions and/or objects to improve readability, reuse, etc. e.g.:

fun String.plusLuhnCheckDigit() = this + luhnCheckDigit(this)
fun luhnCheckDigit(digits: CharSequence): Char {
    return digits.sumByIndexed { index, digit ->
        when {
            index.isEven -> digit.asInt()
            else -> sumDigits(2 * digit.asInt())
        }
    }.let { sum -> 10 - sum % 10 }.asChar()
}

With the following also defined:

Strings.kt

inline fun CharSequence.sumByIndexed(selector: (Int, Char) -> Int): Int {
    return foldIndexed(0) { index, sum, element -> sum + selector(index, element) }
}

Numbers.kt

val Int.isEven: Boolean
    get() = and(1) == 0

NumericalChars.kt

fun Char.asInt() = toInt() - 48
fun Int.asChar() = toChar() + 48

IntMath.kt

val Int.magnitude: Int
    get() = if (this < 0) -this else this

fun sumDigits(n: Int): Int = if (n.magnitude < 10) n else n % 10 + sumDigits(n / 10)
\$\endgroup\$
  • \$\begingroup\$ Thanks for the comment! As I said, I know there are better ways to implement this, but I wanted to showcase Kotlin's many tools that allow this kind of complex functions to be written in a single line. Also, in your sumDigits function, why do you use an if? You would get the same result always if you just used the code inside the else as n/10 would be 0 when n < 10. \$\endgroup\$ – rodolfosrg Aug 26 '16 at 15:28
  • \$\begingroup\$ @rodolfosrg The sumDigits I've included above is generic and is not limited to 2 digits. It recursively sums the digits. As such, fun sumDigits(n: Int): Int = n % 10 + sumDigits(n / 10) would result in a StackOverflowError. \$\endgroup\$ – mfulton26 Aug 26 '16 at 17:22
  • \$\begingroup\$ Also, I considered fun sumDigits(n: Int): Int = if (n == 0) 0 else n % 10 + sumDigits(n / 10) instead but that would result in additional invocations of sumDigitsand plus (it would always end with an additional + 0 unless the last digit were actually "0"). \$\endgroup\$ – mfulton26 Aug 26 '16 at 17:24
  • \$\begingroup\$ Oh that's right. I didn't notice the recursive part. Thanks! \$\endgroup\$ – rodolfosrg Aug 26 '16 at 21:50

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.