tawara's blog

雑記。個人の見解です。

ネストしたhashのvalueを加工する

弊社には技術に関する質問チャンネルがある。Ruby力がないため恥をしのんで質問したら、たくさんの知見を含む回答がもらえたので、忘れないように書いておく。

## 問題

一番深いhashのvalueを[]で囲った文字列にしたい。

irb(main):015:1* hash = {3=>{0=>"テキスト"},
irb(main):016:1*  4=>{0=>"5"},
irb(main):017:1*  5=>{0=>"tawara@example.com"},
irb(main):018:1*  6=>{0=>"2022-08-01"},
irb(main):019:1*  7=>{0=>"テキストエリア"},
irb(main):020:1*  8=>{0=>"ruby"},
irb(main):021:1*  9=>{0=>"ウルトラマン|ゼットン|ゴジラ"},
irb(main):022:1*  10=>{0=>"オードリー"},
irb(main):023:1*  11=>{0=>"ラジオ"},
irb(main):024:1*  12=>{0=>"ポム", 1=>"リト", 2=>"ナガシマ"},
irb(main):025:1*  13=>{0=>"hoge", 3=>"huga", 4=>"hoo"},
irb(main):026:1*  14=>{0=>"大三元|四暗刻", 3=>"小四喜|大四喜", 4=>"地和|天和"},
irb(main):027:0>  15=>{0=>"雑種"}}

irb(main):029:0> hash
=>
{3=>{0=>"[テキスト]"},
 4=>{0=>"[5]"},
 5=>{0=>"[tawara@example.com]"},
 6=>{0=>"[2022-08-01]"},
 7=>{0=>"[テキストエリア]"},
 8=>{0=>"[ruby]"},
 9=>{0=>"[ウルトラマン|ゼットン|ゴジラ]"},
 10=>{0=>"[オードリー]"},
 11=>{0=>"[ラジオ]"},
 12=>{0=>"[ポム]", 1=>"[リト]", 2=>"[ナガシマ]"},
 13=>{0=>"[hoge]", 3=>"[huga]", 4=>"[hoo]"},
 14=>{0=>"[大三元|四暗刻]", 3=>"[小四喜|大四喜]", 4=>"[地和|天和]"},
 15=>{0=>"[雑種]"}}

## 回答1

hash を配列[k,v]に変換、vをさらに[kk, vv]に変換する処理のなかでvvに[]をつけてからhashに戻し、さらにhashに戻すやり方。

irb(main):032:0> hash.map{ |k, v| [k, v.map{ |kk, vv| [kk, "[#{vv}]"] }.to_h] }.to_h
=>
{3=>{0=>"[テキスト]"},
 4=>{0=>"[5]"},
 5=>{0=>"[tawara@example.com]"},
 6=>{0=>"[2022-08-01]"},
 7=>{0=>"[テキストエリア]"},
 8=>{0=>"[ruby]"},
 9=>{0=>"[ウルトラマン|ゼットン|ゴジラ]"},
 10=>{0=>"[オードリー]"},
 11=>{0=>"[ラジオ]"},
 12=>{0=>"[ポム]", 1=>"[リト]", 2=>"[ナガシマ]"},
 13=>{0=>"[hoge]", 3=>"[huga]", 4=>"[hoo]"},
 14=>{0=>"[大三元|四暗刻]", 3=>"[小四喜|大四喜]", 4=>"[地和|天和]"},
 15=>{0=>"[雑種]"}}

## 回答2

rails6で追加された #deep_transform_valuesメソッドを使う。めっちゃわかりやすい。

https://api.rubyonrails.org/classes/Hash.html#method-i-deep_transform_values

irb(main):033:0> hash.deep_transform_values { |v| "[#{v}]" }
=>
{3=>{0=>"[テキスト]"},
 4=>{0=>"[5]"},
 5=>{0=>"[tawara@example.com]"},
 6=>{0=>"[2022-08-01]"},
 7=>{0=>"[テキストエリア]"},
 8=>{0=>"[ruby]"},
 9=>{0=>"[ウルトラマン|ゼットン|ゴジラ]"},
 10=>{0=>"[オードリー]"},
 11=>{0=>"[ラジオ]"},
 12=>{0=>"[ポム]", 1=>"[リト]", 2=>"[ナガシマ]"},
 13=>{0=>"[hoge]", 3=>"[huga]", 4=>"[hoo]"},
 14=>{0=>"[大三元|四暗刻]", 3=>"[小四喜|大四喜]", 4=>"[地和|天和]"},
 15=>{0=>"[雑種]"}}

ちなみにRuby3ならhash.deep_transform_values { "[#{_1}]" }と書ける。引数が少なくておしゃれ。

## 回答3

ruby2.4から追加された#transform_valuesメソッドを見つけたので、こうも書ける。

irb(main):034:1* hash.values.each do |v|
irb(main):035:1*   v.transform_values{|vv| "[#{vv}]"}
irb(main):036:0> end
=>
[{0=>"テキスト"},
 {0=>"5"},
 {0=>"tawara@example.com"},
 {0=>"2022-08-01"},
 {0=>"テキストエリア"},
 {0=>"ruby"},
 {0=>"ウルトラマン|ゼットン|ゴジラ"},
 {0=>"オードリー"},
 {0=>"ラジオ"},
 {0=>"ポム", 1=>"リト", 2=>"ナガシマ"},
 {0=>"hoge", 3=>"huga", 4=>"hoo"},
 {0=>"大三元|四暗刻", 3=>"小四喜|大四喜", 4=>"地和|天和"},
 {0=>"雑種"}]

やまびこのように知見が返ってくるのは組織で働く喜びですね。

(了)