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.

Advertisement

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. […]

  2. My Haskell solution (see http://bonsaicode.wordpress.com/2011/05/20/programming-praxis-isbn-validation/ for a version with comments):

    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)))
    

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: