ハッシュでeach.with_indexしたいです。


ハッシュでeach.with_indexしたい時

結論(本項目より後はポエムです)

hash = { a: 1, b: 2 }
hash.each.with_index(1) do |(key, value), index|
  # do something
end

で ok
( 1周目中 → index = 1 とする時)

経緯

array.each.with_index
を半年弱Rubyを触っていたのに知らず(バカ)、
他人のコードを見ていて知り、一目惚れしました。。。
(それまでは、ループ外でcountを宣言して、都度 += 1 していました)

最近知ったことはすぐ使いたくなる質なので
絶対使いたいと思って居たところで、
いざHashをeachするときに使おうと思ったら使えずに困ったときの話です。

# 知ってる
array = [1, 2, 3]
array.each do |v|
  # do something
end

# 知ってる
array.each.with_index do |v, index|
  # do something
end

# 知ってる
hash = { a: 1, b: 2 }
hash.each do |key, value|
  # do something
end

# という流れから、hashにeach.with_indexする場合を予想 
hash.each.with_index do |key, value, index|
  # do something
end

だろうと思った。

→  hash.each.with_index が思うように動かない
→ なんかvalueがおかしいしindexもおかしいしなんならkeyもおかしい

とりあえず、適当に動作確認しました。

a = {:key => ['va','lu','e']}

# ()で囲わないと、1つ目の引数にhashをflattenしたみたいなやつが来る
a.each.with_index do |k,v,i|
  p k
  p v
  p i
end
#=>
[:key, ["va", "lu", "e"]] 
0
nil

なんかよくわからないですが、 a.each.with_index do |k,v,i| が間違えていることだけはわかったので、
とりあえずHash each with index でググって一番上のページを見ましたら解決しました。
(記事下部に参考サイトとしてリンクを貼らせていただきました)


# やりたかったこと
a.each.with_index do |(k, v),i|
  p k
  p v
  p i
end

#=>
:key
["va", "lu", "e"]
0

# with_index(offset = 0)なので、1から数えたいときはoffsetに1を渡す
a.each.with_index(1) do |(k, v),i|
  p k
  p v
  p i
end
#=>
:key
["va", "lu", "e"]
1

蛇足(考察など)

a.each do |k|
  p k
end
# =>
[:key, ["va", "lu", "e"]]

落ち着いて見直すと
k, v を( )で囲わなかったときはこれと同じ挙動を示している。
多分( )で明示してここまでが引数だよと教えないと、
|k, v, i| の場合、kで諦めてしまうんだと思う。
そういう流れHashについてはkで諦めたので、本来iに入れたかったものがvに入る。
だからvが0でiがnilになってる。(予想)
Enumerator#with_index
を見ると
with_index(offset = 0) {|(*args), idx| ... } -> object
となっているし、多分、index以外の引数はカッコで括る必要がありそう。

ただ、そもそもなぜ hash.each do |k| で kにflattenなやつが来るのか分かっていない。
多重代入への理解が無いせいで理解ができないのかもしれないので、
詳しくはまた調べてわかったら追記しようと思う。

追記

ただ、そもそもなぜ hash.each do |k| で kにflattenなやつが来るのか分かっていない。

Hash#eachはkey,valueを渡す。
この時受け手が1つだと、たくさん渡された値を配列にして受け手が受け取る
flattenみたいなやつ思ってたやつは以下のような流れで出てきた奴だった。

hash = {:a => ['b', 'c']}
a = :a , hash[:a]
p a
#=>  [:a, ["b", "c"]]

index以外を( )で括る必要があるのは、正確にはhashだからではなく、
index以外の引数が複数あるからで、
複数あるなら( )でくくって渡すのが括弧で括るのがwith_indexの仕様のようだった。

参考にさせていただいたサイト様

ハッシュからeach_with_indexでkeyとvalとindexを取り出す。
(こちらはeach_with_indexの話ですが、each.with_indexも同じみたいです)
each.with_indexだとoffsetを渡せるので、ちょっと強いかなと思ってます。

謝辞

仔細な説明をくださった scivola さん、
記事の修正をくださった jnchito さん。
ありがとうございました。