トップ «前の日記(06/01) 最新 次の日記(06/06)» 編集

hossy online - といぼっくす

ゲームの感想日記、たまにIT・プログラミングの話


06/02

_ [プログラミング] Ruby の inject

Ruby で好きなメソッドの一つが、inject です。前の 40C20 なエントリでも使っていました。さて、この構文が Ruby1.8.7 / Ruby1.9 以降で拡張されている事を最近知りました。それをまとめてみようと。

参考サイトはこちらです。

inject(init) {|result, item| ... }

繰り返し計算に使うメソッドです。たとえば、40C20 の途中に出てきた、20! の計算。inject 無しだと、こう書くことが多いと思います。

result = 1
for i in 1..20
  result = result * i
end
puts result
result = 1
1.upto(20) {|i| result = result * i }
puts result
result = 1
(1..20).each {|i| result *= i }
puts result

これを、result 変数を外に出さずに書けるのが、inject メソッドです。

x = (1..20).inject(1) {|result, i| result *= i }
puts x
puts (1..20).inject(1) {|r, i| r *= i }

result の初期値は inject(1) と書かれたように 1 で、その後ループで 1〜20 までの値をかけ算していきます。そうすると 20! が求まります。

結果を x で受けずに、直接 puts で出力もできます。変数が1個分減りました。

1+2+...+10 の図示が「ruby の inject をわかりやすく説明してみる」にあります。お勧め。

inject {|result, item| ... }

さて、前の方法は 初期値 1 に対して、1〜20 の値をかけ算しています。展開してみると、

puts 1 * 1 * 2 * 3 * 4 * ... * 20
     ~~~~~

最初に 1 が 2回出てきます。かけ算するのは 1 からでなくて、その次の 2からで十分でしょう。また、かけ算だと 1 から始めるのが良かったですが、もし 1〜20 までの総和で同じような事をしたいときには、初期値は 1 でなくて 0 のはずです。このあたりを考えるのも何か無駄。

そこで、初期値 (1) を省略した時には、数え上げの 1番目を初期値にして、2番目以降で inject を行う、という構文があります。こちらの方が自然な事が多いかと思います。

puts (1..20).inject(1) {|r, i| r *= i }  #=> 1*1*2*...*20
puts (2..20).inject(1) {|r, i| r *= i }  #=>   1*2*...*20
puts (1..20).inject {|r, i| r *= i }     #=>   1*2*...*20

下2つが同じ意味になります。

inject(sym), inject(init, sym)

さて、初期値を書かなくて良くなったとは言え、やはり {|r, i| r *= i } あたりが難しい感じがします。なにせ、やりたいことは "1*2*...*20" と、順に並ぶ数字の間に "*" かけ算記号を挟むことだけ。それなのに変数が現れるのが。

puts (1..20).to_a.join("*")
#=> 1*2*3*4*5*6*7*8*9*10*11*12*13*14*15*16*17*18*19*20

文字列にすると、なんだか簡単に eval できそうです。

Ruby 1.8.7/Ruby 1.9 以降だと inject で行えます。シンボルを渡せば大丈夫。

puts (1..20).inject(:*)

これで、* 演算子で 1〜20 の値を順番に計算していきます。

(1..20).inject(:*)
#=> 1*2 *3*4*5*6*7*8*9*10*11*12*13*14*15*16*17*18*19*20
#=>   2*3 *4*5*6*7*8*9*10*11*12*13*14*15*16*17*18*19*20
#=>     6*4 *5*6*7*8*9*10*11*12*13*14*15*16*17*18*19*20
#=>      24*5 *6*7*8*9*10*11*12*13*14*15*16*17*18*19*20
#=>       120*6 *7*8*9*10*11*12*13*14*15*16*17*18*19*20
#=>...

より正確には、以下の順番に * メソッドが呼ばれています。

(1..20).inject :*
1.*(2) = 2
2.*(3) = 6
6.*(4) = 24
24.*(5) = 120
...

init は最初のブロックで指定した inject(init) {|result, item| ... } と同じです。

例: Project Euler 5

Project Euler: Problem 5 より:

2520 は 1 から 10 の数字の全ての整数で割り切れる数字であり、そのような数字の中では最小の値である。 では、1 から 20 までの整数全てで割り切れる数字の中で最小の値はいくらになるか。

p (1..20).inject :lcm
#=>1.lcm(2).lcm(3).lcm(4)..中略..lcm(20)
#=>       2.lcm(3).lcm(4)..中略..lcm(20)
#=>              6.lcm(4)..中略..lcm(20)
#=>                    12..中略..lcm(20)
#=>232792560

最後に

シンボルで書けるのは凄いなぁと思ってエントリにしてみたものの、よく考えてみるとシンボルだけで書けるのって演算子以外ではけっこう限られるような、という気も少し。

それでも、単純な事を単純に書ける方法として、知っていると良い/ネタにできる構文だと思いました。