ランキングの集計

リスト中に登場する回数の多い要素トップ N を計算するとき、どんな風に書きますか?

ランキングの集計 - まめめも

via http://d.hatena.ne.jp/sumim/20070619/p2
たとえば、以下のように書きます。

ary = %w(foo bar foo baz foo bar qux foo bar qux quux)
n = 3

count = Hash.new(0)
ary.each do |e|
  count[e] += 1
end

i = 1
count.keys.sort {|a, b|
  count[b] <=> count[a]
}.first(n).each {|e|
  puts "#{i}位:"
  puts "- #{e}"
  i += 1
}

実行結果です。

1位:
- foo
2位:
- bar
3位:
- qux

と、ここまで書いてから、同数の取り扱いが問題になっていることに気づく。ふえーん。だって例題に同数がないし…。
…こほん。仕切り直し。barを1個追加して同数を作りました。

ary = %w(foo bar foo baz foo bar qux foo bar qux quux bar)
n = 3

count = Hash.new(0)
ary.each do |e|
  count[e] += 1
end

i = 1
c = -1
count.keys.sort {|a, b|
  count[b] <=> count[a]
}.each {|e|
  if count[e] != c
    puts "#{i}位:"
    c = count[e]
  end
  puts "- #{e} (#{count[e]})"
  i += 1
  break if i > n
}

実行結果です。

1位:
- foo (4)
- bar (4)
3位:
- qux (2)

追記:id:ku-ma-meさんからバグを指摘していただきました。あらら、本当ですね。わざわざすみません。そうかbreakを間違えていますね。さらに直しました。3位が重複したときの処理も入れました。

ary = %w(foo bar foo baz foo bar qux foo bar qux quux bar xxx xxx)
n = 3

count = Hash.new(0)
ary.each do |e|
  count[e] += 1
end

i = 1
c = -1
count.keys.sort {|a, b|
  count[b] <=> count[a]
}.each {|e|
  if count[e] != c
    break if i > n
    puts "#{i}位:"
    c = count[e]
  end
  puts "- #{e} (#{count[e]})"
  i += 1
}

実行結果です。

1位:
- foo (4)
- bar (4)
3位:
- xxx (2)
- qux (2)