This is part of the Back to Basics series.
See the introductory article for an explanation of why this series is important.

Last time, on Enumerable: Any Way You Slice It…, we learned how to slice and dice enumerable sequences into smaller sequential bits.

This time we are going to quickly look at grouping items in the enumerable collection.

Here is a quick summary:


Let’s say we have a list of friends, and we want to group them by favorite colors. We can use group_by { |item| ... } to accomplish this. This method returns a hash with keys the match the block return values, and values that are the list of matching items in the collection.

all_my_friends = [
  {
    first_name: "John",
    last_name: "Doe",
    favorite_color: "brown",
    dominant_hand: "left"
  },
  {
    first_name: "Jane",
    last_name: "Smith",
    favorite_color: "blue",
    dominant_hand: "right"
  },
  {
    first_name: "Jack",
    last_name: "Sparrow",
    favorite_color: "brown",
    dominant_hand: "right"
  },
  {
    first_name: "Jessica",
    last_name: "Rabbit",
    favorite_color: "red",
    dominant_hand: "left"
  },
  {
    first_name: "James",
    last_name: "Hook",
    favorite_color: "red",
    dominant_hand: "left"
  },
  {
    first_name: "Juliet",
    last_name: "Capulet",
    favorite_color: "blue",
    dominant_hand: "right"
  },
  
].map(&OpenStruct.method(:new))

groups_of_friends_by_color = all_my_friends.group_by { |person| person.favorite_color }
p groups_of_friends_by_color.keys #=> %w(brown blue red)
groups_of_friends_by_color.each do |favorite_color, fans_of_certain_color|
  puts(
    sprintf(
      "Fans of %<favorite_color>s: %<list_of_friends>s",
      favorite_color:,
      list_of_friends: fans_of_certain_color.map(&:first_name).join(", ")
    )
  )
end
Fans of brown: John, Jack
Fans of blue: Jane, Juliet
Fans of red: Jessica, James


What if instead of an arbitrary value we used truthy/false values?

You could definitely use group_by again, but there is a shortcut: partition { |item| ... }. Instead of a hash, this returns an array of two arrays: [truthy_items, falsey_items].

# all_my_friends = [...] # same as before
south_paws, normies = all_my_friends.partition { |person| person.dominant_hand == "left" }
p south_paws.map(&:first_name) # => ["John", "Jessica", "James"]
p normies.map(&:first_name)    # => ["Jane", "Jack", "Juliet"]


How about if we had just a list of votes for favorite color, and wanted to see which was most popular?

You can use tally to “group by” whole items (vs a block/property) and then count the results. This method returns a hash with the item as the key and the count of matching items as the value.

p %w(blue red brown blue red black brown blue red blue).tally
# => {"blue"=>4, "red"=>3, "brown"=>2, "black"=>1}


To read the rest of the series: click here