user11196706
user11196706

Reputation:

Sorting a Ruby array of hashes based on the amount of times a value appears

I have an array of hashes as follows:

details = [
  {:name => "Alice", :age => 20},
  {:name => "Ted",   :age => 25},
  {:name => "Poppy", :age => 33},
  {:name => "Amy",   :age => 20},
  {:name => "Ted",   :age => 90},
  {:name => "Amy",   :age => 22},
  {:name => "Ted",   :age => 23}
]

I want to be able to sort so that I can order based on how many times the names of each person appear. For example, output might be: "Ted, Ted, Ted, Amy, Amy, Alice, Poppy"

Can anyone please help? :)

Thanks

Upvotes: 0

Views: 269

Answers (4)

Cary Swoveland
Cary Swoveland

Reputation: 110675

details.map { |h| h[:name] }.
        group_by(&:itself).
        values.
        sort_by { |a| -a.size }.
        flatten
  #=> ["Ted", "Ted", "Ted", "Amy", "Amy", "Alice", "Poppy"]

One can tack on .join(', ') if a string is desired instead:

"Ted, Ted, Ted, Amy, Amy, Alice, Poppy"

The most expensive operation here is sorting, which is O(n*log(n)); all other operations are O(n). Let

arr = ["Alice", "Ted", "Poppy", "Amy", "Ted", "Amy", "Ted"]

By executing group_by and values before sort_by, only

n = arr.uniq.size 
  #=> 4

values need be sorted, as opposed to sorting all 7 elements arr.

The steps are as follows.

b = details.map { |h| h[:name] }
  #=> ["Alice", "Ted", "Poppy", "Amy", "Ted", "Amy", "Ted"] 
c = b.group_by(&:itself)
  #=> {"Alice"=>["Alice"], "Ted"=>["Ted", "Ted", "Ted"], "Poppy"=>["Poppy"],
  #    "Amy"=>["Amy", "Amy"]} 
d = c.values
  #=> [["Alice"], ["Ted", "Ted", "Ted"], ["Poppy"], ["Amy", "Amy"]] 
e = d.sort_by { |a| -a.size }
  #=> [["Ted", "Ted", "Ted"], ["Amy", "Amy"], ["Alice"], ["Poppy"]] 
e.flatten
  #=> ["Ted", "Ted", "Ted", "Amy", "Amy", "Alice", "Poppy"] 

Upvotes: 0

Shan
Shan

Reputation: 619

output_array = []
Hash[details.group_by{|data| data[:name]}.transform_values(&:count).sort_by{|k,v| v}.reverse].each{|key,value| value.times do output_array.push(key)  end }

output_array will have the results

Upvotes: 0

kubido
kubido

Reputation: 568

this will return based on the name attribute occurrence:

sorted_group_details = details.group_by{|d| d[:name]}.sort_by{|key, val| val.count}

by default, it returns ascending order. if you want to order by the most occurrence just add reverse

sorted_group_details.reverse

and, if you want to return a flatten object from the grouped array:

sorted_group_details.map{|d| d[1]}.flatten

Upvotes: 0

Amadan
Amadan

Reputation: 198314

Get a name-counting hash, then sort by the name counts:

details.each.with_object(Hash.new(0)) { |e, c| c[e[:name]] += 1 }.
    then { |c| details.sort_by { |e| -c[e[:name]] } }.
    map { |e| e[:name] }

Upvotes: 4

Related Questions