Validate and scrub, then recurse

Luhn
Luhn in Scala
object Luhn {

  def valid(input: String): Boolean = {
    if (input.find(chr => !chr.isDigit && !chr.isSpaceChar) != None)
      return false
    val cleanedInput = input.filter(Character.isDigit)
    if (cleanedInput.length() < 2) return false

    validRecur(1, 0, cleanedInput)
  }

  @scala.annotation.tailrec
  private def validRecur(pos: Int, sum: Int, input: String): Boolean = {
    if (input.isEmpty) return sum % 10 == 0
    val num = input.last.asDigit
    pos match {
      case pos if pos % 2 == 0 =>
        validRecur(
          pos + 1,
          sum + (if (num > 4) (num * 2) - 9 else num * 2),
          input.init
        )
      case _ => validRecur(pos + 1, sum + num, input.init)
    }
  }
}

This approach starts be calling the find() method on the String input to check for any characters that are not a digit or a space. If so, then the input is invalid and the method immediately returns false.

If there are no invalid characters, then the input is filter()ed to remove any spaces. If the length of the input without spaces is less than 2, then it is invalid and the method immediately returns false.

The method then returns the result of calling the recursive method, passing in 1 for the starting position, 0 for the sum, and the scrubbed input.

The validRecur() method is annotated with the @tailrec annotation to verify that the method can be compiled with tail call optimization.

A tail call is a particular form of recursion where the last call in the method is a call to the same method and nothing else.

In other words, if the last call in recurMe() is recurMe(arg1, arg2) + 1, the + 1 makes the recursion non-tail recursive.

If the last call in recurMe() is recurMe(arg1, arg2, acc + 1), then the recursion is a tail call, because only the method is being called with no other operation being peformed on it.

If the input String isEmpty, then the method returns if the sum is evenly divisible by 10.

Otherwise, an Int is set from the last character, and the position is then pattern matched for whether it is even or odd. The validRecur() method calls itself with the sum increased according to the position, the position increased by 1, and all but the last character returned by init(). Note that the original position, sum and input are not mutated, but new arguments are created from the operations on them, thus supporting immutability.

9th Oct 2024 · Found it useful?