Реализация формулы Луна

Я пытался реализовать формулу Luhn в Python, вот мой код:

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()

Но это не работает должным образом:

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

и так далее...

Но логика кода кажется мне в порядке. Я следовал за этим рабочим процессом:

Формула Луна:

  1. Удалите последнюю цифру из числа. Последняя цифра - это то, что мы хотим проверить. Обратно числа.

  2. Умножьте цифры в нечетных позициях (1, 3, 5 и т. Д.) На 2 и вычтите 9 для всех результатов, превышающих 9

  3. Добавить все числа вместе

  4. Контрольная цифра (последний номер карты) - это сумма, которую нужно добавить, чтобы получить кратное 10 (по модулю 10).

10 ответов

Решение

Я думаю, что алгоритм не является правильным.

На втором шаге нужно сложить цифры продуктов вместо вычитки 9. Ссылка: Википедия.

В Википедии у вас есть этот пример:

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)

Результат:

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

Это самая краткая формула Python для теста Луна, которую я нашел:

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

Вышеупомянутая функция и другие реализации Luhn (на разных языках программирования) доступны по https://www.rosettacode.org/wiki/Luhn_test_of_credit_card_numbers.

Я хотел бы сделать это простым и легким для чтения, что-то вроде этого:

def luhn(value):
    digits = 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

Но есть множество способов сделать то же самое. Очевидно, что вам придется сделать это по-другому, чтобы увидеть фактический результат, это просто суммирует итоговое значение, чтобы определить, является ли значение допустимым.

Лучший!

Следующее может помочь некоторым людям начать с алгоритма Луна в 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!')

В вашем коде есть несколько ошибок:

result = divmod(sum_of_digits, 10)

возвращает кортеж, вам нужно только по модулю, то есть использовать

result = sum_of_digits % 10

Во-вторых, чтобы проверить правильность, вы не опускаете последнюю цифру (то есть контрольную сумму), но включаете ее в вычисления. использование

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

И проверьте, что результат равен нулю:

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

Или, если вы настаиваете на сохранении этой ненужной сложности, проверьте, чтобы последняя цифра была обратной контрольной суммы по модулю 10: keep

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

но использовать

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

Для более простого и короткого кода я могу дать вам ссылку на мой старый ответ.

Увидеть этот рецепт Python

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 )

Реализации формулы Луна в одной строке кода (без разрывов строк)

входной аргумент должен быть строкой

      def is_card_valid(card_number):
    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

с функцией карты

      def is_card_valid(card_number):
    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])

Учитывая строку цифр, мы можем определить значение каждой цифры путем преобразования в целое число. Это довольно очевидное утверждение. Однако при преобразовании в int базовый код должен выполнить многочисленные проверки, прежде чем выдать результат.

Если мы можем гарантировать , что мы всегда обрабатываем только символы, представляющие числа от 0 до 9, то поиск по словарю окажется быстрее.

Это приводит к чрезвычайно эффективной реализации алгоритма Луна, не требующей какого-либо целочисленного преобразования.

Используя timeit, я определил, что могу вызвать функцию isvalid() с 16-значной строкой миллион раз за ~1,1 с.

      from itertools import cycle

LMAP = {
    '0': 0,
    '1': 2,
    '2': 4,
    '3': 6,
    '4': 8,
    '5': 1,
    '6': 3,
    '7': 5,
    '8': 7,
    '9': 9
}

NMAP = {
    '0': 0,
    '1': 1,
    '2': 2,
    '3': 3,
    '4': 4,
    '5': 5,
    '6': 6,
    '7': 7,
    '8': 8,
    '9': 9
}

def isvalid(ccn: str) -> bool:
    c = cycle((NMAP, LMAP))
    _sum = 0
    for d in ccn[::-1]:
        _sum += next(c)[d]
    return _sum % 10 == 0

def check_digit(ccn: str) -> str:
    c = cycle((LMAP, NMAP))
    _sum = 0
    for d in ccn[::-1]:
        _sum += next(c)[d]
    return 10 - r if (r := _sum % 10) != 0 else 0

for ccn in ['4532015112830341', '4532015112830342']:
    print(isvalid(ccn))

print(check_digit('453201511283034'))

Выход:

      True
False
1

Основная проблема с вашей логикой заключается в том, что вы применяете некоторые вычисления, которые относятся только к нечетным цифрам и к четным цифрам.

У меня есть несколько предложений для вас:

  1. Вместо получения int в качестве входных данных получите строку, чтобы можно было обрабатывать числа с конечными нулями.
  2. Попробуйте применить понимание списка или используйтеmap при обработке списка
  3. Вы можете определить несколько вспомогательных функций, чтобы улучшить редактируемость кода.

Учитывая то, что я предложил, вот как я решил бы эту проблему. Обратите внимание, что я использую аннотации типов Python . Я также выделю шаги алгоритма Луна с комментариями.

      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

Теперь давайте проверим предыдущий код:

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

Я надеюсь, что этот ответ поможет вам или поможет другим людям, попавшим в затруднительное положение, понять, как вычислить чекер Луна.

Другие вопросы по тегам