以前、Python で UUID をもとにしてランダムな文字列を作るってのをやった。
同じことを Ruby でやろうとしたら存外に手間を食ったのでメモしておく。
UUID は 標準ライブラリの SecureRandom
モジュールで生成できる。
irb(main):001:0> require "securerandom"
=> true
irb(main):002:0> u = SecureRandom.uuid
=> "4ad31376-3c83-49d7-a70a-14467f2b4022"
が、Python では UUID
クラスのインスタンスが返ってきてバイト列を得るのも簡単だったけど、Ruby では文字列が返ってくる。なので、16進数表示の文字列からバイト列に変換してやらないといけない。とりあえず邪魔なハイフンを取り除いておく。
irb(main):003:0> h = u.tr("-", "")
=> "4ad313763c8349d7a70a14467f2b4022"
で、バイト列に変換するメソッドを書く。
はじめは2文字ずつに切り分けて String#to_i
で整数に変換してやればいいかと思ったけど、これだと得られるのは Fixnum
クラスのインスタンス(の配列)であってバイト列じゃない。
結局次のようになった。2文字ずつ切り分けるのは同じだけど、切り出した2文字は別々に整数に変換して、1文字目のほうは 4bit 左シフトしてから2文字目のほうと論理OR をとる。そうすると整数16個の配列ができる。これをバイト列に変換するには Array#pack
を使う。
irb(main):004:1* def hex2bstring(hex) irb(main):005:1* barray = [] irb(main):006:2* hex.split("").each_slice(2) do |pair| irb(main):007:2* barray.push((pair[0].to_i(16) << 4) | (pair[1].to_i(16))) irb(main):008:1* end irb(main):009:1* barray.pack("C") irb(main):010:0> end => :hex2bstring
このメソッドを使ってバイト列に変換。
irb(main):011:0> b = hex2bstring(h) => "J\xD3\x13v<\x83I\xD7\xA7\n\x14F\x7F+@\""
さらに Base32 エンコードするんだけど、Ruby の標準ライブラリには Base32 がないので、あらかじめ gem install base32
しておく必要がある。エンコードしたらパディングの =
を取り除く。
irb(main):012:0> require "base32" => true irb(main):013:0> s = Base32.encode(b) => "JLJRG5R4QNE5PJYKCRDH6K2AEI======" irb(main):014:0> s2 = s.tr("=", "") => "JLJRG5R4QNE5PJYKCRDH6K2AEI"
最後は見た目のランダムさを増すために小文字を混ぜるようにする。if
の条件式には Array#sample
を使って [true, false].sample
としてもよかったんだけど、せっかく UUID を生成するのに SecureRandom
を使ってるのでここでも使ってみた。でもわかりにくいかも。
irb(main):015:1 def down(c) irb(main):016:2* if SecureRandom.random_number(2) > 0 irb(main):017:2* c.downcase irb(main):018:2* else irb(main):019:2* c irb(main):020:1* end irb(main):021:0> end => :down
最終的にはこうなった。
irb(main):022:0> s2.split("").map{|c| down(c) }.join
=> "JljRg5r4qnE5pjyKCRdH6k2aei"
Python より面倒だな。