Search code examples
rubyfrequency

Ruby: calculate the frequencies of the string text letters


I was creating a ruby programme that will calculate the frequency of each letter appearing in my text and will be return it as a Hash.

Below is my code:

class LetterHistogram
  attr_reader :letters
  attr_accessor :text

  def initialize(t = "Hello World!")
    @text = t
  end

  def display
    calculateFrequencies
    ("A".."Z").each {|x| puts "#{x}: " + "*" * letters[x]}
  end

  private
  attr_writer :letters

  def calculateFrequencies
    calcuFreq = String.new(text)
    calcuFreq.upcase!.gsub!(/\W+/, '')
    letters.clear
    letters.default = 0
    calcuFreq.each_char {|char| letters[char] += 1}
  end
end

But I getting this error when I run the display method enter image description here

What is the error means and how to solve it?


Solution

  • The main problem is that in calculateFrequencies you are using a not assigned variable: letters. So, when you call calculateFrequencies in display, letters = nil and calling .clear on nil returns the error.

    This is a modified version of the code, using snake_case (which is the Ruby writing standard).

    class LetterHistogram
      attr_accessor :text
    
      def initialize(t = "Hello World!")
        @text = t
      end
    
      def display
        calculate_frequencies.each { |letter, freq| puts "#{letter}: #{freq}"}
      end
    
      private
    
      def calculate_frequencies
        freq = @text.upcase.gsub!(/\W+/, '').each_char.with_object(Hash.new(0)) { |letter, freq| freq[letter] += 1 }
        freq.sort_by{ |letter, freq| freq }.reverse # just to sort
      end
    end
    

    Instantiating an object and calling .display on it:

    senctence = LetterHistogram.new
    senctence.display
    
    #=> L: 3 
    #=> O: 2 
    #=> D: 1 
    #=> R: 1 
    #=> W: 1 
    #=> E: 1 
    #=> H: 1 
    

    How it works

    For calculating the frequency I used a Hash populated by: https://ruby-doc.org/core-2.5.1/Enumerable.html#method-i-each_with_object

    Printing out freq from calculate_frequencies you can see it:

    #=> {"H"=>1, "E"=>1, "L"=>3, "O"=>2, "W"=>1, "R"=>1, "D"=>1}
    


    Alternatively, if you want also not used letters, you can initialise the freq hash with all values to 0, then update the hash, something like this:

    freq = ("A".."Z").each_with_object({}) { |letter, freq| freq[letter] = 0 }
    "Hello World!".upcase.gsub!(/\W+/, '').each_char { |letter| freq[letter] += 1 }
    
    #=> {"A"=>0, "B"=>0, "C"=>0, "D"=>1, "E"=>1, "F"=>0, "G"=>0, "H"=>1, "I"=>0, "J"=>0, "K"=>0, "L"=>3, "M"=>0, "N"=>0, "O"=>2, "P"=>0, "Q"=>0, "R"=>1, "S"=>0, "T"=>0, "U"=>0, "V"=>0, "W"=>1, "X"=>0, "Y"=>0, "Z"=>0}
    

    Finally, to print out the istogram:

    freq.each { |letter, freq| puts "#{letter}: " + "◼︎" * freq if freq > 0 }
    
    #=> D: ◼︎
    #=> E: ◼︎
    #=> H: ◼︎
    #=> L: ◼︎◼︎◼︎
    #=> O: ◼︎◼︎
    #=> R: ◼︎
    #=> W: ◼︎