0

Possible Duplicate:
count duplicate elements in ruby array

I just started learning ruby and I wish to achieve something like this. Let say I have an array

["student", "student", "teacher", "teacher", "teacher"]

What I'm trying to do is to count the number of student and teacher and store it into a hash, so that i can get

{:student= > 2, :teacher=> 3}

Could someone give me some kind of direction on how to do this?

Community
  • 1
  • 1
user486174
  • 41
  • 1
  • 10

7 Answers7

3

You should check this answer, which gives this example:

# sample array
a=["aa","bb","cc","bb","bb","cc"]

# make the hash default to 0 so that += will work correctly
b = Hash.new(0)

# iterate over the array, counting duplicate entries
a.each do |v|
  b[v] += 1
end

b.each do |k, v|
  puts "#{k} appears #{v} times"
end
Community
  • 1
  • 1
alestanis
  • 21,519
  • 4
  • 48
  • 67
1
list = ["student", "student", "teacher"]

# Initializing the hash with value 0 so that we can use += 1
count = Hash.new(0)  

list.each {|el| count[el] += 1}

#Number of student  
count['student']
az7ar
  • 5,187
  • 2
  • 19
  • 23
1
xs.inject({}) { |acc, x| acc.update(x => (acc[x] || 0) + 1) }
#=> {"student"=>2, "teacher"=>3} 

Or:

xs.each_with_object(Hash.new(0)) { |x, acc| acc[x] += 1 }
#=> {"student"=>2, "teacher"=>3}
tokland
  • 66,169
  • 13
  • 144
  • 170
1

This particular problem is a great example of how choosing the right algorithm, but even more importantly the right data structure can massively simplify the solution. In fact, in this particular case, choosing the right data structure will make the algorithm so trivial that it basically completely vanishes: the data structure already is the answer.

The data structure I am talking about is a Multiset: a Multiset is like a Set, except it doesn't store only unique items, instead it stores a count of how often each item is in the Multiset. Basically, a Set tells you whether a particular item is in the Set at all, a Multiset in addition also tells you how often that particular item is in the Multiset.

Unfortunately, there is no Multiset implementation in the Ruby core library or standard library, but there are a couple of implementations floating around the web.

You literally just have to construct a Multiset from your Array. Here's an example:

require 'multiset'

ary = ["student", "student", "teacher", "teacher", "teacher"]

print Multiset[*ary]

Yes, that's all there is to it. This prints:

#2 "student"
#3 "teacher"

And that's it. Example, using https://GitHub.Com/Josh/Multimap/:

require 'multiset'

histogram = Multiset.new(*ary)
# => #<Multiset: {"student", "student", "teacher", "teacher", "teacher"}>

histogram.multiplicity('teacher')
# => 3

Example, using http://maraigue.hhiro.net/multiset/index-en.php:

require 'multiset'

histogram = Multiset[*ary]
# => #<Multiset:#2 'student', #3 'teacher'>

Another possibility is to use a Hash, which basically just means that instead of the Multiset taking care of the element counting for you, you have to do it yourself:

histogram = ary.inject(Hash.new(0)) {|hsh, item| hsh.tap { hsh[item] += 1 }}
print histogram
# { "student" => 2, "teacher" => 3 }

But you can have that easier if instead of counting yourself, you use Enumerable#group_by to group the elements by themselves and then map the groupings to their sizes. Lastly, convert back to a Hash:

Identity = ->x { x }

print Hash[[ary.group_by(&Identity).map {|n, ns| [n, ns.size] }]
# { "student" => 2, "teacher" => 3 }
Jörg W Mittag
  • 363,080
  • 75
  • 446
  • 653
0

It really has been done to death here on SO, but I suggest:

Hash[*a.group_by{|x| x}.flat_map{|k, v| [k.to_sym,v.size]}]
#=> {:student=>2, :teacher=>3}
pguardiario
  • 53,827
  • 19
  • 119
  • 159
0
a = ["student", "student", "teacher", "teacher", "teacher"]

a.inject({}){|h, e| h[e] ||= a.count(e); h}
# => {"student"=>2, "teacher"=>3}
sawa
  • 165,429
  • 45
  • 277
  • 381
  • As happens with @RailsN00ob's answer, this is O(n^2) – tokland Nov 11 '12 at 10:42
  • @tokland I think it is less than O(n^2). Because of `||=`, it skips unnecessary iterations. When it skips, it is just a hash look up: `h[e]`, which should be quick. I initially had an answer which is the same as RailsN00b's, and realized what you mention, and changed to what I have now. My answer is more effective than that. – sawa Nov 11 '12 at 11:41
  • 1
    Yes, it potentially perfoms better thanks to the memoization of the counts, O(n*k) where k is the unique elements in `a`. So best case O(n), worst case still O(n^2). But in a clear a O(n) problem... – tokland Nov 11 '12 at 12:19
0
list = ["student", "student", "teacher", "teacher", "teacher"] #original list

counts = {} #where the count hash will be

list.uniq.map{|x| counts[x]= list.count(x)} 

for every unique item in the list put the count of it from the original list in the counts hash.

one line. clean and simple.

Nick Ginanto
  • 31,090
  • 47
  • 134
  • 244