## ISBN Validation

### May 20, 2011

In a previous exercise we studied Luhn’s algorithm for validating credit card numbers. In today’s exercise we will study the algorithms for validating 10-digit and 13-digit ISBN book numbers, and fetch the author and title of the book from an internet server.

The term ISBN refers to the 10-digit book number that libraries and bookstores use to catalog their collections. The ISBN number consists of four variable-length parts, separated by dashes or spaces, that always have 10 digits in total: the first part is a region, the second part is a publisher, the third part is a title, and the fourth part is a modulo-11 check digit, which may be a digit 0 through 9, or the letter X, which represents 10. To validate the number, ignore the dashes and spaces, multiply the ten digits by 10, 9, 8, …, 1 and sum the digital products; if the sum is 0 modulo 11, the ISBN number is valid.

The term EAN refers to the new 13-digit book numbers. The EAN number consists of five variable-length parts, separated by dashes or spaces, that always have 13 digits in total: the first part is the three digits 9 7 8, the second, third and fourth parts are the region, publisher and title of the ISBN, and the fifth part is a check digit. To validate the number, ignore the dashes and spaces, multiply the ten digits by 1, 3, 1, 3, …, 1 (alternating 1 and 3) and sum the digital products; if the sum is 0 modulo 10, the EAN number is valid.

In practice, the dashes or spaces are frequently omitted. It is easy to convert between ISBN and EAN by adding or deleting the 978 prefix and recomputing the check digit. The standard definitions of ISBN and EAN are available at http://www.isbn.org/standards/home/isbn/international/html/usm7.htm. There are several internet servers that provide free ISBN lookup services; we will use the one at http://isbndb.com.

Your task is to write functions that validate ISBN and EAN numbers, convert between them, and fetch the title and author associated with an ISBN from an internet server. When you are finished, you are welcome to read or run a suggested solution, or to post your own solution or discuss the exercise in the comments below.

Pages: 1 2

### 4 Responses to “ISBN Validation”

1. […] today’s Programming Praxis exercise, our goal is to write a number of functions related to ISBN numbers. […]

```import Control.Applicative hiding ((<|>), optional)
import Data.Char
import Data.List
import Data.Map (elems)
import Network.HTTP
import Text.HJson
import Text.HJson.Query
import Text.Parsec

isbn = (++) <\$> (concat <\$> sepEndBy1 (many1 d) (oneOf " -"))
<*> option [] ([10] <\$ char 'X') where
d = read . return <\$> digit
ean = string "978" *> optional (oneOf " -") *> isbn

isbnCheck, eanCheck :: Integral a => [a] -> a
isbnCheck n = 11 - mod (sum \$ zipWith (*) [10,9..] (take 9 n)) 11
eanCheck n = mod (sum \$ zipWith (*) (cycle [1,3]) (take 9 n)) 10

validISBN, validEAN :: String -> Bool
validISBN = valid isbn isbnCheck
validEAN = valid ean eanCheck

valid p c = either (const False) v . parse p "" where
v ds = length ds == 10 && c ds == last ds

toISBN, toEAN :: String -> Maybe String
toISBN = convert ean isbnCheck
toEAN = fmap ("978-" ++) . convert isbn eanCheck

convert p c = either (const Nothing) (Just . fixCheck) . parse p ""
where fixCheck n = map intToDigit (init n) ++ [check \$ c n]
check n = if n == 10 then 'X' else intToDigit n

lookupISBN :: String -> IO [(String, [String])]
lookupISBN = get . ("http://openlibrary.org/api/books?format=json&\
\jscmd=data&bibkeys=ISBN:" ++) where
f ~(JObject j) = map (\b -> (unjs \$ key "title" b,
map (unjs . key "name") . getFromArr \$ key "authors" b)) \$ elems j
key k = head . getFromKey k
unjs ~(JString s) = s
get url = fmap (either (const undefined) f . fromString) .
getResponseBody =<< simpleHTTP (getRequest url)
```
3. Graham said

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 using `2 + (-1)**x` as `x` counted up.

4. Eric Hanchrow said

Just the verification bits, in PLT racket:

```#! /bin/sh
#| Hey Emacs, this is -*-scheme-*- code!
exec racket --require "\$0" --main -- \${1+"\$@"}
|#

;; https://programmingpraxis.com/2011/05/20/isbn-validation/

#lang racket
(require rackunit rackunit/text-ui)

(define (digitchar->number d)
(if (char=? (char-downcase d) #\x)
10
(- (char->integer d)
(char->integer #\0))))

(define (groups str)
(regexp-split #rx"[- \t]+" str))

(define (->digits . strings)
(map digitchar->number (append* (map string->list strings))))

(define (->checksum constant digits)
(apply + (map * digits constant)))

(provide validate-ISBN/EAN)
(define (validate-ISBN/EAN str)
(match (groups str)
[(list region publisher title check)
(zero? (remainder
(->checksum (build-list 10 (curry - 10))
(->digits region publisher title check))
11))]
[(list "978" region publisher title check)
(zero? (remainder
(->checksum (build-list 10 (lambda (i) (if (even? i ) 1 3)))
(->digits region publisher title check))
10))]
[_ #f]))

(define-test-suite validate-ISBN/EAN-tests
(check-true  (validate-ISBN/EAN "0-330-28987-X"))
(check-true  (validate-ISBN/EAN "0- 330 -28987--X"))
(check-false (validate-ISBN/EAN "1-330-28987-X"))
(check-false (validate-ISBN/EAN "frotz plotz"))

(check-true  (validate-ISBN/EAN "978-0-440-22378-8"))
(check-false (validate-ISBN/EAN "978-0-441-22378-8")))

(define-test-suite all-tests
validate-ISBN/EAN-tests)

(provide main)
(define (main . args)
(exit (run-tests all-tests 'verbose)))
```