class Luhn:
def __init__(self, card_num):
self.isValid = Luhn.luhny_bin(0, 0, list(card_num[::-1]))
def valid(self):
return self.isValid
@staticmethod
def luhny_tune(num):
return dbl - 9 if (dbl := 2 * num) > 9 else dbl
@staticmethod
def luhny_bin(pos, sum, chars):
if not chars:
return pos > 1 and sum % 10 == 0
else:
head, *tail = chars
if head.isdigit():
if not pos % 2:
return Luhn.luhny_bin(pos + 1, sum + int(head), tail)
else:
return Luhn.luhny_bin(pos + 1, sum + Luhn.luhny_tune(int(head)), tail)
if head == " ":
return Luhn.luhny_bin(pos, sum, tail)
return False
The Luhn
object is initialized with the card_num
value, which is the number to be validated with the Luhn algorithm.
The result of the validation is returned from Luhn
's valid()
method.
In this approach, a member variable is set to the result of running the Luhn algorithm.
That variable is returned from the valid()
method.
The methods that do the work have the @staticmethod
decorator.
This indicates that the method belongs to the class and is not recreated for every object instance.
In the code example the __init__
method uses slicing syntax ([::-1]
) to reverse the characters in the input,
which is then passed into the list constructor.
The luhny_bin()
method takes that list, along with two 0
values that represent the initialized values for the position and the sum.
The luhny_bin()
can call itself, which is a behavior called recursion.
Since luhny_bin()
can call itself, the first thing it does is to check that it is done calling itself.
This check is called the terminating condition. It's critical to have a terminating condition, since every call of a recursive function to itself places another frame on the stack. If there is no terminating condition, then the recursive function will keep calling itself until the stack runs out of space and a stack overflow error will occur.
The luhny_bin()
method should terminate when there are no more characters to process.
By using the falsiness of an empty list, the not
operator can be used instead of comparing the len()
of the list to 0
.
When all of the characters have been iterated, the method returns if the position is greater than 1
and if the sum is evenly divisible by 10
.
While there are still characters in the list to iterate, the list is destructured into head, *tail
.
The isdigit()
method is used to see if the head character is a digit.
If so, the modulo operator is used to check if the character's position is evenly divided by 2
.
By using the falsiness of 0
, the not
operator can be used instead of comparing equality to 0
.
It can be thought of as the expression not having a remainder.
If the position is evenly divided by 2
, then it is even, and the character is converted to an int()
and will be added to the sum variable.
If the position is odd, then the number is converted to an int
and is passed to the static method which always doubles it,
and will subtract 9
from the doubled value if the doubled value is greater than 9
.
It does this using a ternary operator.
Inside the ternary operator an assignment expression assigns the doubled value to the dbl
variable with (dbl := 2 * num)
.
The ternary operator returns dbl - 9
if dbl
is greater than 9
, otherwise it returns dbl
.
The resulting value will be added to the sum variable.
Whether the digit is even or odd, the position is added to 1
when it and the sum are passed into the next call of the recursive method.
Also passed in is the tail of the list, which is the list of all the remaining characters after the head character.
Note that the sum and position variables are not being directly changed.
(In other words, they are not being mutated.)
The new sum and position values are calculated as the new arguments to the recursive method.
If the head character is a space, the recursive method calls itself with the same position and sum values, and the tail.
If the head character is neither a digit or a space, the method returns False
.