简体   繁体   中英

Group and display occurrences of array of hashes based on some key in ruby

I have an array of hashes like this:

[
  {"a"=>"first", "b"=>[{"lt"=>7, "lg"=>8}]},
  {"a"=>"first", "b"=>[{"lt"=>7, "lg"=>8}]},
  {"a"=>"second", "b"=>[{"lt"=>7, "lg"=>8}]},
  {"a"=>"third", "b"=>[{"lt"=>9, "lg"=>10}]}
]

How can I display occurrences by "a" key value and display something like this

[
  {{"a"=>"first", "b"=>[{"lt"=>7, "lg"=>8}]}=>2},
  {{"a"=>"second", "b"=>[{"lt"=>7, "lg"=>8}]}=>1},
  {{"a"=>"third", "b"=>[{"lt"=>9, "lg"=>10}]}=>1}
]

I am looking for "ruby way" solution.

frequency_distribution

You could define Enumerable#frequency_distribution by using each_with_object with a Hash and a default occurence value of 0 :

module Enumerable
  def frequency_distribution
    each_with_object(Hash.new(0)) { |element, count| count[element] += 1 }
  end
end

It works this way :

require 'pp'
data = [
    {"a"=>"first", "b"=>[{"lt"=>7, "lg"=>8}]},
    {"a"=>"first", "b"=>[{"lt"=>7, "lg"=>8}]},
    {"a"=>"second", "b"=>[{"lt"=>7, "lg"=>8}]},
    {"a"=>"third", "b"=>[{"lt"=>9, "lg"=>10}]}
]

pp data.frequency_distribution
# {{"a"=>"first", "b"=>[{"lt"=>7, "lg"=>8}]}=>2,
#  {"a"=>"second", "b"=>[{"lt"=>7, "lg"=>8}]}=>1,
#  {"a"=>"third", "b"=>[{"lt"=>9, "lg"=>10}]}=>1}

If you don't want to monkey-patch Enumerable :

def frequency_distribution(array)
  array.each_with_object(Hash.new(0)) { |e, h| h[e] += 1 }
end

frequency_distribution(data)
# {{"a"=>"first", "b"=>[{"lt"=>7, "lg"=>8}]}=>2,
#  {"a"=>"second", "b"=>[{"lt"=>7, "lg"=>8}]}=>1,
#  {"a"=>"third", "b"=>[{"lt"=>9, "lg"=>10}]}=>1}

Note that the output is one single hash. Keys are hashes and values are integers. I cannot think of any good reason to convert it to an array of 1-pair hashes. Lookup would be much slower and less readable.

count_by

For a more generic method, you could define Enumerable#count_by , with the same syntax as group_by or sort_by :

module Enumerable
  def count_by(&block)
    each_with_object(Hash.new(0)) { |element, count| count[block.call(element)] += 1 }
  end
end

require 'pp'
data = [
    {"a"=>"first", "b"=>[{"lt"=>7, "lg"=>8}]},
    {"a"=>"first", "b"=>[{"lt"=>7, "lg"=>8}]},
    {"a"=>"second", "b"=>[{"lt"=>7, "lg"=>8}]},
    {"a"=>"third", "b"=>[{"lt"=>9, "lg"=>10}]}
]

pp data.count_by(&:itself)
# {{"a"=>"first", "b"=>[{"lt"=>7, "lg"=>8}]}=>2,
#  {"a"=>"second", "b"=>[{"lt"=>7, "lg"=>8}]}=>1,
#  {"a"=>"third", "b"=>[{"lt"=>9, "lg"=>10}]}=>1}

pp data.count_by(&:keys)
#=> {["a", "b"]=>4}

pp data.count_by{|key, value| key["a"]}
#=> {"first"=>2, "second"=>1, "third"=>1}
foos.map { |foo| {foo => foos.count(foo)} }.uniq

As I understand the question, it has nothing to do with the particular elements of your given array. More generally, given an array

arr = [a,a,b,c] 

where a , b and c are any Ruby objects, you want a count of each unique element, expressed as an array of hashes. To keep things simple, suppose

arr = [1,1,2,3]

Here are two ways to obtain the counts by making a single pass through arr .

arr.group_by(&:itself).map { |k,v| { k=>v.size } }
  #=> [{ 1=>2 }, { 2=>1 }, { 3=>1 }]

and (a counting hash , as used by @Eric in his answer)

arr.each_with_object(Hash.new(0)) { |e,h| h[e] += 1 }
  #=> [{ 1=>2 }, { 2=>1 }, { 3=>1 }]

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM