2010-10-28 19 views
11

Me dan algunos números de ISBN, p. Ej. 3-528-03851 (no válido), 3-528-16419-0 (válido). Se supone que debo escribir un programa que prueba si el número de ISBN es válido.Comprobando si un número de ISBN es correcto

Aquí está 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 utilizado una expresión regular para comprobar: a) si el formato es válido y b) extraer los dígitos en la cadena de ISBN. Si bien parece funcionar, al ser un principiante de Python estoy ansioso por saber cómo podría mejorar mi código. Sugerencias?

+0

me queda bien. Me gusta tu uso de cortar. Como señaló la otra persona, su expresión ternaria se puede simplificar con solo regresar (resultado% 11) == check_digit – I82Much

+1

También puede eliminar los "me gusta" en blanco, de acuerdo con PEP8

+6

+1 para cosas excepcionalmente buenas primero y luego preguntando por mejoras. ¡Buen trabajo! – dotalchemy

Respuesta

14

en primer lugar, tratar de evitar el código como este:.

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

Dale la vuelta, por lo que la mayor parte del código no está anidado. 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 entero, esto elevará ValueError en lugar de devolver Falso: "0-123-12345 -Q ".
  • Si el dígito de verificación es 10 ("X"), esto aumentará ValueError en lugar de devolver True.
  • Esto supone que el ISBN se agrupa siempre 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. Ver 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 adicional al final.

Así que, simplifiquemos esto. Primero, elimine todos los espacios y guiones, y asegúrese de que la expresión regular coincida con toda la línea al reforzarla en '^ ... $'. Eso asegura que rechaza cadenas 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, solucionemos el problema del dígito de control "X". Haga coincidir el dígito de control en la expresión regular también, de modo que toda la cadena sea validada por la expresión regular, 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 

Por último, utilizando una expresión generador y max es una forma más idiomática de hacer el cálculo final en Python, y la final condicional puede ser simplificado.

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

Bien hecho. El único defecto que veo es que se permiten guiones o espacios consecutivos, probablemente deberían marcarse como un error. –

+1

"Si el dígito de verificación es 11 (" X "): ¿Quiere decir 10? También, dijo que está usando' max', pero creo que quiso decir 'sum'. ::: Indeciso para editar las publicaciones de otras personas sin 100 % certeza ::: Además, hay otro error: los ISBN no están agrupados de forma totalmente arbitraria. El enlace al que apunta indica que siempre hay 4 grupos. Además, el 4º grupo siempre tiene 1 carácter de longitud. – Brian

+0

@Brian: Es válido para sin guiones, por ejemplo, al escribir un ISBN de un código de barras. También hay muchas otras reglas que podría validar si quisiera. Por ejemplo, la agrupación no es arbitraria. Generalmente, esto no importa cuando se ingresa un ISBN. ISBN, sin embargo, en realidad solo estás buscando errores tipográficos, y en cualquier caso está más allá del alcance de una tarea de introducción a la programación. Vale la pena señalar, sin embargo. –

3

mejora insustancial: sustituir return True if (result % 11) == check_digit else False con cheque return (result % 11) == check_digit

+0

Gracias por la pista. Después de 4 años de programación, debería saberlo mejor. Pero, de nuevo, es tarde y me he quedado sin café :-). – helpermethod

3

esto después de que haya terminado bien :)

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

y

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

EDIT: Perdón por lo confuso que no vi la etiqueta de la tarea pero tal vez después de terminar tu tarea puedes ver lo que otros han hecho antes, creo que puedes aprender mucho del código de los demás; Lo siento de nuevo :(

+1

Lo primero que debe hacer al escribir la tarea es ver si hay algo que pueda copiar. ¿Seriamente? –

+0

Creo que destruye completamente el punto de su tarea, lo que sugeriría se inclina más hacia el aprendizaje que Google buscando – dotalchemy

+0

Aunque generalmente estoy de acuerdo, la idea de la tarea era aplicar el algoritmo que hicimos en papel. Debería haberlo mencionado en mi pregunta. – helpermethod

1

Su código es agradable - bien hecho para escribir Python idiomática Aquí hay algunas cosas de menor importancia:


Cuando vea el idioma

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

se puede reemplazar! por una lista por comprensión En este caso:.

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

o incluso de manera más concisa:

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. Yo personalmente consideraría que el segundo de ellos es el mejor.

Además, los paréntesis adicionales en (result % 11) == check_digit son extraños y realmente no creo que los necesite 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 

importante hacer notar que todavía necesita la return False para atrapar el caso de que el ISBN no es ni siquiera en el formato correcto.

+0

+1 ¡Solución muy elegante! – helpermethod

+0

¿Qué tipo de excepción podría plantear si el formato de la cadena no coincide? – helpermethod

+2

No estoy seguro de que una excepción sea la manera de hacerlo aquí; al escribir un método 'validate' no es excepcional tener un argumento inválido. – katrielalex

1

No se olvide (aunque esto puede estar fuera del alcance de su asignación) para calcular el dígito de control del ISBN (el último dígito), para determinar si el ISBN es válida y no sólo aparentemente válida.

Hay información acerca de la implementación del dígito de control on the ISBN.org website, y la implementación debería ser bastante sencilla.Wikipedia ofrece un ejemplo de ello (suponiendo que ya se haya 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; 
} 

Aplicando este se deja para su asignación, así, como un ejercicio para usted.

+0

+1 Gracias, lo olvidé por completo. – helpermethod

+1

Si busca más información sobre los dígitos de verificación, solo asegúrese de buscar ISBN-10, no ISBN-13, ya que los métodos de los dígitos de verificación son diferentes (naturalmente). Alternativamente, sea aún más impresionante con su tarea, y maneje ambos casos automáticamente y regrese si es válido ISBN-10, ISBN-13 o inválido. –

+0

Buena idea, probablemente haga eso ^^. – helpermethod

1

Su dígito de control puede tomar los valores 0-10, basado en 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 sea 'X', y también para la condición de error cuando no sea cualquiera de las anteriores; de lo contrario, su programa se bloqueará.

3
  • La inicialización check_digit puede plantear una 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 cortar?
  • En lugar de buscar, probablemente debería utilizar coincidencias, a menos que desee permitir el uso de basura arbitraria como prefijo. (Además, como regla general ancle el final con $, aunque en su caso eso no tendrá importancia ya que su expresión regular es de ancho fijo.)
  • En lugar de enumerar manualmente los grupos, puede usar ''.join(match.groups()) , y saque el check_digit después. También podría hacer la conversión a int s antes de extraerlo, ya que de todos modos quiere convertirlos a int s.
  • su bucle for podría reemplazarse por una lista/comprensión del generador. Simplemente use sum() para sumar los elementos.
  • True if (expression) else False generalmente se puede reemplazar con simplemente expression. Del mismo modo, False if (expression) else True siempre se puede sustituir por simplemente not expression

poner que todos juntos:

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 por defecto sería volver Ninguno (que es Falsy), pero vuelve explícitas de algunas rutas y no de los demás parece un error para mí, así que yo creo que es más fácil de leer para dejarlo en

2

Todas esas cosas expresiones regulares es grande si usted pertenece a la inspección del cumplimiento isbn.org.

Sin embargo, si desea saber si lo que los clientes potenciales escriben en su navegador vale la pena presionar en una consulta de su base de datos de libros para la venta, no quiere que todo ese buen uniforme rojo se escape. Simplemente tira todo lo que no sea 0-9 y X ... oh sí, nadie usa la tecla Mayús, así que mejor también permitamos x. Entonces, si es 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 del un ISBN. Se calcula sobre un módulo 11 con pesos 10-2, usando X en lugar de de 10 donde diez ocurriría como un dígito de verificación .

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

que es una forma muy largo aliento de decir "cada uno de todos los dígitos se multiplica por un número que va de 10 a 1 y que la suma resultante de los productos debe ser divisible por 11, sin que sobre"

def isbn10_ok(s): 
    data = [c for c in s if c in 'Xx'] 
    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) 

de 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

Cuestiones relacionadas