Проверка правильности номера 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", а также для условия ошибки, если она не является чем-то из вышеперечисленного - в противном случае ваша программа выйдет из строя.