سؤال

I was trying to implement the Luhn Formula in Python. Here is my code:

import sys


def luhn_check(number):
    if number.isdigit():
        last_digit = int(str(number)[-1])
        reverse_sequence = list(int(d) for d in str(int(number[-2::-1])))

        for i in range(0, len(reverse_sequence), 2):
            reverse_sequence[i] *= 2

        for i in range(len(reverse_sequence)):
            if reverse_sequence[i] > 9:
                reverse_sequence[i] -= 9

        sum_of_digits = 0
        for i in range(len(reverse_sequence)):
            sum_of_digits += reverse_sequence[i]

        result = divmod(sum_of_digits, 10)

        if result == last_digit:
            print("[VALID] %s" % number)
        else:
            print("[INVALID] %s" % number)
        quit()

    print("[ERROR] \" %s \" is not a valid sequence." % number)
    quit()


def main():
    if len(sys.argv) < 2:
        print("Usage: python TLF.py <sequence>")
        quit()

    luhn_check(sys.argv[1])

if __name__ == '__main__':
    main()

But it isn't working properly:

[INVALID] 4532015112830366
[INVALID] 6011514433546201
[INVALID] 6771549495586802

and so on...

But the logic of the code seems OK to me. I followed this workflow:

The Luhn Formula

  1. Drop the last digit from the number. The last digit is what we want to check against Reverse the numbers

  2. Multiply the digits in odd positions (1, 3, 5, etc.) by 2 and subtract 9 to all any result higher than 9

  3. Add all the numbers together

  4. The check digit (the last number of the card) is the amount that you would need to add to get a multiple of 10 (Modulo 10)

هل كانت مفيدة؟

المحلول

I think the algorithm is not correct.

The second step you need to sum the digits of the products instead of substract 9. Reference: Wikipedia.

In the Wikipedia you have this example:

def luhn_checksum(card_number):
    def digits_of(n):
        return [int(d) for d in str(n)]
    digits = digits_of(card_number)
    odd_digits = digits[-1::-2]
    even_digits = digits[-2::-2]
    checksum = 0
    checksum += sum(odd_digits)
    for d in even_digits:
        checksum += sum(digits_of(d*2))
    return checksum % 10

def is_luhn_valid(card_number):
    return luhn_checksum(card_number) == 0


result = is_luhn_valid(4532015112830366)
print 'Correct:' + str(result)
result = is_luhn_valid(6011514433546201)
print 'Correct:' + str(result)
result = is_luhn_valid(6771549495586802)
print 'Correct:' + str(result)

Result:

>>>Correct:True
>>>Correct:True
>>>Correct:True

نصائح أخرى

This is the most concise python formula for the Luhn test I have found:

def luhn(n):
    r = [int(ch) for ch in str(n)][::-1]
    return (sum(r[0::2]) + sum(sum(divmod(d*2,10)) for d in r[1::2])) % 10 == 0

The above function and other Luhn implementations (in different programming languages) are available in https://www.rosettacode.org/wiki/Luhn_test_of_credit_card_numbers .

I'd keep it simple and easy to read, something like this:

def luhn(value):
    digits = list(map(int,str(value))
    oddSum = sum(digits[-1::-2])
    evnSum = sum([sum(divmod(2 * d, 10)) for d in digits[-2::-2]])
    return (oddSum + evnSum) % 10 == 0

But there's tons of ways to do the same thing. Obviously, you'd have to do it differently to see the actual output, this just sums up the total to determine if the value is valid.

Best!

see this Python recipe

def cardLuhnChecksumIsValid(card_number):
    """ checks to make sure that the card passes a luhn mod-10 checksum """

    sum = 0
    num_digits = len(card_number)
    oddeven = num_digits & 1

    for count in range(0, num_digits):
        digit = int(card_number[count])

        if not (( count & 1 ) ^ oddeven ):
            digit = digit * 2
        if digit > 9:
            digit = digit - 9

        sum = sum + digit

    return ( (sum % 10) == 0 )

There are some errors in your code:

result = divmod(sum_of_digits, 10)

returns a tuple, you need only modulo, that is use

result = sum_of_digits % 10

Second, to check for validity, you don't omit last digit (that is checksum), but include it in computations. Use

reverse_sequence = list(int(d) for d in str(int(number[::-1]))) 

And check for result being zero:

if not result:
    print("[VALID] %s" % number)

Or if you insist on keeping this not needed complexity, check for last digit to be inverse of checksum modulo 10: keep

reverse_sequence = list(int(d) for d in str(int(number[-2::-1])))

but use

if (result + last_digit) % 10 == 0:
    print("[VALID] %s" % number)

For a simplier and shorter code, I can give you a reference to my older answer.

The following might help some people to start with the Luhn algorithm in python.

num = list(input("Please enter the number to test (no space, no symbols, only \
numbers): "))

num = list(map(int, num))[::-1] #let's transform string into int and reverse it

for index in range(1,len(num),2):
    if num[index]<5:
        num[index] = num[index] *2
    else: #doubling number>=5 will give a 2 digit number
        num[index] = ((num[index]*2)//10) + ((num[index]*2)%10)

checksum=sum(num)

print("checksum= {}".format(checksum))

if checksum%10 !=0:
    print('the number is not valid')
else:
    print('the number is valid!')

Implementations of Luhn Formula in one line of code (if without line breaks)

input argument must be a string

def is_card_valid(card_number: str) -> bool:
    return (sum(
        (element + (index % 2 == 0) * (element - 9 * (element > 4))
         for index, element in enumerate(map(int, card_number[:-1])))
    ) + int(card_number[-1])) % 10 == 0

with map function

def is_card_valid(card_number: str) -> bool:
    return (sum(
        map(lambda n: n[1] + (n[0] % 2 == 0) * (n[1] - 9 * (n[1] > 4)),
            enumerate(map(int, card_number[:-1])))
    ) + int(card_number[-1])) % 10 == 0
check_numbers = ['49927398716', '4847352989263095', '79927398713', '5543352315777720']

def Luhn_Check(number):
    """
        this function checks a number by using the Luhn algorithm.

        Notes (aka - How to Luhn) :
            Luhn algorithm works in a 1 2 1 2 ... order.
            Therefore, in computer speak, 1 0 1 0 ... order

            step 1:
                -> reverse the # so we are not working from right to left (unless you want too)
                -> double every second number

            step 2:
                -> if the doubled number is greater then 9, add the individual digits of the number to get a single digit number
                    (eg. 12, 1 + 2 = 3)

            step 3:
               -> sum all the digits, if the total modulo is equal to 0 (or ends in zero) then the number is valid...
    """

    reverse_numbers = [int(x) for x in number[::-1]] # convert args to int, reverse the numbers, put into a list
    dbl_digits = list() # create empty list
    digits = list(enumerate(reverse_numbers, start=1)) # enumerate numbers starting with an index of 1

    for index, digit in digits:
        if index % 2 == 0:  # double every second (other) digit.

            doub_digit = digit * 2
            dbl_digits.append(doub_digit - 9) if doub_digit > 9 else dbl_digits.append(doub_digit)

        else:
            # if not '0' append to list (this would be the 1 in Luhn algo sequence (1 2 1 2 ...)
            dbl_digits.append(digit)

    return sum(dbl_digits) % 10


if (__name__ == "__main__"):
    print("Valid Numbers: %s " % [x for x in check_numbers if Luhn_Check(x) == 0])

The main problem with your logic is that you are applying some computations that are only for the odd digits to the even digits too.

I have some suggestions for you:

  1. Instead of receiving an int as input, receive a string so you can handle numbers with trailing zeros.
  2. Try to apply list comprehensions or use map when processing a list
  3. You could define some helper functions to improve your code redability.

Considering what I have suggested, this is how I would solve this problem. Notice that I am using Python's type annotations. I will also highlight the steps of Luhn's algorithm with comments.

def compute_sum_of_remainig_digits(remaining_digits: List[int]) -> int:
    # use a list comprehension to extract digits in even positions
    even_positions = remaining_digits[-2::-2]
    # use a list comprehension to extract digits in odd positions
    odd_positions = remaining_digits[-1::-2]
    # 2. Multiply the digits in odd positions (1, 3, 5, etc.) by 2
    # and subtract 9 to all any result higher than 9
    computed_odd = [2 * d - 9 if 2 * d > 9 else 2 * d for d in odd_positions]
    # 3. Add all the numbers together
    return sum(even_positions + computed_odd)


def compute_checksum(number: str) -> int:
    # the following line convert a string of digits into list of digits using map
    # this way you do not need to explicitly declare a for loop
    digits = list(map(int, number))
    # 1. Drop the last digit from the number...
    last_digit = digits[-1]
    total = compute_sum_of_remainig_digits(digits[:-1 ])

    # 4. The check digit (the last number of the card) is
    # the amount that you would need to add...
    return (total + last_digit)


def check_valid_number(number: str) -> bool:
    # 4. ...to get a multiple of 10 (Modulo 10)
    return (compute_checksum(number) * 9) % 10 == 0

Now let's validate the previous code:

>>> valid_number = "4929355681587216"
>>> invalid_number = "5183814022326227"
>>> check_valid_number(valid_number)
True
>>> check_valid_number(invalid_number)
False

I hope this answer helps you or helps other people in trouble to understand how to compute a Luhn's checker.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top