Reputation:
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
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
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
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
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