1

I have this problem counting letters with a hash:

#Counting with hashes!
#Experiment by writing a couple of short programs that will use Hashes to count 
#objects by incrementing a key value.
#Write a funcition that will count the number of letters in a phrase.
#Example "cat in the hat"  -> return:  {"t"=>3, "h"=>2, "a"=>2, "i"=>1, "n"=>1, "e"=>1, "c"=>1}
#From descending order.  Largest to smallest.  Do not include spaces.

Here is my solution:

def count_letters(str)
  count = Hash.new(0)
  str.delete(" ").each_char { |letter|  count[letter]+=1}  
  Hash[count.sort_by {|k,v| v}.reverse]
end

print count_letters("cat in the hat")

In order for me to sort it in descending order, I had to put this snippet of code:

Hash[count.sort_by {|k,v| v}.reverse]

What more refractoring can I do? Is there another way to do descending sort?

Is there better a way of doing this?

Kara
  • 6,115
  • 16
  • 50
  • 57
hken27
  • 413
  • 1
  • 5
  • 12
  • Hashes are random-access containers; Sorting their contents makes no sense and offers no advantage, except that it's prettier. Instead, you might as well sort as you retrieve their contents if it's necessary to iterate over them in a special order; In fact, some languages don't retain the hash's insertion order so it's a total waste of time sorting when you create it. – the Tin Man Oct 18 '13 at 23:31
  • 1
    Actually it looks like in 1.9, Hashes will maintain insertion order during iteration b/c they are backed by a linked list. http://www.igvita.com/2009/02/04/ruby-19-internals-ordered-hash/ – Alex.Bullard Oct 18 '13 at 23:35
  • They maintain insertion order, until another hash entry is added that is *not* in the order, then what? Re-sort the elements and create a new hash? Ruby doesn't have a sorted-hash class, because, again, what's the point? It's not an overly useful feature unless you later want to iterate over the hash, similarly to how we'd iterate over an array of two-element sub-arrays. And, if that's the goal, then an array of two-element arrays is going to be less overhead. – the Tin Man Oct 19 '13 at 01:08
  • @theTinMan I've found keeping insertion order very handy in many situations where generic `Hash` can enough represent your data structures without need to reinvent your Hash-like class. Just for an illustration Python lacked ordered hash for a long time until after many requests there were introduced `OrderedDict` in the standard `collections` module. – David Unric Oct 19 '13 at 10:18

3 Answers3

3

You can avoid the reverse by sorting by -v

def count_letters(str)
  counts = str.delete(' ').each_char.inject(Hash.new(0)) {|a,c| a[c] += 1; a}
  Hash[counts.sort_by {|_,v| -v}]
end
Alex.Bullard
  • 5,533
  • 2
  • 25
  • 32
  • I think there's a small improvement using scan: `counts = str.scan(/[A-Za-z]/).inject(Hash.new(0)) {|a,c| a[c] += 1; a}` – jsuth Dec 11 '19 at 09:18
2

Typically we'd do it like this:

def count_letters(s)
  Hash[s.delete(' ').split('').group_by{ |c| c }.map{ |k, v| [k, v.size] }]
end

print count_letters("cat in the hat")
# >> {"c"=>1, "a"=>2, "t"=>3, "i"=>1, "n"=>1, "h"=>2, "e"=>1}

Sorting it is then easy:

def count_letters(s)
  Hash[
    s.delete(' ')
     .split('')
     .group_by{ |c| c }
     .map{ |k, v| [k, v.size] }
     .sort_by{ |k, v| [-v, k] }
  ]
end

print count_letters("cat in the hat")
# >> {"t"=>3, "a"=>2, "h"=>2, "c"=>1, "e"=>1, "i"=>1, "n"=>1}

The resulting sort is descending by count, and ascending by character when count is the same.

I'm sorting in the method, but for real work I'd not do a sort unless I needed to, and then I'd do it only where it needed to be sorted. Doing it for every hash is a waste since it doesn't speed up the retrieval of the values.


From running benchmarks, we know that using -v isn't the best way to reverse the sort order. It's actually faster to use v and then append reverse to the resulting array.

Community
  • 1
  • 1
the Tin Man
  • 158,662
  • 42
  • 215
  • 303
2

Solution:

 def  letter_count(word)
    hash = {}
    hash.default = 0 
    letters = word.downcase.chars
    letters.each do |letter| 
        hash[letter] +=1
  end
  p hash
end

Answer:

letter_count("I love you")
{"i"=>1, "l"=>1, "o"=>2, "v"=>1, "e"=>1, "y"=>1, "u"=>1}
NathanOliver
  • 171,901
  • 28
  • 288
  • 402
Alwan Mortada
  • 278
  • 3
  • 10