Allyl Isocyanate
Allyl Isocyanate

Reputation: 13626

Grouping an array on the basis of its first element, without duplication in Ruby

I'm executing an active record command Product.pluck(:category_id, :price), which returns an array of 2 element arrays:

[
  [1, 500],
  [1, 100],
  [2, 300]
]

I want to group on the basis of the first element, creating a hash that looks like:

{1 => [500, 100], 2 => [300]}

group_by seems logical, but replicates the entire array. I.e. a.group_by(&:first) produces:

{1=>[[1, 500], [1, 100]], 2=>[[2, 300]]}

Upvotes: 9

Views: 4331

Answers (5)

katsusuke
katsusuke

Reputation: 571

I do not like the destructive operation.

array.group_by(&:first).map { |id, a| [id, a.map(&:last)] }.to_h

Upvotes: 2

Lev Lukomskyi
Lev Lukomskyi

Reputation: 6667

Used this functionality several times in my app, added extension to an array:

# config/initializers/array_ext.rb
class Array
  # given an array of two-element arrays groups second element by first element, eg:
  # [[1, 2], [1, 3], [2, 4]].group_second_by_first #=> {1 => [2, 3], 2 => [4]}
  def group_second_by_first
    each_with_object({}) { |(first, second), h| (h[first] ||= []) << second }
  end
end

Upvotes: 0

inket
inket

Reputation: 1681

This one-liner seemed to work for me.

array.group_by(&:first).map { |k, v| [k, v.each(&:shift)] }.to_h

Upvotes: 5

daremkd
daremkd

Reputation: 8424

Since you're grouping by the first element, just remove it with shift and turn the result into a hash:

array.group_by(&:first).map do |key, value|
  value = value.flat_map { |x| x.shift; x }
  [key, value]
end #=> {1=>[500, 100], 2=>[300]}

Upvotes: 3

tadman
tadman

Reputation: 211580

You can do a secondary transform to it:

Hash[
  array.group_by(&:first).collect do |key, values|
    [ key, values.collect { |v| v[1] } ]
  end
]

Alternatively just map out the logic directly:

array.each_with_object({ }) do |item, result|
  (result[item[0]] ||= [ ]) << item[1]
end

Upvotes: 20

Related Questions