バーコードのチェックサムとHaskellの練習
コンビニの商品にも、スーパーの商品にも、たいていの売り物にはバーコードがついていますね。 すだれハゲみたいなアレです。
http://ja.wikipedia.org/wiki/バーコード
日本の商品に使用されるバーコードは JANコード というもので、8桁あるいは13桁の数字を表しています。
バーコードの最後の1桁は「チェックデジット」というものです。 ざっくりいうと、8桁のバーコードの場合、最後の1桁は残りの7桁を全て足しあわせた数の'一の位'です。 *1
92384781
というバーコードであれば、1
がチェックデジットです。9+2+3+8+4+7+8 = 41
なので、41
の'一の位'である1
がバーコードの最後に来るわけです。
...というようなことを風のうわさで聞きました。
「バーコードにはチェックデジットなるものがついてるらしいよ」、と。
では、なぜバーコードにはチェックデジットなんて付いているのでしょうか。 13桁の数値なら、約10兆個の商品コードが生成できます。 そのうち1桁をチェックデジットに使ってしまうと、1兆個の商品コードしか作れないじゃないですか!梅雨明けてないじゃないスか!やだー!
ということでウンウン唸りながら考えて、しばらくしてポンと膝をたたきました。*2
「ヤツは、バーコードの誤認識を防ぐためにあるんだな!」
ということに気が付いたのです。
コンビニとか、スーパーって、ピッピがあるじゃないですか。分かりませんか?あの、赤い光が出ててピッピ言ってるアレです。
あいつが「ピッ」って言ったら、バーコードを読み込んだ証拠です。「なかなかピッて言わないなこいつ」ってことはあっても、「ピッって言ったくせに読み込んでないじゃないスかー!やだー!」ってことはありません。
でも、商品のバーコードってわりとヨレヨレだったりするじゃないですか。掠れてたり、汚れてたり、曲がってたり、濡れてたり。
92384781
ってバーコードなのに、間違えて72384781
って読み込んでしまったりしそうじゃないですか。
でも、コンビニの店員さんに「すみません、こいつがピッって言ったから『この商品はすでにインプットされた』と誤解されてしまったかと思います。しかし、今のはちょっとしたミスでして、もう一度読み込むのでちょっとお待ちいただけますか、このピッピはあとでよーく叱っておきますので、何卒ご理解とご協力をお願い致します」なんて謝られたことはありませんよね。僕はありません。
読み込み率100%です。すごいです。ピッピからピクシーに進化させてやりたいくらいです。ダッシュでおつきみやまでつきのいしを拾ってくる勢いです。
そこで、チェックデジットの出番です。
92384781
ってバーコードを間違えて72384781
って読んでしまっても、そこで「ピッ」とは言わないんです。
グッと我慢して、「こいつのチェックデジット合ってるかな」って確認するんです。
72384781
の場合、7+2+3+8+4+7+8=39
なのでチェックデジットは39
の'一の位'である9
ですが、72384781
の最後のひとけた目は1
になっている。
「であえー!であえー!クセモノだー!!」
となり、ピッピは「てへっ間違えちゃった」とつぶやきながら、正しいバーコードを読むために読み込みを継続するんですね。
なるほどーなるほどー。
最初に考えた人はすごいですねー。
ということで、チェックデジット付きのバーコードをランダムに吐き出すHaskellプログラムを書きました。
こわーいこわーいIOモナドの練習です。
「ある関数を同じ引数で呼び出せば、必ず同じ値が返ってくる」という参照透明性を持つHaskellでは、「ランダムな値」というのはクセモノです。 こういうクセモノに対処するには、嫌でもIOモナドと付き合う必要があります。 今回は、
- なるべく汚染されたIOモナドの使用を最小限にして、バーコード生成プログラムを書く
- 無限リストでバーコードを吐き出す
というのを目標にバーコードを生成してみました。
8桁のアルファベットで、チェックデジットにはXORを使用、O
は0
と読み間違えやすいので省いています。
感想としては「nub遅ぇ〜!!」「IOモナド難しい..Rubyで書いたらゆるふわ楽勝なのに..」ってかんじですね。 haskellのnubはなんでこんなに遅いのか、オフトゥンの中で考えてみようと思います。
*1:正確なことはのチェックデジット計算方法を御覧ください。