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?

4
  • 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. Commented Oct 18, 2013 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. igvita.com/2009/02/04/ruby-19-internals-ordered-hash Commented Oct 18, 2013 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. Commented Oct 19, 2013 at 1: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. Commented Oct 19, 2013 at 10:18

3 Answers 3

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
Sign up to request clarification or add additional context in comments.

1 Comment

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}
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.

2 Comments

Thank you very good explanations. what does -v mean? does it mean the same thing as reverse?
It means the negative value of v.
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}

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.