Posted at

繰り返しのHashを楽に書きたかった

More than 1 year has passed since last update.


はじめに

railsを勉強していた時に、Hashを楽に書きたくなった。

{"0回"=>0, "1回"=>1, "2回"=>2, "3回"=>3, "4回"=>4, "5回"=>5, "6回"=>6, "7回"=>7, "8回"=>8, "9回"=>9, "10回"=>10}

10個くらいならまだしも、これが50個、100個となると到底やっていられない。そこで、「以下のようにするともっと楽に書くことができるよ」by先輩エンジニア。


hash.rb

(0..10).reduce(Hash.new) { |sum, i| sum.merge("#{i}回" => i) }


僕「」

これだけ見ても意味するところがよくわからなかったので、一つ一つ自分で確認してみた。その後追いで、そのまま順番に書いてみる。


目標



  • .storeを理解する


  • .eachを理解する


  • .reduceを理解する


  • .mergeを理解する

  • Hashに慣れたい


.storeを理解する

一番はじめに、初心者らしくwhile文を使って書いてみる。


test.rb

a = Hash.new

i = 0

while i <= 10 do
a.store("#{i}回", i)
i += 1
end

puts a


はじめに空っぽのHashであるaを用意して、そこに一つずつ.storeを使ってHashの要素を足していく。これはイメージがつきやすい。

.storeについてはこちら


.eachを理解する

次に、while文を使わないで同じものを書いてみる。


test.rb

a = Hash.new

(0..10).each do |i|
a.store("#{i}回", i)
end

puts a


これもわかりやすい。0から10の範囲で、.eachを使って一つずつ、.storeでHashの要素を足している。

.eachについてはこちら


.reduceを理解する

さて、個人的にはここからが若干わかりにくかった。

.reduceが何をするのか。Rubyリファレンスを見てみる。(.reduceメソッドは.injectメソッドと同じなので、そちらを参考にする)


injectメソッドは、ブロックを使って繰り返し計算を行うのに使います。ブロックに順に「要素1、要素2」、「ブロックの前回の戻り値、要素3」、「ブロックの前回の戻り値、要素4」、...を渡します。メソッドの戻り値はブロックが最後に返した値になります。


よくわからないので具体的に見てみる。


test.rb

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

puts numbers.reduce {|sum, n| sum + n } #=> 55

これが何をしているのか。「繰り返し計算」であることを意識して考える。

まず1周目。sumに要素の一つ目の1,nに二つ目の2が入り、戻り値としてsum + n = 1 + 2 = 3が戻る。

二周目。sumに前回の戻り値のsum + n = 3が入る。nには要素の3つ目の3が入る。戻り値は同様に6となる。

三周目。sumに前回の戻り値の6が入る。nには要素の4つ目の4が入る。戻り値は同様に10となる。

.

.

.

以下同様に、ここでは10個目の要素である10が足されるまで続く、返り値をsum + nとしたことで、与えられた数の総和を返してくれた。

これが.reduceがやってくれていること。


test.rb

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

puts numbers.reduce(45) {|sum, n| sum + n } #=>100

上のように.reduceの直後に()の中に値を入れてあげることで、一番はじめにsumに入れる初期値を設定することもできる。

.reduceについては別名でもある.injectの解説を参照してもらいたい。

これがHashにどう関わってくるかは一旦保留。


.mergeを理解する

とりあえずリファレンスを読んでみる。


mergeメソッドは、2つのハッシュを統合します。レシーバhashと引数other_hashのキーと値を集め、新しいハッシュに入れて返します。重複するキーがあるときは、other_hashの値が使われます。


同じく、よくわからないのでとりあえずやってみる。


test.rb

image1 = { "1回目" => 1, "2回目" => 2 }

image2 = { "3回目" => 3, "4回目" => 4 }
puts image1.merge(image2) #=>{"1回目"=>1, "2回目"=>2, "3回目"=>3, "4回目"=>4}

.mergeだけだと意外とわかりやすい。要するにHashの後ろに別のHashを付け足してくれるメソッドということなのか。

.mergeについてはこちら


冒頭のを理解する

改めて先のコードを見てみる。


hash.rb

(0..10).reduce(Hash.new) { |sum, i| sum.merge("#{i}回" => i) }


さて、意味を考えてみよう。出てくるメソッドは.reduce.mergeだ。(.newはまぁ置いておいて)

順番に考えていく。.reduceなので繰り返し計算だ。

初期値として(Hash.new)として空っぽのHashが与えられている。

この初期値ををsumに入れて、iに(0..10)から順番に数を入れていく。一周目なのでここでは0。

返り値は、sum、つまり空っぽのHashに("0回" => 0)の要素を足したHash、{"0回" => 0}だ。

次に二周目。

sumに前回の返り値の{"0回" => 0}を、iには順番なので1を入れる。

返り値は、sum、つまり{"0回" => 0}に("1回" => 1)の要素を足したHash、{"0回" => 0, "1回" => 1}だ。

.

.

.

これを11周(0から10なので)くりかえすので、結果として

{"0回"=>0, "1回"=>1, "2回"=>2, "3回"=>3, "4回"=>4, "5回"=>5, "6回"=>6, "7回"=>7, "8回"=>8, "9回"=>9, "10回"=>10}

このHashが返される。理解できた!!!!!


終わりに

時間はそこそこかかったものの、自力で理解できた。とりあえずググってみればヒントはたくさん転がっているので、自力で考える癖をつけること。そしてTry and Errorを繰り返すことが大事なのだと実感した。

P.S. これは自分の解釈なので、間違っているところがあればご指摘いただけると嬉しいです。

もっとうまい書き方などもあればぜひ教えてください!