简体   繁体   中英

Return top n of an array for each hash type in Ruby

I have an array of hashes which looks like this:

[
  {:name=>"CCC_010112.JPG", :type=>"CCC", :date=>"120101"},
  {:name=>"BBB_050112.JPG", :type=>"BBB", :date=>"120501"},
  {:name=>"BBB_040112.JPG", :type=>"BBB", :date=>"120401"},
  {:name=>"BBB_030112.JPG", :type=>"BBB", :date=>"120301"},
  {:name=>"BBB_020112.JPG", :type=>"BBB", :date=>"120201"},
  {:name=>"BBB_010112.JPG", :type=>"BBB", :date=>"120101"},
  {:name=>"AAA_040112.JPG", :type=>"AAA", :date=>"120401"},
  {:name=>"AAA_030112.JPG", :type=>"AAA", :date=>"120301"},
  {:name=>"AAA_020112.JPG", :type=>"AAA", :date=>"120201"},
]

I am trying to capture the first three of each type. (eg, my results should yield the array bove, only cutting out the bottom two of the "BBB" elements. I tried variants of the following:

puts a.each{|e| e[:type]}.take(3) #Shows top 3 of all

I would do using Enumerable#chunk , as in the array all different types like 'CCC' , 'BBB' etc are already clubbed together. If they were scattered, group_by , to club different types of hashes first.

a = [
       {:name=>"CCC_010112.JPG", :type=>"CCC", :date=>"120101"},
       {:name=>"BBB_050112.JPG", :type=>"BBB", :date=>"120501"},
       {:name=>"BBB_040112.JPG", :type=>"BBB", :date=>"120401"},
       {:name=>"BBB_030112.JPG", :type=>"BBB", :date=>"120301"},
       {:name=>"BBB_020112.JPG", :type=>"BBB", :date=>"120201"},
       {:name=>"BBB_010112.JPG", :type=>"BBB", :date=>"120101"},
       {:name=>"AAA_040112.JPG", :type=>"AAA", :date=>"120401"},
       {:name=>"AAA_030112.JPG", :type=>"AAA", :date=>"120301"},
       {:name=>"AAA_020112.JPG", :type=>"AAA", :date=>"120201"}
    ]

final_top3_ary = a.chunk { |hash| hash[:type] }.flat_map { |_,ary| ary.take(3) }

final_top3_ary
# => [{:name=>"CCC_010112.JPG", :type=>"CCC", :date=>"120101"},
#     {:name=>"BBB_050112.JPG", :type=>"BBB", :date=>"120501"},
#     {:name=>"BBB_040112.JPG", :type=>"BBB", :date=>"120401"},
#     {:name=>"BBB_030112.JPG", :type=>"BBB", :date=>"120301"},
#     {:name=>"AAA_040112.JPG", :type=>"AAA", :date=>"120401"},
#     {:name=>"AAA_030112.JPG", :type=>"AAA", :date=>"120301"},
#     {:name=>"AAA_020112.JPG", :type=>"AAA", :date=>"120201"}]

There's probably a more efficient way, but off the top:

a = [
{:name=>"CCC_010112.JPG", :type=>"CCC", :date=>"120101"},
{:name=>"BBB_050112.JPG", :type=>"BBB", :date=>"120501"},
{:name=>"BBB_040112.JPG", :type=>"BBB", :date=>"120401"},
{:name=>"BBB_030112.JPG", :type=>"BBB", :date=>"120301"},
{:name=>"BBB_020112.JPG", :type=>"BBB", :date=>"120201"},
{:name=>"BBB_010112.JPG", :type=>"BBB", :date=>"120101"},
{:name=>"AAA_040112.JPG", :type=>"AAA", :date=>"120401"},
{:name=>"AAA_030112.JPG", :type=>"AAA", :date=>"120301"},
{:name=>"AAA_020112.JPG", :type=>"AAA", :date=>"120201"}
]

a_types = (a.collect { |e| e[:type] }).uniq
a_top3 = []
a_types.each { |t| a_top3 << (a.select { |e| e[:type] == t }).take(3) }
a_top3.flatten!

Since you mentioned in comments that your list is already reverse-sorted, we can use just this:

top3_of_each = a.inject([]) do |acc, h|
  acc << h if acc.length < 3 || h[:type] != acc[acc.length-3][:type]
  acc
end

If the list is not reverse-sorted, then we need to do more work on grouping hashes by types and finding top 3 for each type.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM