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?
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: ◼︎
Related
I have a question about mysterious 'e' characters appearing in my counts hash.
My initial approach was clunky and inelegant:
def letter_count(str)
counts = {}
words = str.split(" ")
words.each do |word|
letters = word.split("")
letters.each do |letter|
if counts.include?(letter)
counts[letter] += 1
else
counts[letter] = 1
end
end
end
counts
end
This approach worked, but I wanted to make it a little more readable, so I abbreviated it to:
def letter_count(str)
counts = Hash.new(0)
str.split("").each{|letter| counts[letter] += 1 unless letter == ""}
counts
end
This is where I encountered the issue, and fixed it by using:
str.split("").each{|letter| counts[letter] += 1 unless letter == " "} # added a space.
I don't understand why empty spaces were being represented by the letter 'e' or being counted at all.
I don't understand why empty spaces were being represented by the letter 'e' or being counted at all.
I can't duplicate the problem:
def letter_count(str)
counts = Hash.new(0)
str.split("").each{|letter| counts[letter] += 1 unless letter == ""}
counts
end
letter_count('a cat') # => {"a"=>2, " "=>1, "c"=>1, "t"=>1}
"empty spaces"? There's no such thing. A space is not empty; It's considered blank but not empty:
' '.empty? # => false
Loading the ActiveSupport extension:
require 'active_support/core_ext/object/blank'
' '.blank? # => true
spaces are valid characters, which is why they're being counted. You have to disallow them if you don't want them counted.
For reference, here's how I'd do it:
def letter_count(str)
str.chars.each_with_object(Hash.new(0)) { |l, h| h[l] += 1 }
end
letter_count('a cat') # => {"a"=>2, " "=>1, "c"=>1, "t"=>1}
A messier way would be:
def letter_count(str)
str.chars.group_by { |c| c }.map { |char, chars| [char, chars.count] }.to_h
end
Breaking that down:
def letter_count(str)
str.chars # => ["a", " ", "c", "a", "t"]
.group_by { |c| c } # => {"a"=>["a", "a"], " "=>[" "], "c"=>["c"], "t"=>["t"]}
.map { |char, chars| [char, chars.count] } # => [["a", 2], [" ", 1], ["c", 1], ["t", 1]]
.to_h # => {"a"=>2, " "=>1, "c"=>1, "t"=>1}
end
Ruby already has String#each_char which you could use.
def char_count(string)
counts = Hash.new(0)
string.each_char { |char|
counts[char] += 1
}
return counts
end
puts char_count("Basset hounds got long ears").inspect
# {"B"=>1, "a"=>2, "s"=>4, "e"=>2, "t"=>2, " "=>4, "h"=>1,
# "o"=>3, "u"=>1, "n"=>2, "d"=>1, "g"=>2, "l"=>1, "r"=>1}
As for why you're getting the wrong characters, are you sure you're passing in the string you think you are?
Trying to get the most occurring letter in a string.
So far:
puts "give me a string"
words = gets.chomp.split
counts = Hash.new(0)
words.each do |word|
counts[word] += 1
end
Does not run further than asking for a string. What am I doing wrong?
If you're running this in irb, then the computer may think that the ruby code you're typing in is the text to analyse:
irb(main):001:0> puts "give me a string"
give me a string
=> nil
irb(main):002:0> words = gets.chomp.split
counts = Hash.new(0)
words.each do |word|
counts[word] += 1
end=> ["counts", "=", "Hash.new(0)"]
irb(main):003:0> words.each do |word|
irb(main):004:1* counts[word] += 1
irb(main):005:1> end
NameError: undefined local variable or method `counts' for main:Object
from (irb):4:in `block in irb_binding'
from (irb):3:in `each'
from (irb):3
from /Users/agrimm/.rbenv/versions/2.2.1/bin/irb:11:in `<main>'
irb(main):006:0>
If you wrap it in a block of some sort, you won't get that confusion:
begin
puts "give me a string"
words = gets.chomp.split
counts = Hash.new(0)
words.each do |word|
counts[word] += 1
end
counts
end
gives
irb(main):001:0> begin
irb(main):002:1* puts "give me a string"
irb(main):003:1> words = gets.chomp.split
irb(main):004:1> counts = Hash.new(0)
irb(main):005:1> words.each do |word|
irb(main):006:2* counts[word] += 1
irb(main):007:2> end
irb(main):008:1> counts
irb(main):009:1> end
give me a string
foo bar
=> {"foo"=>1, "bar"=>1}
Then you can work on the fact that split by itself isn't what you want. :)
This should work:
puts "give me a string"
result = gets.chomp.split(//).reduce(Hash.new(0)) { |h, v| h.store(v, h[v] + 1); h }.max_by{|k,v| v}
puts result.to_s
Output:
#Alan ➜ test rvm:(ruby-2.2#europa) ruby test.rb
give me a string
aa bbb cccc ddddd
["d", 5]
Or in irb:
:008 > 'This is some random string'.split(//).reduce(Hash.new(0)) { |h, v| h.store(v, h[v] + 1); h }.max_by{|k,v| v}
=> ["s", 4]
Rather than getting a count word by word, you can process the whole string immediately.
str = gets.chomp
hash = Hash.new(0)
str.each_char do |c|
hash[c] += 1 unless c == " " #used to filter the space
end
After getting the number of letters, you can then find the letter with highest count with
max = hash.values.max
Then match it to the key in the hash and you're done :)
puts hash.select{ |key| hash[key] == max }
Or to simplify the above methods
hash.max_by{ |key,value| value }
The compact form of this is :
hash = Hash.new(0)
gets.chomp.each_char { |c| hash[c] += 1 unless c == " " }
puts hash.max_by{ |key,value| value }
This returns the highest occurring character within a given string:
puts "give me a string"
characters = gets.chomp.split("").reject { |c| c == " " }
counts = Hash.new(0)
characters.each { |character| counts[character] += 1 }
print counts.max_by { |k, v| v }
str = 'put returns between paragraph put returns between paragraph put returns between paragraph'
def word_count(string)
resut= []
return result = string.split.inject(Hash.new(0)) { |h,v| h[v] += 1; h }
end
def parse_word(word)
word.gsub!(/[^a-zA-Z0-9]/, " ")
word.downcase!
#yoo= word
end
result =word_count(str)
print result, "\n\n"
res2 = result.select { |pair| pair[1] > 1 } `#Error coming`
I am getting OutPut
**
OutPut
**
{"put"=>3, "returns"=>3, "between"=>3, "paragraph"=>3}
I need OutPut Like this
**
OutPut
**
{"put"=>3, "returns"=>3, "between"=>3, "paragraph"=>3}
and
put: 3
returns: 3
between: 3
but the main problem is that he gave us the code to do that but i cant able to understand it
I am not getting this what this code will do can anyone help me ...And modify it so it can work
The following processes the first paragraph of put returns ... Note that ss is an array of those words that occur at least twice in this paragraph.
nect = ss.select { |p| p[1] > 1 }
nect .sort.each do |key, count|
puts "#{key}: #{count}"
end
module WordCount
def self.word_count(s)
count_frequency(words_from_string(s))
end
def self.word_count_from_file(filename)
s = File.open(filename) { |file| file.read }
word_count(s)
end
def self.words_from_string(s)
s.downcase.scan(/[\w']+/)
end
def self.count_frequency(words)
counts = Hash.new(0)
for word in words
counts[word] += 1
end
# counts.to_a.sort {|a,b| b[1] <=> a[1]}
# sort by decreasing count, then lexicographically
counts.to_a.sort do |a,b|
[b[1],a[0]] <=> [a[1],b[0]]
end
end
end
def word_count(s)
WordCount.word_count(s)
end
Please help to explain what is needed in my code to decipher if the array contains an integer, if it does I need to add 1 to it and if doesn't if will just display the string or symbol.
I have left #notes where my brain stopped working
# possible arrays
# array = [1, "two", :three]
# array = [1, 2, 3]
class Array
def new_map
result = []
self.each do |item|
yield(item)
if #check to see if item is an integer
result << item + 1
else
# add string to result array
end
end
result
end
end
Here is the Rspec test:
describe "Array" do
describe "new_map" do
it "should not call map" do
a = [1, 2, 3]
a.stub(:map) { '' }
a.new_map { |i| i + 1 }.should eq([2, 3, 4])
end
it "should map any object" do
a = [1, "two", :three]
a.new_map { |i| i.class }.should eq([Fixnum, String, Symbol])
end
end
end
if item.is_a? Integer
result << item + 1
class Array
def new_map
result = []
self.each do |item|
yield(item)
if item.class == Integer # or if item.is_a? Integer
result << item + 1
else
# add string to result array
end
end
result
end
end
example:
=> 1.is_a? Integer
=> true
=> "1".is_a? Integer
=> false
=> 1_000_000.is_a? Integer
=> true
=> 1_000_000.class
=> Fixnum
=> 1_000_000.is_a? Integer
=> true
Try this:
class Array
def new_map
map do |item|
yield(item)
end
end
end
Actually you first spec does not make sense. You yield i + 1 to the block. This must fail if ì is not a Fixnum. You must not check if something is an Integer in your method, but in the block. This should work:
describe "Array" do
describe "new_map" do
it "should not call map" do
a = [1, 2, 3]
a.new_map { |i| (i.is_a?(Integer) ? i + 1 : i) }.should eq([2, 3, 4])
end
it "should map any object" do
a = [1, "two", :three]
a.new_map { |i| i.class }.should eq([Fixnum, String, Symbol])
end
end
end
class Array
def new_map
result = []
self.each do |item|
result << yield(item)
end
result
end
end
And both your tests pass, don't check object's class unnecessarily, trust duck typing.
In order to implement auto-vivification of Ruby hash, one can employ the following class
class AutoHash < Hash
def initialize(*args)
super()
#update, #update_index = args[0][:update], args[0][:update_key] unless
args.empty?
end
def [](k)
if self.has_key?k
super(k)
else
AutoHash.new(:update => self, :update_key => k)
end
end
def []=(k, v)
#update[#update_index] = self if #update and #update_index
super
end
def few(n=0)
Array.new(n) { AutoHash.new }
end
end
This class allows to do the following things
a = AutoHash.new
a[:a][:b] = 1
p a[:c] # => {} # key :c has not been created
p a # => {:a=>{:b=>1}} # note, that it does not have key :c
a,b,c = AutoHash.new.few 3
b[:d] = 1
p [a,b,c] # => [{}, {:d=>1}, {}] # hashes are independent
There is a bit more advanced definition of this class proposed by Joshua, which is a bit hard for me to understand.
Problem
There is one situation, where I think the new class can be improved. The following code fails with the error message NoMethodError: undefined method '+' for {}:AutoHash
a = AutoHash.new
5.times { a[:sum] += 10 }
What would you do to handle it? Can one define []+= operator?
Related questions
Is auto-initialization of multi-dimensional hash array possible in Ruby, as it is in PHP?
Multiple initialization of auto-vivifying hashes using a new operator in Ruby
ruby hash initialization r
still open: How to create an operator for deep copy/cloning of objects in Ruby?
There is no way to define a []+= method in ruby. What happens when you type
x[y] += z
is
x[y] = x[y] + z
so both the [] and []= methods are called on x (and + is called on x[y], which in this case is an AutoHash). I think that the best way to handle this problem would be to define a + method on AutoHash, which will just return it's argument. This will make AutoHash.new[:x] += y work for just about any type of y, because the "empty" version of y.class ('' for strings, 0 for numbers, ...) plus y will almost always equal y.
class AutoHash
def +(x); x; end
end
Adding that method will make both of these work:
# Numbers:
a = AutoHash.new
5.times { a[:sum] += 10 }
a[:sum] #=> 50
# Strings:
a = AutoHash.new
5.times { a[:sum] += 'a string ' }
a[:sum] #=> "a string a string a string a string a string "
And by the way, here is a cleaner version of your code:
class AutoHash < Hash
def initialize(args={})
super
#update, #update_index = args[:update], args[:update_key]
end
def [](k)
if has_key? k
super(k)
else
AutoHash.new :update => self, :update_key => k
end
end
def []=(k, v)
#update[#update_index] = self if #update and #update_index
super
end
def +(x); x; end
def self.few(n)
Array.new(n) { AutoHash.new }
end
end
:)
What I think you want is this:
hash = Hash.new { |h, k| h[k] = 0 }
hash['foo'] += 3
# => 3
That will return 3, then 6, etc. without an error, because the the new value is default assigned 0.
require 'xkeys' # on rubygems.org
a = {}.extend XKeys::Hash
a[:a, :b] = 1
p a[:c] # => nil (key :c has not been created)
p a # => { :a => { :b => 1 } }
a.clear
5.times { a[:sum, :else => 0] += 10 }
p a # => { :sum => 50 }