As is my custom when learning a new language, I implement a basic custom Rational class in Ruby:

# implementation of a Rational number class in Ruby module MyRational class Rational attr_reader :numerator, :denominator def initialize(num, denom = 1) if denom == 0 raise "Denominator cannot be 0 for a rational number" end @numerator = num @denominator = denom normalize end # += etc all come for free when the basic operator is overloaded! def +(other) @numerator = @numerator * other.denominator + @denominator * other.numerator @denominator = @denominator * other.denominator normalize self end def -(other) @numerator = @numerator * other.denominator - @denominator * other.numerator @denominator = @denominator * other.denominator normalize self end def *(other) @numerator = @numerator * other.numerator @denominator = @denominator * other.denominator normalize self end def /(other) @numerator = @numerator * other.denominator @denominator = @denominator * other.numerator normalize self end def to_s if @denominator == 1 @numerator.to_s else @numerator.to_s + "/" + @denominator.to_s end end # generics class methods def self.+(f, s) n = f.numerator * s.denominator + f.denominator * s.numerator d = f.denominator * s.denominator Rational.new(n, d) end def self.-(f, s) n = f.numerator * s.denominator - f.denominator * s.numerator d = f.denominator * s.denominator Rational.new(n, d) end def self.*(f, s) n = f.numerator * s.numerator d = f.denominator * s.denominator Rational.new(n, d) end def self./(f, s) n = f.numerator * s.denominator d = f.denominator * s.numerator Rational.new(n, d) end private def gcd(x, y) if y == 0 x else gcd(y, x%y) end end def normalize() g = gcd(@numerator, @denominator) @numerator /= g @denominator /= g end end end

Taking it for a test spin:

irb(main):003:0> load "my_rational.rb" load "my_rational.rb" => true irb(main):004:0> x = MyRational::Rational.new(1,2) x = MyRational::Rational.new(1,2) => #<MyRational::Rational:0x007fd02a09bc88 @numerator=1, @denominator=2> irb(main):005:0> y = MyRational::Rational.new(3,4) y = MyRational::Rational.new(3,4) => #<MyRational::Rational:0x007fd02a0a21a0 @numerator=3, @denominator=4> irb(main):006:0> x += y x += y => #<MyRational::Rational:0x007fd02a09bc88 @numerator=5, @denominator=4> irb(main):007:0> puts x puts x 5/4 => nil irb(main):008:0> x -= y x -= y => #<MyRational::Rational:0x007fd02a09bc88 @numerator=1, @denominator=2> irb(main):009:0> puts x puts x 1/2 => nil irb(main):010:0> x *= y x *= y => #<MyRational::Rational:0x007fd02a09bc88 @numerator=3, @denominator=8> irb(main):011:0> puts x puts x 3/8 => nil irb(main):012:0> x /= y x /= y => #<MyRational::Rational:0x007fd02a09bc88 @numerator=1, @denominator=2> irb(main):013:0> puts x puts x 1/2 => nil irb(main):014:0> MyRational::Rational.+(x, y) MyRational::Rational.+(x, y) => #<MyRational::Rational:0x007fd02a0bad90 @numerator=5, @denominator=4> irb(main):015:0> puts MyRational::Rational.+(x, y) puts MyRational::Rational.+(x, y) 5/4 => nil irb(main):016:0> puts MyRational::Rational.-(x, y) puts MyRational::Rational.-(x, y) -1/4 => nil irb(main):017:0> puts MyRational::Rational.*(x, y) puts MyRational::Rational.*(x, y) 3/8 => nil irb(main):018:0> puts MyRational::Rational./(x, y) puts MyRational::Rational./(x, y) 2/3 => nil

Of course, operations like number op rational, rational op number, where “op” is an operation will not work. These could be added by extending the number classes, etc, and also adding support in the class methods, but perhaps there is a better way that I will learn when I improve my Ruby knowledge! For now, the more I use Ruby, the more I like it!

Advertisements

Nice to see you learning Ruby!

A few fun idiomatic usages to try:

1. Two spaces for indentation is the usual in Ruby

2. @numerator *= other.numerator (i.e. x = x + 1 can be written x += 1 and is idiomatic Ruby)

3. @numerator.to_s + “/” + @denominator.to_s => faster and more idiomatic to do “#{@numerator}/#{@denominator}”

4. line 8: raise “Denominator cannot be 0 for a rational number” if denom.zero?

5. lines 12 and 13 can also be written in one line: @numerator, @denominator = num, denom

6. lines 99 and 105 could be structured as a guard clause/simple case, then more complex case):

def gcd(x, y)

return x if y.zero?

gcd(y, x%y)

end

Greatly enjoying your blog posts. Cheers!

LikeLike

Thanks for the feedback, Jon! Very helpful indeed! 🙂 … and yes, your earlier tweet about Ruby kind of inspired me to take a dive. I can’t believe I’ve put it off for so long – it really is a beautiful language, and I have only myself to blame for having been deceived by the surface similarities with Python!

LikeLike