How to group by count in array without using loop

Ruby

Ruby Problem Overview


arr = [1,2,1,3,5,2,4]

How can I count the array by group value with sorting? I need the following output:

x[1] = 2  
x[2] = 2  
x[3] = 1  
x[4] = 1  
x[5] = 1

Ruby Solutions


Solution 1 - Ruby

x = arr.inject(Hash.new(0)) { |h, e| h[e] += 1 ; h }

Solution 2 - Ruby

Only available under ruby 1.9

Basically the same as Michael's answer, but a slightly shorter way:

x = arr.each_with_object(Hash.new(0)) {|e, h| h[e] += 1}

In similar situations,

  • When the starting element is a mutable object such as an Array, Hash, String, you can use each_with_object, as in the case above.

  • When the starting element is an immutable object such as Numeric, you have to use inject as below.

    sum = (1..10).inject(0) {|sum, n| sum + n} # => 55

Solution 3 - Ruby

There is a short version which is in ruby 2.7 => Enumerable#tally.

[1,2,1,3,5,2,4].tally  #=> { 1=>2, 2=>2, 3=>1, 5=>1, 4=>1 }

# Other possible usage

(1..6).tally { |i| i%3 }   #=> { 0=>2, 1=>2, 2=>2 }

Solution 4 - Ruby

Yet another - similar to others - approach:

result=Hash[arr.group_by{|x|x}.map{|k,v| [k,v.size]}]
  1. Group by each element's value.
  2. Map the grouping to an array of [value, counter] pairs.
  3. Turn the array of paris into key-values within a Hash, i.e. accessible via result[1]=2 ....

Solution 5 - Ruby

arr.group_by(&:itself).transform_values(&:size)
#=> {1=>2, 2=>2, 3=>1, 5=>1, 4=>1}

Solution 6 - Ruby

Whenever you find someone asserting that something is the fastest on this type of primitive routine, I always find its interesting to confirm that because without confirmation most of us are really just guessing. So I took all of the methods here and benchmarked them.

I took an array of 120 links I extracted from a web page that I needed to group by count and implemented all of these using a seconds = Benchmark.realtime do loop and got all the times.

Assume links is the name of the array I need to count:

#0.00077
seconds = Benchmark.realtime do
  counted_links = {}
  links.each { |e| counted_links[e] = links.count(e) if counted_links[e].nil?}
end
seconds

#0.000232
seconds = Benchmark.realtime do
  counted_links = {}
  links.sort.group_by {|x|x}.each{|x,y| counted_links[x] = y.size}
end

#0.00076
seconds = Benchmark.realtime do 
  Hash[links.uniq.map{ |i| [i, links.count(i)] }]
end

#0.000107 
seconds = Benchmark.realtime do 
  links.inject(Hash.new(0)) {|h, v| h[v] += 1; h}
end

#0.000109
seconds = Benchmark.realtime do 
  links.each_with_object(Hash.new(0)) {|e, h| h[e] += 1}
end

#0.000143
seconds = Benchmark.realtime do 
  links.inject(Hash.new(0)) { |h, e| h[e] += 1 ; h }
end

And then a little bit of ruby to figure out the answer:

times = [0.00077, 0.000232, 0.00076, 0.000107, 0.000109, 0.000143].min
==> 0.000107

So the actual fastest method, ymmv of course, is:

links.inject(Hash.new(0)) {|h, v| h[v] += 1; h}

Solution 7 - Ruby

x = Hash[arr.uniq.map{ |i| [i, arr.count(i)] }]

Latest Ruby has to_h method:

x = arr.uniq.map{ |i| [i, arr.count(i)] }.to_h

Solution 8 - Ruby

I am sure there are better ways,

>> arr.sort.group_by {|x|x}.each{|x,y| print "#{x} #{y.size}\n"}
1 2
2 2
3 1
4 1
5 1

assign x and y values to a hash as needed.

Solution 9 - Ruby

Just for the record, I recently read about Object#tap here. My solution would be:

Hash.new(0).tap{|h| arr.each{|i| h[i] += 1}}

The #tap method passes the caller to the block and then returns it. This is pretty handy when you have to incrementally build an array/hash.

Solution 10 - Ruby

This should do it

arr = [1,2,1,3,5,2,4]

puts arr.inject(Hash.new(0)) {|h, v| h[v] += 1; h}
#=> {1=>2, 2=>2, 3=>1, 5=>1, 4=>1}

Solution 11 - Ruby

arr = [1,2,1,3,5,2,4]
r = {}
arr.each { |e| r[e] = arr.count(e) if r[e].nil?}

Outputs

p r
#==> {1=>2, 2=>2, 3=>1, 5=>1, 4=>1}

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
QuestionMr. BlackView Question on Stackoverflow
Solution 1 - RubyMichael KohlView Answer on Stackoverflow
Solution 2 - RubysawaView Answer on Stackoverflow
Solution 3 - RubyMr. BlackView Answer on Stackoverflow
Solution 4 - RubylllllllView Answer on Stackoverflow
Solution 5 - RubyEliadLView Answer on Stackoverflow
Solution 6 - RubyfuzzygroupView Answer on Stackoverflow
Solution 7 - RubyrubyprinceView Answer on Stackoverflow
Solution 8 - RubykurumiView Answer on Stackoverflow
Solution 9 - RubyerasingView Answer on Stackoverflow
Solution 10 - RubyThoKraView Answer on Stackoverflow
Solution 11 - RubythebugfinderView Answer on Stackoverflow