『ランキングの集計』を考える


こういうのはまだ出ていないみたいなので。集計したカウント(重複を排除後、ソート)本位にループを回します。ポイントは Hash#select で同じ順位の要素を集めてまとめて処理していること、でしょうか。

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

count = Hash.new{0}
ary.each { |x| count[x] += 1 }

i = 1
count.values.uniq.sort.reverse_each do |c|
  puts "#{i} 位:"
  count.select{ |k,v| v == c }.each{ |k,v| puts "- #{k}"; i += 1 }
  break if i > n
end

ほぼ同じものを Squeak Smalltalk で。

| ary n count i |
ary := #(foo bar foo baz foo bar qux foo bar qux quux).
n := 3.

count := Dictionary new.
ary do: [:x | count at: x put: (count at: x ifAbsent: [0]) + 1].

i := 1.
count values asSet asSortedArray reverseDo: [:c |
    | assocs |
    Transcript cr; show: ('{1} 位:' format: {i}).
    assocs := count associationsSelect: [:assoc | assoc value == c ].
    assocs keysDo: [:k | Transcript cr; show: ('- {1}' format: {k}). i := i + 1].
    i > n ifTrue: [^self]
]


さらに集計作業ではお定まりの a Bag 版も。ランク表示のほうは、少し趣向を変えて #inject:into: を使ってみました。

| ary n count i |
ary := #(foo bar foo baz foo bar qux foo bar qux quux).
n := 3.

count := ary asBag sortedCounts.
count := count select: [:assoc | assoc key >= (count at: n) key].

World findATranscript: nil.
i := 0.
count inject: nil into: [:prev :assoc |
    (prev isNil or: [prev key > assoc key])
        ifTrue: [Transcript cr; show: (i := i + 1) printString, ' 位:'].
    Transcript cr; show: '- ', assoc value.
    assoc]