10桁・13桁のISBNを相互に変換するPerlワンライナー
ISBN(世界的に使われている書籍の識別番号)には、10桁のものと13桁のものがあります。
http://ja.wikipedia.org/wiki/ISBN
仕事でISBNを相互に変換するプログラムが必要になったのですが、CPANモジュール(Business::ISBN)を使うほどの処理でもないし、単純な実装のものが見つからなかったので自前で実装しました。ついでに、これワンライナーでも書けるんじゃね、と思い立ったのでPerlのワンライナーも作ってみました(ちょっと冗長なのでワンライナーにする意味はあまり無かったかなぁとも思いつつ)。
ISBN-13 → ISBN-10
perl -le '@a=map{$s+=(10-$i++)*$_;$_;}(grep{/\d/}split(//,shift))[3..11];$d=(11-$s%11)%11;print @a,$d==10?q{X}:$d' [ISBN]
ISBN-13 → ISBN-10(ハイフン付き)
perl -le '@a=map{$s+=(10-$i++)*$_;$_;}(grep{/\d/}split(//,shift))[3..11];$d=(11-$s%11)%11;printf(qq{%d-%d%d%d%d-%d%d%d%d-%d\n},@a,$d==10?q{X}:$d' [ISBN]
ISBN-10 → ISBN-13
perl -le '@a=map{$s+=($i++%2?1:3)*$_;$_;}(grep{/\d/}split(//,shift))[0..8];print 978,@a,(10-($s+8)%10)%10' [ISBN-10]
ISBN-10 → ISBN-13(ハイフン付き)
perl -le '@a=map{$s+=($i++%2?1:3)*$_;$_;}(grep{/\d/}split(//,shift))[0..8];printf(qq{978-%d-%d%d%d%d-%d%d%d%d-%d\n},@a,(10-($s+8)%10)%10)' [ISBN-10]
[ISBN-13] あるいは [ISBN-10] を実際の ISBNに置き換えて実行してください。指定するISBNは、ハイフン有りでも無しでも構いません。
ワンライナーですので、エラー処理などはしていません。不正なISBNを渡すと不正な値を返すと思います。
ちゃんと書くと、以下のようになるのでしょうかね。
# ISBN13 → ISBN10 sub isbn13_to_10 { my ( $isbn, $hyphen ) = @_; $isbn =~ s/\D//g; return if ( ! $isbn || $isbn !~ /^\d{13}$/ ); my @isbn10 = (split(//, $isbn))[3..11]; push @isbn10, isbn10_digit( @isbn10 ); return sprintf( "%d-%d%d%d%d-%d%d%d%d-%d", @isbn10 ) if ( $hyphen ); return join( '', @isbn10 ); } # ISBN-10 のチェックディジットを求める sub isbn10_digit { my @ints = @_; my $i = 0; my $sum = 0; foreach my $int ( @ints ) { $sum += (10 - $i) * $int; $i++; } my $digit = ( 11 - $sum % 11 ) % 11; return ($digit == 10 ? 'X' : $digit); } # ISBN10 → ISBN13 sub isbn10_to_13 { my ( $isbn, $hyphen, $prefix ) = @_; $prefix ||= '978'; $isbn =~ s/[^0-9x]//ig; return if ( ! $isbn || $isbn !~ /^\d{9}[0-9x]$/i ); my @isbn13 = ( split(//, $prefix), (split(//, $isbn))[0..8] ); push @isbn13, isbn13_digit( @isbn13 ); return sprintf( "%d%d%d-%d-%d%d%d%d-%d%d%d%d-%d", @isbn13 ) if ( $hyphen ); return join( '', @isbn13 ); } # ISBN-13 のチェックディジットを求める sub isbn13_digit { my @ints = @_; my $i = 0; my $sum = 0; foreach my $int ( @ints ) { $sum += ($i % 2 ? 3 : 1) * $int; $i++ } return ( 10 - $sum % 10 ) % 10; } # ISBNとして正しくない場合、正しいISBNを返す # 正しいISBNを求められない場合は、-1 を返す sub isnt_isbn { my $isbn = shift; $isbn =~ s/[^0-9x]//ig; return -1 if ( ! $isbn ); if ( $isbn =~ /^\d{9}[0-9x]$/i ) { my @arr = (split(//, $isbn))[0..8]; my $isbn10 = join( '', @arr, isbn10_digit( @arr ) ); return $isbn10 if ( uc( $isbn ) ne $isbn10 ); return; } elsif ( $isbn =~ /^\d{13}$/ ) { my @arr = (split(//, $isbn))[0..11]; my $isbn13 = join( '', @arr, isbn13_digit( @arr ) ); return $isbn13 if ( $isbn ne $isbn13 ); return; } return -1; }