Luhnアルゴリズムでクレジットカードの番号をチェック

クレジットカード番号のチェックサムには Luhnアルゴリズムというのが使われているのを知った。

cf. Luhnアルゴリズムでクレジットカードの番号をチェック – brainstorm
cf. Luhnアルゴリズム – Wikipedia

Rubyでやってみた。

# coding: utf-8
#
# Check number with Luhn algorithm

def check_number(digits)
  even = false
  sum = digits.reverse.split(//).inject(0) do |s, d|
    d = d.to_i
    if even
      d = d * 2
      s -= 9 if d > 9
    end
    even = !even
    s += d
  end
  sum % 10 == 0
end


NUMBERS = [
  '5555555555554444',
  '5105105105105100',
  '4111111111111111',
  '4012888888881881',
  '3530111333300000',
  '3566002020360505',
  '30569309025904',
  '38520000023237',
  '378282246310005',
  '371449635398431',
  '378734493671000',
  '6011111111111117',
  '6011000990139424'
]

NUMBERS.each do |digits|
  puts check_number(digits)
end

カード番号の例は↓ここで紹介されているもの。
cf. ECサイトの動作テストに使える、クレジットカードのテスト番号一覧 – Webクリエイターボックス

実行結果:

^o^ > check_card_numbers.rb
true
true
true
true
true
true
true
true
true
true
true
true
true

RubyのArray#*

Ruby の Array#* がこんな挙動をするとは知らなかったよ。いつからだろう。

cf. Rubyにおける算術演算の基礎 – hp12c

試してみる。

irb(main):001:0> [1,2,3,4,5] * ","
=> "1,2,3,4,5"

パラメータが文字列の場合は、それをセパレータとして連結するってこと。Array#join と同じだってマニュアルにも書いてある。

cf. http://doc.ruby-lang.org/ja/1.9.3/class/Array.html

上記のマニュアルには、「(パラメータが)文字列以外のオブジェクトを指定した場合は to_str メソッドによる暗黙の型変換を試みます。」とも書いてある。

ところで Array#* のパラメータに整数を指定すると、配列を指定した整数回繰り返した配列を返す(これは当然知ってた)。で、マニュアルのその項には「(パラメータには)繰り返したい回数を整数で指定します。整数以外のオブジェクトを指定した場合は to_int メソッドによる暗黙の型変換を試みます。」って書いてある。

じゃあ、to_s と to_int の両方を持ったオブジェクトをパラメータにしたらどうなるんだろう。

irb(main):002:0> class Foo
irb(main):003:1>   def to_s
irb(main):004:2>     "3"
irb(main):005:2>   end
irb(main):006:1>   def to_int
irb(main):007:2>     3
irb(main):008:2>   end
irb(main):009:1> end
=> nil
irb(main):010:0> foo = Foo.new
=> 3
irb(main):011:0> [1,2,3,4,5] * foo
=> [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3, 4, 5]

どうやら、to_int のほうが優先されるみたいだ。

ISBN 出版者記号の割り当て規則

ISBNを扱うのに ISBN Tools というライブラリを使っている。

なんでかっていうと ISBN_Tools.hyphenate_isbn13 っていうメソッドがあって,数字だけのISBNをハイフンの入ったISBNに変換してくれるのが使えると思ったからだ。

こんな感じ:

irb(main):006:0> ISBN_Tools.hyphenate_isbn13('9780672328848')
=> "978-0-6723-2884-8"

ところがこのメソッド,肝心の日本(グループ番号4)に対応してくれてない。

ソースを見たところ,data/ranges.dat にグループ番号と出版者記号の定義を追加すればいいみたいだ。

というわけでちょっと調べてみた。

日本国内のことなんだから日本のISBNを管理しているところへいけばいいんだろう,と思ったんだけど,その日本図書コード管理センターのサイトの中を探してみても規則で割り当ててるのか,どうもどこにも載ってない。

同じ不満をもっる人がネットのあちこちにいるみたいだな。どうなってんだ。

で,結局 ISBN の本家 International ISBN Agency のサイトでPDFを見つけた。これでいいんだよな。

これによると日本の出版者記号は下のような規則になっているようだ。

2ケタ 00~19
3ケタ 200~699
4ケタ 7000~8499
5ケタ 85000~89999
6ケタ 900000~949999
7ケタ 9500000~9999999

さて,ISBN Tools に戻ろう。

調べた成果を反映するには前述の data/ranges.dat に次の1行を追加すればいい。

4,00..19,200..699,7000..8499,85000..89999,900000..949999,9500000..9999999

カンマ区切りになってて,はじめの値がグループ番号,2つめ以降に出版者記号の連続する範囲を列挙している。これで日本の出版者にも対応するようになった。

irb(main):007:0> ISBN_Tools.hyphenate_isbn13('9784863540224')
=> "978-4-8635-4022-4"

10ケタのISBNでもできる。

irb(main):008:0> ISBN_Tools.hyphenate_isbn10('4873110238')
=> "4-87311-023-8"

Windows上のApacheでFastCGIを動かすメモ

自分用のメモ。

cf. Windows + Apache + FastCGI – h4yの日記

cf. WindowsでRuby on Rails その4 Ruby on RailsをFastCGIで動かす – 色々な事を忘れないよう忘備録と日記

FastCGIのダウンロードは公式サイトダウンロードコーナーから。Apache のバージョンが 2.0.63 なので mod_fastcgi-2.4.2-AP20.dll をダウンロードした。これを mod_fastcgi.dll にリネームして,Apache の modules ディレクトリにコピー。

つぎは Ruby 側の準備。gem で fcgi をインストールしようとしたけど ruby のヘッダファイルがないって怒られる:

^o^ >gem install fcgi --remote
Building native extensions.  This could take a while...
ERROR:  Error installing fcgi:
ERROR: Failed to build gem native extension.
C:/usr/ruby/bin/ruby.exe extconf.rb install fcgi --remote
can't find header files for ruby.
Gem files will remain installed in C:/usr/ruby/lib/ruby/gems/1.8/gems/fcgi-0.8.7
for inspection.
Results logged to C:/usr/ruby/lib/ruby/gems/1.8/gems/fcgi-0.8.7/ext/fcgi/gem_mak
e.out

ググってみると,RubyForApache というのが見つかったので version 1.3.1 をダウンロード。なんか2005年って古いんだけど。

ファイルはインストーラなのでダブルクリックしてインストール。途中でインストールするコンポーネントを聞かれるので,mod_fastcgi 以外のコンポーネントのチェックをはずす。mod_rubyとmysql.soは今回はパス。

あと C:\Windows\System32\msvcp71.dll に書き込めないというメッセージが出るけど,これは無視して進める。

最後に Apache の設定。httpd.conf につぎの行を追加する。

LoadModule fastcgi_module modules/mod_fastcgi.dll
AddHandler fastcgi-script .fcgi

Apache を再起動して終了。

テスト用のスクリプトはこんなの。

#! C:/usr/ruby/bin/ruby.exe
require 'fcgi'
FCGI.each_cgi do |cgi|
print "Content-Type: text/plain\n\n"
print "Hello world. This is FastCGI.\n"
end

それと ExcecCGI を有効にしておく必要があるみたいなので .htaccess で設定する。

Options +ExecCGI

Ruby 1.9 で FizzBuzz

Ruby 1.9.1 もリリースされたことだし,新機能(Fiber と Array#cycle)を使ってFizzBuzz を書いてみたよ。d:id:takatoh:20070509:fizzbuzz のRuby版。

cf. Ruby 1.9.1 の歩き方 – るびま 0025号

# -*- encoding: utf-8 -*-
fizzbuzz = Fiber.new do
fizz = ["", "", "Fizz"].cycle
buzz = ["", "", "", "", "Buzz"].cycle
n = 1
while
s = fizz.next + buzz.next
Fiber.yield(s == "" ? n.to_s : s)
n += 1
end
end
100.times{ puts fizzbuzz.resume }

実行結果は省略。

追記:

Fiber ってスレッドに似た云々とかいうより,途中で評価を中断出来る(&途中の値を返せる)クロージャだと考えた方がわかりやすいんじゃないかと思った。

METHINKS IT IS LIKE A WEASEL

cf. どう書く?org – METHINKS IT IS A WEASEL

ずいぶん前に「ブラインドウォッチメイカー」を読んだ書いてみたもの。出遅れだし,お題ともちょっと違うので投稿せずにここにさらしておく。

IDEAL = "METHINKS IT IS LIKE A WEASEL"
class Individual
def initialize(phenotype)
@phenotype = phenotype.upcase
end
attr_reader :phenotype
def delivery
next_phenotype = @phenotype.dup
next_phenotype[rand(@phenotype.size)] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ ".slice(ra nd(27), 1)
Individual.new(next_phenotype)
end
def breed(n)
next_generation = []
n.times do
next_generation << delivery
end
next_generation
end
def to_s
@phenotype
end
end
class Nature
def initialize(ideal)
@ideal = ideal
end
def select(group)
r = []
max = 0
group.each do |i|
s = evaluate(i)
if s > max
max = s
r = [i]
elsif s == max
r << i
end
end
r[0]
end
def evaluate(ind)
phenotype = ind.phenotype
score = 0
0.upto(@ideal.size - 1) do |i|
score += 1 if phenotype[i] == @ideal[i]
end
score
end
end
if __FILE__ == $0
require 'optparse'
def err_exit(msg)
$stderr.print msg
exit
end
options = {:number => 100}
opts = OptionParser.new
opts.banner = "Usage: ruby miilaw.rb [option] initial\n"
opts.on_tail('-h', '--help', 'show this massage.') {puts opts; exit(0)}
opts.on('-n', '--number=NUM', Integer, 'set number of children.') {|options[:numbe r]|}
opts.parse!
err_exit(opts.help) if ARGV.empty?
len = IDEAL.size
init = ARGV.shift.upcase
if init.size < len
init = "%-#{len}s" % init
elsif init.size > len
init = init[0,len]
end
individual = Individual.new(init)
nature = Nature.new(IDEAL)
genaration = 0
print "#{genaration} : #{individual}\n"
while true do
genaration += 1
next_gen = individual.breed(options[:number])
individual = nature.select(next_gen)
print "#{genaration} : #{individual}\n"
break if individual.phenotype == IDEAL
end
end

HTMLを整える

Hpricot とかで Web ページをスクレイピングするときに,対象のページを解析するのが結構めんどくさい。

ページによっては人間が読むのを想定しているとは思えない(というか大抵は想定してない)ような HTML で,読むのにひどく骨が折れる。なのでせめて見やすくなるように整形するスクリプトを書いてみた。

探せばちょうどいいツールがありそうだけど。

コマンドライン引数でファイル名か URL を指定する。

require 'rubygems'
require 'hpricot'
require 'open-uri'
def show_html(elem)
s = ""
elem.search("/*").each do |e|
if e.instance_of?(Hpricot::XMLDecl)
s << e.to_s
elsif e.instance_of?(Hpricot::DocType)
s << e.to_s
elsif e.instance_of?(Hpricot::Text)
s << e.to_s
elsif e.instance_of?(Hpricot::Elem)
s << "<#{e.name}"
e.attributes.each do |k, v|
s << " #{k}=#{v.inspect}"
end
s << ">\n"
s << show_html(e).gsub(/^/, "  ")
end
end
s
end
src = open(ARGV.shift, "r"){|f| f.read }
src.gsub!(/^\s+/, "")
doc = Hpricot(src)
print(show_html(doc).gsub(/^\s*\n/, ""))

出力(の一部)はこんな感じ。要素のネストをインデントで表現するようにした。

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta content="text/html; charset=euc-jp" http-equiv="Content-Type">
<meta content="text/css" http-equiv="Content-Style-Type">
<meta content="text/javascript" http-equiv="Content-Script-Type">
<title>
2007-12-18 - Haskell はスケるよ
<link href="/takatoh/" title="Haskell \244\317\245\271\245\261\244\353\244\350" rel="start">
<link href="/help" title="\245\330\245\353\245\327" rel="help">
<link href="/takatoh/20071216" title="\301\260\244\316\306\374" rel="prev">
<link href="/css/base.css" rel="stylesheet" media="all" type="text/css">
<link href="/headerstyle?color=lg" rel="stylesheet" media="all" type="text/css">
<link href="/theme/hatena_carving-blue/hatena_carving-blue.css" rel="stylesheet" media="all" type="text/css">
<link href="http://d.hatena.ne.jp/takatoh/rss" title="RSS" rel="alternate" type="application/rss+xml">
<link href="http://d.hatena.ne.jp/takatoh/rss2" title="RSS 2.0" rel="alternate" type="application/rss+xml">
<link href="http://d.hatena.ne.jp/takatoh/foaf" title="FOAF" rel="meta" type="application/rdf+xml">
<link href="http://d.hatena.ne.jp/takatoh/opensearch/diary.xml" title="Haskell \244\317\245\271\245\261\244\353\244\350\306\342\306\374\265\255\270\241\272\367" rel="search" type="application/ope nsearchdescription+xml">
<link href="http://d.hatena.ne.jp/takatoh/opensearch/archive.xml" title="Haskell \244\317\245\271\245\261\244\353\244\350\306\342\260\354\315\367\270\241\272\367" rel="search" type="application/o pensearchdescription+xml">
<link href="http://d.hatena.ne.jp/images/lg_favicon.ico" rel="shortcut icon">
<style type="text/css">
<link href="http://d.hatena.ne.jp/takatoh/mobile?date=20071218" rel="alternate" type="text/html" media="handheld">
<script type="text/javascript" src="http://d.hatena.ne.jp/js/prototype-1.4.0.js">
<script type="text/javascript" src="http://d.hatena.ne.jp/js/textinput_description.js">
<script type="text/javascript" src="/js/embed_movie_player.js">
<script type="text/javascript">
Event.observe(window, 'load', function() {
new TextInputDescription($('comment-username'), $('comment-form'), 'なまえ');
(以下略)

余計な空行は消してるつもりなのにどういうわけか残ってる。まぁ,目的は達成できてるようなのでいいか。

ダブル完全数

cf. どう書く?.org – ダブル完全数

HaskellのほうがRubyよりすっきりしてるな。

divisors n = filter ((==0).mod n) [1..(n `div` 2 + 1)]

isDoublePerfectNumber n = (sum.divisors) n == (n*2)

main = mapM_ (putStrLn.show) $ filter isDoublePerfectNumber [1..10000]
def divisors(n)
  (1..(n/2+1)).to_a.select{|x| n % x == 0 }
end

def double_complete_number?(n)
  divisors(n).inject(0){|a,b| a+b } == 2 * n
end

(1..10000).to_a.each do |n|
  puts n if double_complete_number?(n)
end

結果は同じ(あたりまえ)だけど,Rubyのほうが速かった。

^o^ >runhaskell dpn.hs
120
672
^o^ >ruby dpn.rb
120
672

長方形の交差判定

cf. どう書く?.org – 長方形の交差判定

問題文中の top < bottom は間違いじゃないかと書いたら,グラフィックイメージの座標だと考えればOKだとコメントをもらった。なるほど。

判定方法は,要するに一方の長方形の4つある頂点のどれかか,もう一方の長方形の内部にあれば重なってると判定していいわけだ。

今日はRubyで書いた。

class Rect
def initialize(left, top, right, bottom)
@left = left
@top = top
@right = right
@bottom = bottom
end
def vertexes
[ [@left, @top],
[@left, @bottom],
[@right, @bottom],
[@right, @top] ]
end
def inner?(x,y)
(@left < x && x < @right) && (@top < y && y < @bottom)
end
def overlap?(rect)
rect.vertexes.any?{|x,y| inner?(x,y) }
end
end
r1 = Rect.new(  0,   0, 100, 100)
r2 = Rect.new(100,   0, 200, 100)
r3 = Rect.new( 50,  50, 150, 100)
p r1.overlap?(r2)                   # => false
p r1.overlap?(r3)                   # => true
p r2.overlap?(r3)                   # => true

追記:

このコードだと長方形がX方向とY方向の両方にずれていないと正しく判定できないことに気づいた。極端な話,ぴったりと重なっている長方形が「重なっていない」判定になる。

irb(main):001:0> r1 = Rect.new(0,0,100,100)
=> #<Rect:0x499f9f4 @top=0, @left=0, @bottom=100, @right=100>
irb(main):002:0> r1.overlap?(r1)
=> false

なんてこったい。