Comprobando si un número ISBN es correcto

Me dan algunos números de ISBN, por ejemplo, 3-528-03851 (no válido), 3-528-16419-0 (válido). Se supone que debo escribir un progtwig que pruebe si el número ISBN es válido.

Aquí ‘mi código:

 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 

He usado una expresión regular para verificar a) si el formato es válido yb) extrae los dígitos en la cadena ISBN. Si bien parece que funciona, al ser un principiante de Python, estoy ansioso por saber cómo podría mejorar mi código. Sugerencias?

Primero, trata de evitar un código como este:

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

Dale la vuelta para que la mayor parte del código no esté nested. Esto nos da:

 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 

Hay algunos errores en el código:

  • Si el dígito de control no es un número entero, esto generará ValueError en lugar de devolver False: “0-123-12345-Q”.
  • Si el dígito de control es 10 (“X”), esto boostá ValueError en lugar de devolver True.
  • Esto supone que el ISBN siempre se agrupa como “1-123-12345-1”. Ese no es el caso; Los ISBN se agrupan arbitrariamente. Por ejemplo, la agrupación “12-12345-12-1” es válida. Consulte http://www.isbn.org/standards/home/isbn/international/html/usm4.htm .
  • Esto supone que el ISBN está agrupado por guiones. Los espacios también son válidos.
  • No comprueba que no haya caracteres adicionales; ‘0-123-4567819’ devuelve Verdadero, ignorando el 1 extra al final.

Entonces, simplifiquemos esto. Primero, elimine todos los espacios y guiones, y asegúrese de que la expresión regular coincida con toda la línea colocándola en ‘^ … $’. Eso asegura que rechace las cadenas que son demasiado largas.

 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 

A continuación, vamos a solucionar el problema de dígitos de verificación “X”. Igualar también el dígito de verificación en la expresión regular, de modo que la expresión regular valida toda la cadena, luego convierta el dígito de verificación correctamente.

 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 

Finalmente, usar una expresión generadora y max es una forma más idiomática de hacer el cálculo final en Python, y el condicional final se puede simplificar.

 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 

Mejora inútil: reemplazar return True if (result % 11) == check_digit else False con return (result % 11) == check_digit

comprueba esto después de que hayas terminado ok 🙂

http://www.staff.ncl.ac.uk/djwilkinson/software/isbn.py

y

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

EDITAR: Perdón por lo confuso que no vi la etiqueta de la tarea, pero tal vez después de terminar la tarea pueda ver lo que otros han hecho antes, creo que se puede aprender mucho del código de otros; Lo siento de nuevo 🙁

  • La inicialización de check_digit puede generar un ValueError si el último carácter no es un dígito decimal. ¿Por qué no sacar el dígito de control con su expresión regular en lugar de usar rebanar?
  • En lugar de buscar, probablemente deberías usar la coincidencia, a menos que quieras permitir una basura arbitraria como prefijo. (Además, como regla general, anclaría el final con $ , aunque en su caso eso no importará ya que su expresión regular es de ancho fijo).
  • En lugar de listar manualmente los grupos, puede usar ''.join(match.groups()) , y sacar el check_digit después. Puede hacer la conversión a int s antes de retirarlo, ya que desea convertirlos todos a int s de todos modos.
  • Su bucle for podría ser reemplazado por una lista / comprensión de generador. Solo usa sum() para sumr los elementos.
  • True if (expression) else False generalmente puede reemplazarse con una simple expression . Del mismo modo, False if (expression) else True siempre se puede reemplazar con simplemente not expression

Poniendo todo junto:

 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 

La última línea es posiblemente innecesaria, ya que el comportamiento predeterminado sería devolver Ninguno (lo cual es falso), pero los retornos explícitos de algunos caminos y no de otros me parecen un error, así que creo que es más legible dejarlo.

Todo ese material de expresiones regulares es excelente si pertenece a la inspección de cumplimiento de isbn.org.

Sin embargo, si desea saber si lo que los clientes potenciales escriben en su navegador vale la pena incluir en una consulta de su base de datos de libros a la venta, no querrá toda esa bonita capa de uniforme rojo. Simplemente deseche todo menos 0-9 y X … oh, sí, nadie usa la tecla de mayúsculas, así que es mejor que permitamos x también. Entonces, si tiene una longitud de 10 y pasa la prueba de verificación de dígitos, vale la pena hacer la consulta.

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

El dígito de control es el último dígito de un ISBN. Se calcula en un módulo 11 con ponderaciones 10-2, utilizando X en lugar de 10, donde diez aparecerían como un dígito de control.

Esto significa que cada uno de los primeros nueve dígitos del ISBN, excluyendo el propio dígito de control, se multiplica por un número que va de 10 a 2 y que la sum resultante de los productos, más el dígito de control, debe ser divisible por 11 sin un rest.

que es una manera muy larga de decir “cada uno de los dígitos se multiplica por un número que va de 10 a 1 y que la sum resultante de los productos debe ser divisible por 11 sin un rest”

 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) 

Salida:

 '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 

Aparte: un gran sitio de venta de libros conocido aceptará 047307480x, 047307480X y 0-473-07480-X pero no 0-473-07480-x :-O

No olvide (aunque esto puede estar fuera del scope de su asignación) para calcular el dígito de control del ISBN (el dígito final), para determinar si el ISBN es válido y no solo aparentemente válido .

Hay algo de información sobre la implementación del dígito de control en el sitio web ISBN.org , y la implementación debe ser bastante sencilla. Wikipedia ofrece uno de estos ejemplos (suponiendo que ya ha convertido cualquier ASCII “X” a un decimal 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; } 

La aplicación de esto para su tarea se deja, bueno, como un ejercicio para usted.

Tu código es bueno, ¡bien hecho para escribir Python idiomático! Aquí hay algunas cosas menores:


Cuando ves el idioma

 result =  for elt in : result += elt 

Puedes reemplazarlo por una lista de comprensión. En este caso:

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

o incluso más concisamente:

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

Por supuesto, es un juicio de valor si esto es mejor que el original. Personalmente consideraría que el segundo de estos es el mejor.

Además, los paréntesis adicionales en (result % 11) == check_digit son extraños y realmente no creo que los necesites para mayor claridad. Eso te deja en general con:

 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 

Tenga en cuenta que todavía necesita el return False para detectar el caso de que el ISBN no tenga el formato correcto.

El dígito de verificación puede tomar los valores 0-10, según el hecho de que es módulo-11. Hay un problema con la línea:

  check_digit = int(isbn[-1]) 

ya que esto funciona solo para los dígitos 0-9. Necesitará algo para el caso cuando el dígito es ‘X’, y también para la condición de error cuando no es ninguna de las anteriores, de lo contrario, su progtwig se bloqueará.