Ruby:ランダムな文字列を作る

以前、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 より面倒だな。