HiveBrain v1.2.0
Get Started
← Back to all entries
patternkotlinMinor

Credit card / IMEI check digit in Kotlin

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
creditcardimeikotlindigitcheck

Problem

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?

Solution

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)

Code Snippets

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()
}
inline fun CharSequence.sumByIndexed(selector: (Int, Char) -> Int): Int {
    return foldIndexed(0) { index, sum, element -> sum + selector(index, element) }
}
val Int.isEven: Boolean
    get() = and(1) == 0
fun Char.asInt() = toInt() - 48
fun Int.asChar() = toChar() + 48
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)

Context

StackExchange Code Review Q#139636, answer score: 3

Revisions (0)

No revisions yet.