Difference between + and << in Ruby - ruby

Below are two identical classes with the difference of the operators + and <<. These can be found in the inject method. In the + case the tests pass and in the << some of them fail. Why?
class Integer
ROMAN_NUMERALS = {
0 => '',
1 => 'I', 2 => 'II', 3 => 'III', 4 => 'IV', 5 => 'V', 6 => 'VI', 7 => 'VII', 8 => 'VIII', 9 => 'IX',
10 => 'X', 20 => 'XX', 30 => 'XXX', 40 => 'XL', 50 => 'L', 60 => 'LX', 70 => 'LXX', 80 => 'LXXX', 90 => 'XC',
100 => 'C', 200 => 'CC', 300 => 'CCC', 400 => 'CD', 500 => 'D', 600 => 'DC', 700 => 'DCC', 800 => 'DCCC', 900 => 'CM',
1000 => 'M', 2000 => 'MM', 3000 => 'MMM'
}
def to_roman
to_s.reverse.chars.each_with_index.inject("") do |roman_numeral, (character, index)|
ROMAN_NUMERALS[character.to_i * 10 ** index] << roman_numeral
end
end
end
I get different results to when I run
class Integer
ROMAN_NUMERALS = {
0 => '',
1 => 'I', 2 => 'II', 3 => 'III', 4 => 'IV', 5 => 'V', 6 => 'VI', 7 => 'VII', 8 => 'VIII', 9 => 'IX',
10 => 'X', 20 => 'XX', 30 => 'XXX', 40 => 'XL', 50 => 'L', 60 => 'LX', 70 => 'LXX', 80 => 'LXXX', 90 => 'XC',
100 => 'C', 200 => 'CC', 300 => 'CCC', 400 => 'CD', 500 => 'D', 600 => 'DC', 700 => 'DCC', 800 => 'DCCC', 900 => 'CM',
1000 => 'M', 2000 => 'MM', 3000 => 'MMM'
}
def to_roman
to_s.reverse.chars.each_with_index.inject("") do |roman_numeral, (character, index)|
ROMAN_NUMERALS[character.to_i * 10 ** index] + roman_numeral
end
end
end
The tests I am using are below
require 'minitest/autorun'
require_relative 'roman'
class RomanTest < MiniTest::Unit::TestCase
def test_1
assert_equal 'I', 1.to_roman
end
def test_2
assert_equal 'II', 2.to_roman
end
def test_3
assert_equal 'III', 3.to_roman
end
def test_4
assert_equal 'IV', 4.to_roman
end
def test_5
assert_equal 'V', 5.to_roman
end
def test_6
assert_equal 'VI', 6.to_roman
end
def test_9
assert_equal 'IX', 9.to_roman
end
def test_27
assert_equal 'XXVII', 27.to_roman
end
def test_48
assert_equal 'XLVIII', 48.to_roman
end
def test_59
assert_equal 'LIX', 59.to_roman
end
def test_93
assert_equal 'XCIII', 93.to_roman
end
def test_141
assert_equal 'CXLI', 141.to_roman
end
def test_163
assert_equal 'CLXIII', 163.to_roman
end
def test_402
assert_equal 'CDII', 402.to_roman
end
def test_575
assert_equal 'DLXXV', 575.to_roman
end
def test_911
assert_equal 'CMXI', 911.to_roman
end
def test_1024
assert_equal 'MXXIV', 1024.to_roman
end
def test_3000
assert_equal 'MMM', 3000.to_roman
end
end
See how the specs fail in one case but not in the other. I thought these are meant to work in the same way.

This line is a problem...
ROMAN_NUMERALS[character.to_i * 10 ** index] << roman_numeral
It will return a string which is the value of the correct ROMAN_NUMERALS key plus roman_numeral, which is what you want, BUT it is also changing the value in the ROMAN_NUMERALS hash! The shovel operator << changes the string on the left of the operator (it's what we call a mutating operator).
So if you test for 1001 the unit 1 will return "I" (that's fine) then the zero will return an empty string BUT will change the value for zero into "I"... the second zero will return "I" (incorrect) and will change the value for zero into "II". The 1 in the thousands position will return "M" but then change the hash value into "MII".

When the line ROMAN_NUMERALS[character.to_i * 10 ** index] << roman_numeral is being executed you are replacing the value corresponding to the key character.to_i * 10 ** index with its value plus roman_numeral.

Now that your question has been answered, I would like to suggest an alternative method and also a different way to perform your tests. This requires Ruby v1.9+, so we can depend on the order of the hash keys.
Code
First, reverse the order of the hash elements.
RNR = Hash[ROMAN_NUMERALS.to_a.reverse]
#=> {3000=>"MMM", 2000=>"MM", 1000=>"M",..., 2=>"II", 1=>"I", 0=>""}
Then:
class Integer
def to_roman
num = self
roman = ""
while num > 0
i,r = RNR.find { |i,r| i <= num }
roman << r
num -= i
end
roman
end
end
Test objectives
We need to test a substantial number of integer values and make sure that we are testing each integer against the correct roman numeral equivalent. Both of these objectives can be met by creating a method that converts roman numerals to integers:
RNRI = RNR.invert
#=> {"MMM"=>3000, "MM"=>2000, "M"=>1000,..., "II"=>2, "I"=>1, ""=>0}
class String
def roman_to_integer
num = 0
roman = self
while roman.size > 0
r, i = RNRI.find { |r,m| roman =~ /^#{r}/ }
num += i
roman = roman[r.size..-1]
end
num
end
end
Examples
Now let's invoke both Integer#to_roman and String#roman_to_integer for various integer values:
def check_one(i)
roman = i.to_roman
puts "#{i}.to_roman = #{roman}, #{roman}.roman_to_integer = " +
#{roman.roman_to_integer}"
end
check_one(402) # 'CDII'
# 402.to_roman = CDII, CDII.roman_to_integer = 402
check_one(575) # 'DLXXV'
# 575.to_roman = DLXXV, DLXXV.roman_to_integer = 575
check_one(911) # 'CMXI'
# 911.to_roman = CMXI, CMXI.roman_to_integer = 911
check_one(1024) # 'MXXIV'
# 1024.to_roman = MXXIV, MXXIV.roman_to_integer = 1024
check_one(3000) # 'MMM'
# 3000.to_roman = MMM, MMM.roman_to_integer = 3000
Tests
So now in your testing you can use:
def test_all(n)
(1..n).each { |i| test_one(i) }
end
def test_one(i)
roman = i.to_roman
assert_equal(i, roman.roman_to_integer, "#{i}.to_roman=#{roman}, " +
"#{roman}.roman_to_integer = #{roman.roman_to_integer}")
end

Related

Converting a given number to a word using array

I am trying to create a method for the classic bottles of beers output.
Given a number, #beers, I want to convert it to the word version of the number. I wanted to split the number into an array and then compare with a list of numbers to words:
class BeerSong
attr_accessor :beers
def initialize(beers)
if beers > 99
beers = 99
elsif beers < 1
beers = "Zero"
end
#beers = beers
end
def worded
num_name = {
90 => "Ninety", 80 => "Eighty", 70 => "Seventy",60 => "Sixty",
50 => "Fifty",40 => "Forty",30 => "Thirty",20 => "Twenty",
19 => "Nineteen", 18 => "Eighteen", 17 => "Seventeen", 16 => "Sixteen",
15 => "Fifteen",14 => "Fourteen", 13 =>"Thirteen", 12 => "Twelve",
11 => "Eleven", 10 => "Ten", 9 => "Nine",8 => "Eight", 7 => "Seven",
6 => "Six",5 => "Five",4 => "Four",3 => "Three",2 => "Two",1 => "One"
}
worded = ""
beers = #beers
split_beers = beers.to_s.split
num_name.each do |number, name|
split_number = number.to_s.split
if beers == number
worded << name
else
number > 19 && split_number[0].to_i == split_beers[0].to_i
worded << name
worded << "-"
end
end
num_name.each do |number, name|
if number < 10 && split_beers[1].to_i == number
worded << name
end
end
worded
end
def print_song
while #beers.to_i > 2
puts "#{worded} bottles of beer on the wall,"
puts "#{worded} bottles of beer,"
puts "Take one down, pass it around,"
#beers -= 1
puts "#{worded} bottles of beer on the wall.\n\n"
end
if #beers.to_i == 2
puts "#{worded} bottles of beer on the wall,"
puts "#{worded} bottles of beer,"
puts "Take one down, pass it around,"
#beers -= 1
puts "#{worded} bottle of beer on the wall.\n\n"
end
if #beers.to_i == 1
puts "#{worded} bottle of beer on the wall,"
puts "#{worded} bottle of beer,"
puts "Take one down, pass it around,"
puts "Zero bottles of beer on the wall.\n\n"
end
if #beers.to_i == 0
print ""
end
end
end
I am trying to compare the first digit to get the tens, then compare the second digit for the units separated by a hyphen.
Your lines:
split_beers = beers.to_s.split
split_number = number.to_s.split
assume an array as a result which is not the case because you don't provide a pattern to split on
so the default is used. See: http://ruby-doc.org/core-2.0.0/String.html#method-i-split
puts "10".to_s.split.inspect # gives ==> ["10"]
puts "10".to_s.split('',2).inspect # gives ==> ["1", "0"]
The last is what you want.

How to convert number to word in ruby without using any gem? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 7 years ago.
Improve this question
I have a number say 100 i want it converted into one hundred in word.similary for floating point say 0.0 is zero and 3.4 to three point four
There are many gems available which convert number to word:
gem 'to_words'
humanize
numbers_and_words
But as you don't want to use gem I found something like (without any gem):
class Fixnum
def english_word
#h = { 0=>"zero", 1=>"One", 2=>"Two", 3=>"Three", 4=>"Four", 5=>"Five",6=>"six", 7=>"seven", 8=>"Eight", 9=>"Nine",10=>"Ten",11=>"Eleven",12=>"Twelve", 13=>"Thirteen",14=>"Fourteen",15=>"Fifteen", 16=>"Sixteen",17=>"Seventeen",18=>"Eighteen", 19=>"Nineteen",20=>"Twenty",30=>"Thirty", 40=>"Fourty",50=>"Fifty",60=>"Sixty",70=>"Seventy", 80=>"Eighty",90=>"Ninty" }
#i=0
#array=[]
#result=""a
if self > 99
str_num=self.to_s ##num.to_s
str_num_len=str_num.length
str_full_num=str_num.insert(0,"0"*(11-str_num_len))
str_full_num=str_num.insert(8,"0")
str_full_num.scan(/../) { |x| #array<<x }
6.times do
self.def_calc
#i+=1
end
else
if self > 9
puts (self.proc_double_dig((self/10)*10)) + (self.proc_single_dig(self%10))
else
if self > 0
puts self.proc_single_dig(self)
else
return "AMOUNT NOT KNOWN or NILL"
end
end
end
end
def def_calc
case #i
when 0
str=self.proc_unit(#array[#i])
if (str.scan(/\w+/)).length!=0
then str=str+ "hundred & "
#result=#result+str
end
when 1
str=self.proc_unit(#array[#i])
if (str.scan(/\w+/)).length!=0
then str=str+ " Crore, "
#result=#result+str
end
when 2
str=self.proc_unit(#array[#i])
if (str.scan(/\w+/)).length!=0
then str=str+ " Lakh, "
#result=#result+str
end
when 3
str=self.proc_unit(#array[#i])
if (str.scan(/\w+/)).length!=0
then str=str+ " Thousand, "
#result=#result+str
end
when 4
str=self.proc_unit(#array[#i])
if (str.scan(/\w+/)).length!=0
then str=str+ " Hundred, "
#result=#result+str
end
when 5
str=self.proc_unit(#array[#i])
if (str.scan(/\w+/)).length!=0
then str=str+ ". "
#result=#result+str
end
print #result.sub(/..$/,"")
else
end
end
def proc_unit(x)
if x.to_i>0
if x.to_i<=10
return self.proc_single_dig(x.to_i)
else
if x.to_i<=20
return self.proc_double_dig(x.to_i)
else
return (self.proc_double_dig((x.to_i/10)*10)) + (self.proc_single_dig(x.to_i%10))
end
end
end
return ""
end
def proc_double_dig(z)
if z==0
return ""
else
return #h[z]
end
end
def proc_single_dig(y)
if y==0
return ""
else
return #h[y]
end
end
protected :def_calc, :proc_unit, :proc_double_dig,
:proc_single_dig
end
puts 453645445.english_word
#FourtyFive Crore, Thirtysix Lakh, FourtyFive Thousand,Four Hundred,FourtyFive
Reference Taken from : https://raveendran.wordpress.com/2009/05/29/ruby-convert-number-to-english-word/
I hope it helps you :)
This is what i have found solution . i hope this will help others.
def in_words(int)
numbers_to_name = {
10**18 => "quintillion", 10**15 => "quadrillion", 10**12 => "trillion",
10**9 => "billion", 10**6 => "million", 1000 => "thousand", 100 => "hundred",
90 => "ninety", 80 => "eighty", 70 => "seventy", 60 => "sixty", 50 => "fifty",
40 => "forty", 30 => "thirty", 20 => "twenty", 19=>"nineteen",
18=>"eighteen", 17=>"seventeen", 16=>"sixteen", 15=>"fifteen",
14=>"fourteen", 13=>"thirteen", 12=>"twelve", 11 => "eleven", 10 => "ten",
9 => "nine", 8 => "eight", 7 => "seven", 6 => "six", 5 => "five",
4 => "four", 3 => "three", 2 => "two", 1 => "one"
}
str = ""
numbers_to_name.each do |num, name|
if int == 0
return str
elsif int.to_s.length == 1 && int/num > 0
return str + "#{name}"
elsif int < 100 && int/num > 0
return str + "#{name}" if int%num == 0
return str + "#{name} " + in_words(int%num)
elsif int/num > 0
return str + in_words(int/num) + " #{name} " + in_words(int%num)
end
end
end
def words_from_numbers number
fix = number.to_i == 0 ? "zero" : in_words(number.to_i).strip.gsub('hundred ','hundred and ')
frac = number - number.to_i == 0 ? "" : " point one"
fix + frac
end

Class equality with coercion

I created this class based on predefined tests
class Roman
include Comparable
ROMAN_NUMBERS = { 1000 => "m", 900 => "cm", 500 => "d", 400 => "cd",
100 => "c", 90 => "xc", 50 => "l", 40 => "xl", 10 => "x", 9 => "ix",
5 => "v", 4 => "iv", 1 => "i" }
def initialize(number)
#number = number
end
def coerce(other)
if Integer === other
[ other, #number ]
else
[ Float(other), Float(#number) ]
end
end
def <=>(n)
#number <=> n
end
def -(n)
Roman.new(#number - n)
end
def to_s
roman = ''
ROMAN_NUMBERS.each do |value, letter|
roman << letter * (#number / value)
#number = #number % value
end
roman
end
end
but I'm still failing this one
it "should support substraction" do
(Roman.new(5) - Roman.new(3)).should == 2
(Roman.new(4) - Roman.new(1)).should == 3
(Roman.new(4) - 2).should == 2
res = 6 - Roman.new(1)
res.should == 5
(res.kind_of? Roman).should be true
end
The problem is, that res is "kind of" Fixnum. How's that possible? How to make it pass the test?
The problem is that 6 - Roman.new(1) is the same as 6.-(Roman.new(1)) i.e. it is calling the built in subtraction method of Fixnum. Since Fixnum doesn't know about your Roman class, this calls your coerce method.
But you defined coerce to return a Fixnum in this case! The solution is to coerce the other operand, rather than self.
def coerce other
[self.class.new(other), self]
end
But if you try this with your code, you'll get a stack error! That's because it's now coercing both operands into instances of Roman and it still doesn't know how to subtract them. You should redefine your subtraction method to handle those cases.
attr_reader :number
def - n
return Roman.new(#number - n.number) if n.is_a? Roman
Roman.new(#number - n)
end

Set the value as a range of numbers in Ruby

My question is whether I can use a range as the value in a key:value pair in a hash. I am working on a problem where I am trying to return a letter grade (A-F) for an average of numerical grades (array of numbers). I have a working solution, but I came across something intriguing. Here is my code:
def get_grade(array)
avg = (array.inject {|num, x| num + x}) / array.length
grades = {
"A" => [90..10]
"B" => [80..89],
"C" => [70..79],
"D" => [60..69],
"F" => [0..59],
}
grades.default = "Error"
puts grades.key(avg)
end
arraya = [100,90,100,99,99]
puts get_grade(arraya)
I know I could return the letter grade with either a case or an if statement. It seems like I should be able to use a hash instead but it doesn't work. Why can't I set up a hash with a range as value? Thanks.
You could use a case statement:
def get_grade(scores)
case scores.inject(&:+) / scores.length
when 90..100; 'A'
when 80..89; 'B'
when 70..79; 'C'
when 60..69; 'D'
when 0..59; 'F'
else; 'Error'
end
end
arraya = [100,90,100,99,99]
puts get_grade(arraya)
#=> A
You may want to rewrite your method as the following:
def get_grade(array)
avg = array.inject(:+) / array.length
grades = {
"A" => (90..100),
"B" => (80..89),
"C" => (70..79),
"D" => (60..69),
"F" => (0..59),
}
grade = grades.find{|key, range| range.include?(avg) }
grade.nil? ? "Unknown" : grade.first
end
arraya = [100,90,100,99,99]
puts get_grade(arraya) # => A

implementing the comparison operators - Ruby

I'm new to Ruby, and I'm trying to implement a comparison between Grades, was shown in the example
include Comparable
class Grade
attr_accessor :grades, :grade
def initialize( grade = "" )
self.grades = { :nil => -1, :"F" => 0, :"D-" => 1, :"D" => 2, :"D+" => 3,
:"C-" => 4, :"C" => 5, :"C+" => 6, :"B-" => 7, :"B" => 8,
:"B+" => 9, :"A-" => 10, "A+" => 11 }
if self.grades[ grade ]
self.grade = grade
else
self.grade = nil
end
end
def <=>( other )
if self.grades[ self.grade ] < self.grades[ other.grade ]
return -1
elsif self.grades[ self.grade ] == self.grades[ other.grade ]
return 0
else
return 1
end
end
end
a_plus = Grade.new("A+")
a = Grade.new("A")
[a_plus, a].sort # should return [a, a_plus]
So, I'm getting:
grade.rb:31:in `<': comparison of Fixnum with nil failed (ArgumentError)
from grade.rb:31:in `<=>'
from grade.rb:43:in `sort'
from grade.rb:43:in `<main>'
I just want to implement Comparison between objects in Ruby
From Ruby Doc module Comparable:
Comparable uses <=> to implement the conventional comparison operators (<, <=, ==, >=, and >) and the method between?.
When you want to implement such thing, only implement <=>. The rest should automatically follow. If you define <, for example, you will mess up the class.
You can do something like this:
class Grade
include Comparable
attr_reader :index
##grades = %w[F D- D D+ C- C C+ B- B B+ A- A+]
def initialize (grade = nil); #index = ##grades.index(grade).to_i end
def <=> (other); #index <=> other.index end
end
a_plus = Grade.new("A+")
a = Grade.new("A")
a_plus > a
# => true
[a_plus, a].sort
# => `[a, a_plus]` will be given in this order
You just need to go as following:
class Foo
include Comparable
attr_reader :bar
def initialize bar
#bar = bar
end
def <=>(another_foo)
self.bar <=> another_foo.bar
end
end
So at the <=>'s definition you can add your own logic.
Comment to your original post : when you have a message like
in '>': undefined method '>' for nil:NilClass (NoMethodError)
just put print statements to display the values. Thus you would have immediately found that in initialize, self.grades[ self.grade ] was returning nil, because the parameter in Grade.new("A+") is a String, but the keys in the hash are symbols, so you need to convert with to_sym.
Your original class rearranged, with print statements (and only showing >(other)) :
class Grade
attr_reader :grade
##grades = { :nil => -1, :"F" => 0, :"D-" => 1, :"D" => 2, :"D+" => 3,
:"C-" => 4, :"C" => 5, :"C+" => 6, :"B-" => 7, :"B" => 8,
:"B+" => 9, :"A-" => 10, :"A+" => 11 }
def initialize( p_grade = "" )
#grade = p_grade if ##grades[ p_grade.to_sym ]
puts "init param=#{p_grade} value=<#{##grades[ p_grade.to_sym ]}> #grades=<#{#grade}>"
end
def >( other )
puts "in >( other ) : key1=#{self.grade} key2=#{other.grade}"
puts "in >( other ) : $#{##grades[ self.grade ]}$ ??? $#{##grades[ other.grade ]}$"
return ##grades[ self.grade ] > ##grades[ other.grade ]
end
end
print '--- Grade.new("A+") : '; a_plus = Grade.new("A+")
print '--- Grade.new("A") : '; a = Grade.new("A")
print '--- a_plus > a : '; p a_plus > a
Execution :
$ ruby -w t.rb
--- Grade.new("A+") : init param=A+ value=<11> #grades=<A+>
--- Grade.new("A") : t.rb:9: warning: instance variable #grade not initialized
init param=A value=<> #grades=<>
--- a_plus > a : in >( other ) : key1=A+ key2=
in >( other ) : $$ ??? $$
t.rb:15:in `>': undefined method `>' for nil:NilClass (NoMethodError)
from t.rb:21:in `<main>'
Grade.new("A") : as A does not exist in the hash, the instance variable #grade is not set, and self.grades[ self.grade ] > ... sends the message > to nil, an instance of NilClass which doesn't define >.
Notice the trick #grades=<#{xyz}>, surrounding the interpolated value with <> or $$ makes the display more obvious when the value is nil.
Note also the -w in ruby -w t.rb, displaying interesting warning messages.

Resources