Ruby でブロック付きメソッド呼び出しを省略する際、最後の引数に &obj
という形で #to_proc
を持つオブジェクトを指定する。
経験的にはメソッド名の Symbol を指定することが圧倒的に多く、時々 Method や Proc という感じがする。 Hash も #to_proc
があるので指定できるけれども、使った覚えが無い。
Hash でなにか良い使い道が無いか考えたところ、リストの要素に番号(非負整数のID)を振って置換する際に便利そうだったのでやってみた。
コード
def ord() = Hash.new { |h, k| h[k] = h.size }
# 登場順に採番
p "abracadabra".each_char.map(&ord())
#=> [0, 1, 2, 0, 3, 0, 4, 0, 1, 2, 0]
# 小さい順に採番(座標圧縮)
ord_s = ord()
"mississippi".each_char.sort.each(&ord_s)
p "mississippi".each_char.map(&ord_s)
#=> [1, 0, 3, 3, 0, 3, 3, 0, 2, 2, 0]
p ord_s
#=> {"i"=>0, "m"=>1, "p"=>2, "s"=>3}
※ ord
の ()
は書かなくてもいいが、メソッドと変数を区別しやすいよう明示している
仕組み
リストの各要素について Hash のキーに対応する値を参照する場合は、わざわざブロックを書かなくても &obj
で済む。
h = {1 => 10, 2 => 20, 3 => 30}
seq = [1, 2, 3]
# これらは同じ
seq.map { |elem| h[elem] }
seq.map(&h)
今回の用途では「 Hash にキーが無ければ生成・保存する」という処理を追加している。
seq.map do |elem|
h[elem] = elem * 10 unless h.key?(elem)
h[elem]
end
これは Hash に default_proc を設定しておくことでブロックから追い出せる。(別のブロックを書くことにはなるが)
h.default_proc = ->(hash, key) { hash[key] = key * 10 }
seq.map do |elem|
h[elem]
end
採番については、ゼロ始まりとしておけば 採番済みの要素数 == 次の採番の番号
になるため、 Hash#size
で次の番号を作成できる。
おまけ
同じ考え方によって、即席のメモ化にも使える。コード長については普通に書いたほうが短いが、 Hash を外部の変数に用意せず使い捨てできる長所はある。
# 毎回 to_i を実行する
p "abracadabra".each_char.map { |s| s.to_i(36) }
#=> [10, 11, 27, 10, 12, 10, 13, 10, 11, 27, 10]
# 一度 to_i を実行した要素については、キャッシュした結果を返す
h = {}
p "abracadabra".each_char.map { |s| h[s] ||= s.to_i(36) }
# Hash をその場で作れば、キャッシュは使い捨てになる
p "abracadabra".each_char.map(&Hash.new { |h, s| h[s] = s.to_i(36) })