Rubyで理解するMapReduce
MapReduceの勉強と練習をかねてRubyでそれらしいことを書いてみます。
間違ったことをしてるかもしれないので、詳しい人がツッコミを入れてくれると嬉しいです。
ruby 1.8.7で動作を確認しています。
テーマ
Apacheのログっぽいデータを分析して、それぞれのファイルへのアクセス数を算出します。
入力データはこんな感じ。
# Apacheのログっぽいデータの集合 input_data = [ '[04/01 00:00:00] "GET index.html HTTP/1.1" 200', '[04/01 00:00:00] "GET index.html HTTP/1.1" 200', '[04/01 00:00:00] "GET reduce.html HTTP/1.1" 200', '[04/01 00:00:00] "GET reduce.html HTTP/1.1" 200', '[04/01 00:00:00] "GET index.html HTTP/1.1" 200', '[04/01 00:00:00] "GET map.html HTTP/1.1" 200', ]
not MapReduce
まずは何も考えず普通に書いてみます。
r = {} input_data.each do |d| f = d[/GET (.+?) /, 1] r[f] ||= 0 r[f] += 1 end p r # => {"map.html"=>1, "index.html"=>3, "reduce.html"=>2}
MapReduce
次にMapReduceで書いてみます。
# MapReduce!! p input_data.map{|d| d[/GET (.+?) /, 1] }.group_by{|d| d}.reduce({}){|r,kv| r.update({kv[0] => kv[1].size}) } # => {"map.html"=>1, "index.html"=>3, "reduce.html"=>2}
分解すると、次のことをやっています。
# Mapフェーズ # ログデータからファイル名を抜き出す m = input_data.map{|d| d[/GET (.+?) /, 1] } p m # => ["index.html", "index.html", "reduce.html", "reduce.html", "index.html", "map.html"] # Shuffleフェーズ # 同じ名前のファイルをまとめる g = m.group_by{|d| d} p g # => {"map.html"=>["map.html"], "index.html"=>["index.html", "index.html", "index.html"], "reduce.html"=>["reduce.html", "reduce.html"]} # Reduceフェーズ # ファイルの個数をカウントする g.reduce({}){|r,kv| r.update({kv[0] => kv[1].size}) } p g # => {"map.html"=>1, "index.html"=>3, "reduce.html"=>2}