ISBN Validation
May 20, 2011
The dashes and spaces in ISBN and EAN numbers are a nuisance, so our first function gets rid of them:
(define (clean isbn? str)
(let loop ((cs (string->list str)) (out '()))
(cond ((null? cs) (reverse out))
((and isbn? (null? (cdr cs)) (char-ci=? (car cs) #\X))
(reverse (cons (car cs) out)))
((char-numeric? (car cs))
(loop (cdr cs) (cons (car cs) out)))
(else (loop (cdr cs) out)))))
We validate ISBN numbers according to the method given above, being careful to handle the X check digit properly:
(define (isbn? str)
(let loop ((cs (clean #t str)) (mul 10) (sum 0))
(if (null? cs) (zero? (modulo sum 11))
(loop (cdr cs) (- mul 1) (+ sum (* mul
(if (char-ci=? (car cs) #\X) 10
(- (char->integer (car cs)) 48))))))))
Validating an EAN is simpler because everything is a digit; the expression (- 4 mul)
alternates the multiplier between 1 and 3:
(define (ean? str)
(let loop ((cs (clean #f str)) (mul 1) (sum 0))
(if (null? cs) (zero? (modulo sum 10))
(loop (cdr cs) (- 4 mul) (+ sum (* mul
(- (char->integer (car cs)) 48))))))))
Converting back and forth between ISBN and EAN isn’t hard, though it can be confusing to keep track of all the special constants involved:
(define (isbn->ean str)
(if (not (isbn? str)) (error 'isbn->ean "invalid isbn")
(let loop ((cs (clean #t str)) (ean '(#\8 #\7 #\9))
(mul 3) (sum 38))
(if (null? (cdr cs))
(list->string (reverse (cons (integer->char (+
(modulo (- 10 (modulo sum 10)) 10) 48)) ean)))
(loop (cdr cs) (cons (car cs) ean) (- 4 mul)
(+ sum (* mul (- (char->integer (car cs)) 48))))))))
(define (ean->isbn str)
(if (not (ean? str)) (error 'ean->isbn "invalid ean")
(let loop ((cs (drop 3 (clean #f str))) (isbn '())
(mul 10) (sum 0))
(if (null? (cdr cs))
(list->string (reverse
(cons (let ((d (modulo sum 11)))
(cond ((= d 0) #) ((= d 1) #\X)
(else (integer->char (- 59 d)))))
isbn)))
(loop (cdr cs) (cons (car cs) isbn) (- mul 1)
(+ sum (* mul (- (char->integer (car cs)) 48))))))))
To lookup the author and title, we steal the with-input-from-url
function of a previous exercise. The access key is provided by isbndb.com; you’ll have to contact them for your own:
(define access-key "12345678") ; not a valid access key
Here is the function to look up the author and title. It builds the query string to be sent to the isbndb.com server, executes the query, then reads the result line-by-line looking for the needed information:
(define (lookup-isbn isbn)
(with-input-from-url
(string-append "http://isbndb.com/api/books.xml?access_key="
access-key "&index1=isbn&value1="
(list->string (clean #t isbn)))
(lambda ()
(do ((str (read-line) (read-line))) ((eof-object? str))
(when (and (< 7 (string-length str))
(string=? (substring str 0 7) "<Title>"))
(display "Title: ")
(display (substring str 7 (- (string-length str) 8)))
(newline))
(when (and (< 13 (string-length str))
(string=? (substring str 0 13) ""))
(display "Authors: ")
(display (substring str 13 (- (string-length str) 14)))
(newline))))))
Isbndb.com provides much more than just author and title, which you can see with this function:
(define (display-isbndb.com isbn)
(with-input-from-url
(string-append "http://isbndb.com/api/books.xml?access_key="
access-key "&index1=isbn&value1="
(list->string (clean #t isbn)))
(lambda ()
(do ((c (read-char) (read-char))) ((eof-object? c))
(display c)))))
Here’s an example:
> (lookup-isbn (ean->isbn "978-0070004849"))
Title: Structure and interpretation of computer programs
Authors: Harold Abelson and Gerald Jay Sussman, with Julie Sussman; foreword by Alan J. Perlis
We used drop
and read-line
from the Standard Prelude. You can run the program at http://programmingpraxis.codepad.org/vxKNN1KE.
[…] today’s Programming Praxis exercise, our goal is to write a number of functions related to ISBN numbers. […]
My Haskell solution (see http://bonsaicode.wordpress.com/2011/05/20/programming-praxis-isbn-validation/ for a version with comments):
My Python submission
For the EAN validation, I opted against using
from itertools import cycle
to get a repeating list[1, 3, 1, 3,...]
, instead using2 + (-1)**x
asx
counted up.Just the verification bits, in PLT racket: