雑草SEの備忘録

東大を卒業し、SEとして働くことになった。備忘録的に作業を綴る。

Ruby on Rails初心者が一歩進むために~2~

タイトルの通り。Ruby on Railsを勉強していてこの使い方知ってるとだいぶ幅が広がるなというのがあったので、メモ。

今回は、reduceです。エイリアスでinjectというのもあって、両者は同じ挙動をします。

1.reduce, inject

http://ref.xaio.jp/ruby/classes/enumerable/inject

reduceやinjectの使い方ですが、上記サイトには、次のように書かれている。

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

うん。。よくわからない。
それで、Exampleを見てみる。

numbers = [4, 3, 9, 8, 5, 6, 1, 7, 2]
puts numbers.inject {|sum, n| sum + n }
puts numbers.inject(100) {|diff, n| diff - n }

これですぐ理解できるという人はすごい。私は少し悩んだ。
numbersという配列があって、その要素を一つ一つをとりだすのが、

numbers.inject {|sum, n| sum + n }

のnの部分。それで、sumはどこからでてくるのかというと、injectの引数。一つ目の例は、引数が省略されているが、引数が無い時は、

numbers.inject(0) {|sum, n| sum + n }

と同じ。sumはこのinjectの引数(この場合は0)を受ける。injectはブロックをとるので、その中で処理を書く。今回の例の場合は、
0に4を足して4、
4に3を足して9、
・・・
43に2を足して45
という感じ。つまり、各要素に対して繰り返し同じ処理をしたいときに使える。
引数を二つとってこんな書き方もできる。

numbers.inject(0, :+)

一つ目の引数は初期値。二つ目の引数はメソッド名。今回の場合は足し算。
Rubyはメソッドをシンボルにして呼び出すことが良くある。public_sendなんかはその最たる例であろう。

2.each_with_object

http://ref.xaio.jp/ruby/classes/enumerable/each_with_object

tags = [
  {href: "http://example1.com", tag: "tag1", style: "class1"},
  {href: "http://example2.com", tag: nil, style: "class2"},
  {href: nil, tag: "tag3", style: "class3"},
  {href: "http://example4.com", tag: "tag4", style: "class4"}
]

このような配列があったとする。hrefやtagがnilのない配列を作りたいとする。
そういうときは、each_with_objectを使ってこのように書くことができる。

tags.each_with_object([]) do |tag, array|
  array << { href: tag[:href], tag: tag[:tag], style: tag[:style] } unless tag[:href].nil? || tag[:tag].nil?
end

結果はこちら。

=> [{:href=>"http://example1.com", :tag=>"tag1", :style=>"class1"},
 {:href=>"http://example4.com", :tag=>"tag4", :style=>"class4"}]

each_with_objectではブロックの第2引数の処理結果が返り値となる。
今回の場合だと、arrayという変数だ。初期値は[]で空の配列。
次のように書くのと同じ動きをする。

# 変数の初期化
array = []
tags.each do |tag|
  next if tag[:href].nil? || tag[:tag].nil?
  array << { href: tag[:href], tag: tag[:tag], style: tag[:style] }
end

変数arrayを見ると、

[25] pry(main)> array
=> [{:href=>"http://example1.com", :tag=>"tag1", :style=>"class1"},
 {:href=>"http://example4.com", :tag=>"tag4", :style=>"class4"}]

こんな感じだ。
このやり方だと変数を初期化してループでぐるぐるまわすというのがいかにもrubyっぽくない。そこでeach_with_objectの登場というわけだ。
reduceで書くこともできる。

tags.reduce([]) do |array, tag|
  array << tag unless tag[:href].nil? || tag[:tag].nil?
  array
end

ただし、reduceの場合、ブロックの最後にarrayを返さないといけない。またdoのあとの引数もeach_with_objectとはちょっと違うの注意が必要だ。