1

I have a Ruby hash:

example = {
  :key1  => [1, 1, 4],
  :key2  => [1, 2, 3],
  :key3  => [1, 3, 2],
  :key4  => [1, 5, 0],
  :key5  => [1, 7, 2],
  :key6  => [2, 1, 5],
  :key7  => [2, 2, 4],
  :key8  => [2, 4, 2],
  :key9  => [3, 1, 6],
  :key10 => [3, 2, 5],
  :key11 => [3, 3, 4]
}

How can I group the hash by the first element in the value's array? Once it is grouped, how can I get count of each of those groups and store them into an additional hash?

I'm open to skipping the group_by part if I'm able to extract the counts.

Example desired output:

groups = {:group1 => 5, :group2 => 3, :group3 => 3}

5 Answers 5

5

Here is a way using each_with_object :

example.each_with_object(Hash.new(0)) { |(_, (v, *)), h|  h[:"group#{v}"] += 1 }
# => {:group1=>5, :group2=>3, :group3=>3}
Sign up to request clarification or add additional context in comments.

Comments

1

Here's one for the "easy" version(array w/o keys):

example.group_by { |k, v| v.first }.values.map(&:count)

Comments

0

Or it might be clearer like this:

arrays_by_first_element = example.values.group_by { |a| a[0] }
groups = {}
arrays_by_first_element.each { |k, v| groups[k] = v.size }

Comments

0

This is one way to do that:

example.values.group_by(&:first).each_with_object({}) { |(k,v),h|
  h.update("group#{k}".to_sym=>v.size) }
  #=> {:group1=>5, :group2=>3, :group3=>3}

The steps:

example = {:key1=> [1, 1, 4], :key2=> [1, 2, 3], :key3=>[1, 3, 2],
           :key4=> [1, 5, 0], :key5=> [1, 7, 2], :key6=>[2, 1, 5],
           :key7=> [2, 2, 4], :key8=> [2, 4, 2], :key9=>[3, 1, 6],
           :key10=>[3, 2, 5], :key11=>[3, 3, 4]}
v = example.values
  #=> [[1, 1, 4], [1, 2, 3], [1, 3, 2], [1, 5, 0], [1, 7, 2], [2, 1, 5],
  #    [2, 2, 4], [2, 4, 2], [3, 1, 6], [3, 2, 5], [3, 3, 4]] 
g = v.group_by(&:first) # same as group_by { |k,_| k }
  #=> {1=>[[1, 1, 4], [1, 2, 3], [1, 3, 2], [1, 5, 0], [1, 7, 2]],
  #    2=>[[2, 1, 5], [2, 2, 4], [2, 4, 2]],
  #    3=>[[3, 1, 6], [3, 2, 5], [3, 3, 4]]} 
enum = g.each_with_object({})
  #=> #<Enumerator: { 1=>[[1, 1, 4], [1, 2, 3], [1, 3, 2], [1, 5, 0],  [1, 7, 2]],
  #                   2=>[[2, 1, 5], [2, 2, 4], [2, 4, 2]],
  #                   3=>[[3, 1, 6], [3, 2, 5], [3, 3, 4]]
  #                 }:each_with_object({})> 

Convert the enumerator to an array to examine its values:

enum.to_a
  #=> [[[1, [[1, 1, 4], [1, 2, 3], [1, 3, 2], [1, 5, 0], [1, 7, 2]]], {}],
  #    [[2, [[2, 1, 5], [2, 2, 4], [2, 4, 2]]], {}],
  #    [[3, [[3, 1, 6], [3, 2, 5], [3, 3, 4]]], {}]]  

Note the value of the hash, which presently is empty. Pass the first element of enum to the block:

(k,v),h = enum.next
  #=> [[1, [[1, 1, 4], [1, 2, 3], [1, 3, 2], [1, 5, 0], [1, 7, 2]]], {}]
k #=> 1 
v #=> [[1, 1, 4], [1, 2, 3], [1, 3, 2], [1, 5, 0], [1, 7, 2]] 
h #=> {} 
h.update("group#{k}".to_sym=>v.size)
  #=> {}.update(:group1=>5)
  #=> {:group1=>5} (new value of h)

If we now examine the elements of enum, we see the value of the hash has been updated:

enum.to_a
  #=> [[[1, [[1, 1, 4], [1, 2, 3], [1, 3, 2], [1, 5, 0], [1, 7, 2]]],
  #       {:group1=>5}],
  #    [[2, [[2, 1, 5], [2, 2, 4], [2, 4, 2]]], {:group1=>5}],
  #    [[3, [[3, 1, 6], [3, 2, 5], [3, 3, 4]]], {:group1=>5}]]  

Continuing...

(k,v),h = enum.next
  #=> [[2, [[2, 1, 5], [2, 2, 4], [2, 4, 2]]], {:group1=>5}] 
k #=> 2 
v #=> [[2, 1, 5], [2, 2, 4], [2, 4, 2]] 
h #=> {:group1=>5} 
h.update("group#{k}".to_sym=>v.size)
  #=> {:group1=>5, :group2=>3} 

(k,v),h = enum.next 
  #=> [[3, [[3, 1, 6], [3, 2, 5], [3, 3, 4]]], {:group1=>5, :group2=>3}]
h.update("group#{k}".to_sym=>v.size)
  #=> {:group1=>5, :group2=>3, :group3=>3} 

Comments

0

Simple one liner:

puts example.map{|k,v| v[0]}.inject(Hash.new(0)) { |total, e| total[("group%s" % e)] += 1; total}

output:

{"group1"=>5, "group2"=>3, "group3"=>3}

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.