Ruby: How to group a Ruby array?

Ruby on-RailsRuby

Ruby on-Rails Problem Overview


I have a Ruby array

> list = Request.find_all_by_artist("Metallica").map(&:song)
=> ["Nothing else Matters", "Enter sandman", "Enter Sandman", "Master of Puppets", "Master of Puppets", "Master of Puppets"]

and I want a list with the counts like this:

{"Nothing Else Matters" => 1,
 "Enter Sandman" => 2,
 "Master of Puppets" => 3}

So ideally I want a hash that will give me the count and notice how I have Enter Sandman and enter sandman so I need it case insensitive. I am pretty sure I can loop through it but is there a cleaner way?

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

list.group_by(&:capitalize).map {|k,v| [k, v.length]}
#=> [["Master of puppets", 3], ["Enter sandman", 2], ["Nothing else matters", 1]]

The group by creates a hash from the capitalized version of an album name to an array containing all the strings in list that match it (e.g. "Enter sandman" => ["Enter Sandman", "Enter sandman"]). The map then replaces each array with its length, so you get e.g. ["Enter sandman", 2] for "Enter sandman".

If you need the result to be a hash, you can call to_h on the result or wrap a Hash[ ] around it.

Solution 2 - Ruby on-Rails

list.inject(Hash.new(0)){|h,k| k.downcase!; h[k.capitalize] += 1;h}

Solution 3 - Ruby on-Rails

Another take:

h = Hash.new {|hash, key| hash[key] = 0}
list.each {|song| h[song.downcase] += 1}
p h  # => {"nothing else matters"=>1, "enter sandman"=>2, "master of puppets"=>3}

As I commented, you might prefer titlecase

Solution 4 - Ruby on-Rails

Grouping and sorting of a data set of unknown size in Ruby should be a choice of last resort. This is a chore best left to DB. Typically problems like yours is solved using a combination of COUNT, GROUP BY, HAVING and ORDER BY clauses. Fortunately, rails provides a count method for such use cases.

song_counts= Request.count(
              :select => "LOWER(song) AS song"
              :group => :song, :order=> :song,
              :conditions => {:artist => "Metallica"})

song_counts.each do |song, count|
  p "#{song.titleize} : #{count}"
end

Solution 5 - Ruby on-Rails

As of Ruby 2.7, you can use Enumerable#tally.

list.tally
# => {"Nothing else Matters"=>1, "Enter sandman"=>1, "Enter Sandman"=>1, "Master of Puppets"=>3}

Solution 6 - Ruby on-Rails

Late but clean answer I have,

l = list.group_by(&:titleize)
l.merge(l) { |k,v| l[k] = v.count }

Note: If we do want unique keys i.e. without titleize, then replace it with itself

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMatt ElhotibyView Question on Stackoverflow
Solution 1 - Ruby on-Railssepp2kView Answer on Stackoverflow
Solution 2 - Ruby on-Railsghostdog74View Answer on Stackoverflow
Solution 3 - Ruby on-Railsglenn jackmanView Answer on Stackoverflow
Solution 4 - Ruby on-RailsHarish ShettyView Answer on Stackoverflow
Solution 5 - Ruby on-RailsBrunoFView Answer on Stackoverflow
Solution 6 - Ruby on-RailsrayView Answer on Stackoverflow