またまた Ruby ブログ「hp12c」からの問題(?)です。
melborne.github.io
さて、データ
data = <<EOS player gameA gameB Bob 20 56 Ross 68 33 Bob 78 55 Kent 90 15 Alice 84 79 Ross 10 15 Jimmy 80 31 Bob 12 36 Kent 88 43 Kent 12 33 Alice 90 32 Ross 67 77 Alice 56 92 Jimmy 33 88 Jimmy 11 87 EOS
から出力
player gameA gameB total Alice 230 203 433 Jimmy 124 206 330 Kent 190 91 281 Ross 145 125 270 Bob 110 147 257
を得よという問題です(totalで降順)。
元ブログでの回答はこちら。
require "csv" class CSV def group_by(&blk) Hash[ super.map { |k, v| [k, CSV::Table.new(v)] } ] end end csv = CSV.new(data, col_sep:' ', headers:true, converters: :numeric, header_converters: :symbol) scores_by_player = csv.group_by(&:first) stat = scores_by_player.map do |(_, player), t| ab = [:gamea, :gameb].map { |e| t[e].inject(:+) } [player, *ab, ab.inject(:+)] end puts "%s\t%s\t%s\ttotal" % csv.headers puts stat.sort_by{ |s| -s.last }.map { |line| "%s\t%d\t%d\t%d" % line }
標準添付ライブラリを使っているわけですね。しかし、メソッドのオーバーライドはさすがにちょっとという気がします。それに、コードが凝りすぎて自分には読みにくい感じ。
ライブラリを使わず、極ふつうに素直にやったらどうなるか、考えてみました。
header, *given = data.each_line.map(&:split) ga, gb = Hash.new(0), Hash.new(0) given.each do |name, a, b| ga[name] += a.to_i gb[name] += b.to_i end table = [header + ["total"]] + given.map(&:first).uniq.map {|n| [n, ga[n], gb[n], ga[n] + gb[n]]} .sort {|a, b| b[3] <=> a[3]} puts table.map {|p, a, b, t| sprintf "%s\t%s\t%s\t%s", p, a, b, t}
結構めんどうですね。もっとうまくできますかね。