ISBNのチェックデジット

結城浩さんの日記にISBNのチェックデジットについて書かれていた。

ISBNの最初の9桁をベクトルだと考え、 (10, 9, 8, 7, 6, 5, 4, 3, 2)というベクトルとの内積をmod 11した結果は ISBNの最後の1桁(10ならばX)になるらしいです。

と書かれている。が、実際のISBNコードを使って計算してみると合わない。実はこれは間違いで、正しくは

(内積値 + c) mod 11 = 0

となる最小の c がチェックデジットになるのだ。(from: ISBNチェックデジット計算

プログラムを書いてみる

簡単な計算ではあるが、手計算だと面倒(特に内積の計算)だし、ここ2ヶ月程Schemeのプログラミングに軽くはまっているので、ちょちょいとプログラムを書いてみる。関数名のセンスの悪さは見逃してちょ。

(define (inner-product x y)
  (eval (cons '+ ((lambda (x y)
                    (map * x y)) x y))
        (interaction-environment)))

(define (find-check-digit p c)
  (if (zero? (modulo (+ p c) 11)) c
	(find-check-digit p (+ c 1))))

(define (isbn-check-digit n)
  (find-check-digit (inner-product n '(10 9 8 7 6 5 4 3 2)) 0))

一昨日のエントリーで紹介したパスポート・ブルーの単行本第2巻、第3巻のISBNコード(4091256120、4091256139)を例にやってみると、

> (isbn-check-digit '(4 0 9 1 2 5 6 1 2))
0
> (isbn-check-digit '(4 0 9 1 2 5 6 1 3))
9

このプログラムでは find-check-digit で c を0からカウントアップしながらチェックデジットを求めているように、上述の条件をそのままプログラムにしたのだ。しかしこれは、

c = (11 - 内積値) mod 11

とすることができるので

(define (isbn-check-digit2 n)
  (modulo (- 11 (inner-product n '(10 9 8 7 6 5 4 3 2))) 11))

という関数も書ける。

どんな入力値でもたった1回のmodだけで済む後者の方が計算量的には優れているのだが、なんかプログラムコードの体裁がイマイチな気がする。定数項が多いからかな。前者の方がなんとなくLispっぽくて(Schemeだけど)いいかなーとも思ったり。

しゃーないので、全部ひとつの関数にして(簡約と言っていいのかな)定数項や変数を省いてみる。

(define (isbn-check-digit3 n)
  (modulo (- 11 (eval
                  (cons '+ ((lambda (x)
                              (map * x '(10 9 8 7 6 5 4 3 2))) n))
                  (interaction-environment))) 11))

だいぶマシになったかな。

もうちっとエレガントに書けないかね。アイディア求む。

続き