Проверка правильности номера ISBN

Мне даны некоторые номера ISBN, например. 3-528-03851 (недействительно), 3-528-16419-0 (действительный). Я должен написать программу, которая проверяет, действительно ли номер ISBN.

Вот мой код:

def check(isbn):
    check_digit = int(isbn[-1])
    match = re.search(r'(\d)-(\d{3})-(\d{5})', isbn[:-1])

    if match:
        digits = match.group(1) + match.group(2) + match.group(3)
        result = 0

        for i, digit in enumerate(digits):
          result += (i + 1) * int(digit)

        return True if (result % 11) == check_digit else False

    return False

Я использовал регулярное выражение для проверки a), если формат действителен и b) извлекает цифры в строке ISBN. Хотя, похоже, он работает, будучи новичком на Python, я очень хочу узнать, как улучшить код. Предложения?

Ответ 1

Во-первых, попробуйте избежать кода, подобного этому:

if Action():
    lots of code
    return True
return False

Переверните его, поэтому основная часть кода не вложена. Это дает нам:

def check(isbn):
    check_digit = int(isbn[-1])
    match = re.search(r'(\d)-(\d{3})-(\d{5})', isbn[:-1])

    if not match:
        return False

    digits = match.group(1) + match.group(2) + match.group(3)
    result = 0

    for i, digit in enumerate(digits):
      result += (i + 1) * int(digit)

    return True if (result % 11) == check_digit else False

В коде есть некоторые ошибки:

  • Если контрольная цифра не является целым числом, это приведет к повышению значения ValueError вместо возврата False: "0-123-12345-Q".
  • Если контрольная цифра равна 10 ( "X" ), это приведет к повышению ValueError вместо возврата True.
  • Это предполагает, что ISBN всегда группируется как "1-123-12345-1". Это не так; ISBN сгруппированы произвольно. Например, действительна группировка "12-12345-12-1". См. http://www.isbn.org/standards/home/isbn/international/html/usm4.htm.
  • Это предполагает, что ISBN сгруппирован по дефисам. Также допустимы пробелы.
  • Он не проверяет, нет ли лишних символов; '0-123-4567819' возвращает True, игнорируя добавочный 1 в конце.

Итак, пусть это упростит. Во-первых, удалите все пробелы и дефисы и убедитесь, что регулярное выражение соответствует всей строке, скопировав ее в "^... $". Это гарантирует, что он отклонит слишком длинные строки.

def check(isbn):
    isbn = isbn.replace("-", "").replace(" ", "");
    check_digit = int(isbn[-1])
    match = re.search(r'^(\d{9})$', isbn[:-1])
    if not match:
        return False

    digits = match.group(1)

    result = 0
    for i, digit in enumerate(digits):
      result += (i + 1) * int(digit)

    return True if (result % 11) == check_digit else False

Затем исправьте контрольную цифру "X". Совместите контрольную цифру в регулярном выражении, так что вся строка проверяется регулярным выражением, а затем правильно преобразует контрольную цифру.

def check(isbn):
    isbn = isbn.replace("-", "").replace(" ", "").upper();
    match = re.search(r'^(\d{9})(\d|X)$', isbn)
    if not match:
        return False

    digits = match.group(1)
    check_digit = 10 if match.group(2) == 'X' else int(match.group(2))

    result = 0
    for i, digit in enumerate(digits):
      result += (i + 1) * int(digit)

    return True if (result % 11) == check_digit else False

Наконец, использование выражения генератора и max является более идиоматическим способом выполнения окончательного расчета в Python, и окончательное условие может быть упрощено.

def check(isbn):
    isbn = isbn.replace("-", "").replace(" ", "").upper();
    match = re.search(r'^(\d{9})(\d|X)$', isbn)
    if not match:
        return False

    digits = match.group(1)
    check_digit = 10 if match.group(2) == 'X' else int(match.group(2))

    result = sum((i + 1) * int(digit) for i, digit in enumerate(digits))
    return (result % 11) == check_digit

Ответ 2

Бесцельное улучшение: замените return True if (result % 11) == check_digit else False на return (result % 11) == check_digit

Ответ 3

проверьте это после того, как вы закончите ok:)

http://www.staff.ncl.ac.uk/d.j.wilkinson/software/isbn.py

и

http://chrisrbennett.com/2006/11/isbn-check-methods.html

РЕДАКТИРОВАТЬ: Извините за запутанность, я не видел ярлыка домашней работы, но, возможно, после окончания домашней работы вы можете видеть, что еще делали раньше, я думаю, вы можете многому научиться у другого кода; снова жаль: (

Ответ 4

  • Инициализация check_digit может поднять ValueError, если последний символ не является десятичной цифрой. Почему бы не вытащить контрольную цифру с вашим регулярным выражением вместо использования разрезания?
  • Вместо поиска вы, вероятно, должны использовать совпадение, если вы не хотите разрешать произвольный барабан в качестве префикса. (Кроме того, как правило, я привязывал конец к $, хотя в вашем случае это не имеет значения, поскольку ваше регулярное выражение имеет фиксированную ширину.)
  • Вместо того, чтобы вручную перечислять группы, вы можете просто использовать ''.join(match.groups()) и вытащить check_digit после этого. Вы могли бы также сделать преобразование в int, прежде чем вытащить его, так как вы все равно должны преобразовать все из них в int.
  • Ваш цикл for может быть заменен пониманием списка/генератора. Просто используйте sum() для добавления элементов.
  • True if (expression) else False обычно можно заменить просто expression. Аналогично, False if (expression) else True всегда можно заменить просто not expression

Объединяя все это:

def check(isbn):
    match = re.match(r'(\d)-(\d{3})-(\d{5})-(\d)$', isbn)
    if match:
        digits = [int(x) for x in ''.join(match.groups())]
        check_digit = digits.pop()
        return check_digit == sum([(i + 1) * digit
                                  for i, digit in enumerate(digits)]) % 11
    return False

Последняя строка, возможно, не нужна, поскольку по умолчанию было бы возвращать None (это ложь), но явные возвращения из некоторых путей, а не из других, выглядят как ошибка для меня, поэтому я считаю, что более читаемо оставить это в.

Ответ 5

Все эти регулярные выражения великолепны, если вы принадлежите инспекции соответствия isbn.org.

Однако, если вы хотите узнать, стоит ли использовать потенциальные клиенты в своем браузере, нужно ли вставлять запрос в свою базу данных книг для продажи, вы не хотите, чтобы все это было красивым красным мундиром. Просто выбросьте все, кроме 0-9 и X... о, да, никто не использует клавишу переключения, поэтому нам лучше разрешить x. Затем, если длина 10 и проходит тест контрольной цифры, стоит сделать запрос.

Из http://www.isbn.org/standards/home/isbn/international/html/usm4.htm

Контрольная цифра - это последняя цифра ISBN. Он рассчитывается по модулю 11 с весами 10-2, используя X вместо из 10, где десять будет происходить как проверка цифра.

Это означает, что каждый из девяти цифры ISBN - исключая сама контрольная цифра - умножается на число от 10 до 2 и что полученная сумма продуктов, плюс контрольная цифра, должны быть делится на 11 без остатка.

который является очень длинным способом сказать: "каждая цифра умножается на число от 10 до 1 и что итоговая сумма продуктов должна быть делимой на 11 без остатка"

def isbn10_ok(s):
    data = [c for c in s if c in '0123456789Xx']
    if len(data) != 10: return False
    if data[-1] in 'Xx': data[-1] = 10
    try:
        return not sum((10 - i) * int(x) for i, x in enumerate(data)) % 11
    except ValueError:
        # rare case: 'X' or 'x' in first 9 "digits"
        return False


tests = """\
    3-528-03851
    3-528-16419-0
    ISBN 0-8436-1072-7
    0864425244
    1864425244
    0864X25244
    1 904310 16 8
    0-473-07480-x
    0-473-07480-X
    0-473-07480-9
    0-473-07480-0
    123456789
    12345678901
    1234567890
    0000000000
    """.splitlines()

for test in tests:
    test = test.strip()
    print repr(test), isbn10_ok(test)

Вывод:

'3-528-03851' False
'3-528-16419-0' True
'ISBN 0-8436-1072-7' True
'0864425244' True
'1864425244' False
'0864X25244' False
'1 904310 16 8' True
'0-473-07480-x' True
'0-473-07480-X' True
'0-473-07480-9' False
'0-473-07480-0' False
'123456789' False
'12345678901' False
'1234567890' False
'0000000000' True
'' False

Кроме того, большой известный книготорговый сайт примет 047307480x, 047307480X и 0-473-07480-X, но не 0-473-07480-x :-O

Ответ 6

Ваш код хорош - хорошо сделано для написания идиоматического Python! Вот некоторые незначительные вещи:


Когда вы видите идиому

result = <initiator>
for elt in <iterable>:
    result += elt

вы можете заменить его на понимание списка. В этом случае:

result = sum((i+1)*int(digit) for i, digit in enumerate(digits)

или даже более кратко:

return sum((i+1)*int(digit) for i, digit in enumerate(digits) % 11 == check_digit

Конечно, это оценочное суждение, насколько это лучше оригинала. Я лично считаю, что второй из них лучше всего.

Кроме того, дополнительные скобки в (result % 11) == check_digit являются посторонними, и я действительно не думаю, что они вам нужны для ясности. Это оставляет вас в целом:

def validate(isbn):
    check_digit = int(isbn[-1])
    match = re.search(r'(\d)-(\d{3})-(\d{5})', isbn[:-1])

    if match:
        digits = match.group(1) + match.group(2) + match.group(3)
        parity = sum((i+1)*int(digit) for i, digit in enumerate(digits)
        return parity % 11 == check_digit
    else:
        return False

Обратите внимание, что вам все еще нужен return False, чтобы понять, что ISBN даже не в правильном формате.

Ответ 7

Не забывайте (хотя это может оказаться за пределами области вашего назначения), чтобы вычислить контрольную цифру ISBN (окончательную цифру), чтобы определить, действителен ли ISBN, а не просто выглядит действительно.

Там некоторая информация о реализации контрольной цифры на веб-сайте ISBN.org, и реализация должна быть довольно простой. Wikipedia предлагает один такой пример (предполагая, что вы уже конвертировали любой ASCII "X" в десятичный 10):

bool is_isbn_valid(char digits[10]) {
    int i, a = 0, b = 0;
    for (i = 0; i < 10; i++) {
        a += digits[i];  // Assumed already converted from ASCII to 0..10
        b += a;
    }
    return b % 11 == 0;
}

Применение этого для вашего задания оставлено, ну, как упражнение для вас.

Ответ 8

Ваша контрольная цифра может принимать значения 0-10, исходя из того, что она по модулю-11. Там проблема с линией:

    check_digit = int(isbn[-1]) 

поскольку это работает только для цифр 0-9. Вам понадобится что-то для случая, когда цифра "X", а также для условия ошибки, если она не является чем-то из вышеперечисленного - в противном случае ваша программа выйдет из строя.