読者です 読者をやめる 読者になる 読者になる

Mae向きなブログ

Mae向きな日記のブログ版。ようやくこちらに移行してきました。

coerce規約

3ヶ月研修 Ruby

id:rahaema:20080708, id:rahaema:20080709 でcoerce規約について書きましたが、まだまだ理解が不十分なので引き続き取り組んでみました。

理解をするために、何か作ろうと思ったのですが、いい題材が思い浮かばず…。結局、「帯分数(mixed number)」を取り扱うことにしました。

練習用なので、加算しかできません。

mixed_number.rb

def MixedNumber(integer, fraction)
  MixedNumber.new(integer, fraction)
end

class MixedNumber < Numeric
  attr_accessor :integer, :fraction
  def initialize(integer, fraction)
    @integer = integer
    @fraction = fraction
  end

  def + (other)
    if other.kind_of?(MixedNumber)
      integer = @integer + other.integer
      fraction = @fraction + other.fraction
      if fraction.numerator > fraction.denominator # 仮分数のとき
        q, r = fraction.numerator.
          divmod(fraction.denominator)
        integer += q
        fraction = Rational(r, fraction.denominator)
      end
      MixedNumber.new(integer, fraction)
    elsif other.kind_of?(Rational)
      self + MixedNumber.new(0, other)
    elsif other.kind_of?(Integer)
      self + MixedNumber.new(0, Rational(other, 1))
    elsif other.kind_of?(Float)
      Float(self) + other
    else
      x, y = other.coerce(self)
      x + y
    end
  end

  def coerce(other)
    if other.kind_of?(Integer)
      return MixedNumber.new(0, Rational(other, 1)), self
    elsif other.kind_of?(Rational)
      up = @integer * @fraction.denominator + 
        @fraction.numerator
      return other, Rational(up, @fraction.numerator)
    else
      return other, self.to_f
    end
  end

  def to_f
    @integer + @fraction.to_f
  end

  def to_s
    @integer.to_s + "+" + @fraction.to_s
  end

  def inspect
    sprintf("MixedNumber(%s, %s)", 
            @integer.inspect, @fraction.inspect)
  end
end

実行してみます。Test::Unitフレームワークの勉強をかねて、以下のようなテストケースを作成してみました(本来なら、テストケースを先に作る)。

./test/test_mixed_number.rb

$:.unshift File.join(File.dirname(__FILE__), "..")
require 'mixed_number'
require 'rational'
require 'test/unit'

class TestMixedNumber < Test::Unit::TestCase
  def test_plus
    mixednum1 = MixedNumber(2, Rational(3, 5)) # 2+3/5
    mixednum2 = MixedNumber(1, Rational(3, 4)) # 1+3/4
    # MixedNumber + MixedNumber
    assert_equal("4+7/20", (mixednum1 + mixednum2).to_s)
    # MixedNumber + Integer
    assert_equal("7+3/5", (mixednum1 + 5).to_s)
    # MixedNumber + Float
    assert_equal("6.1", (mixednum1 + 3.5).to_s)
    # Integer + MixedNumber
    assert_equal("7+3/5", (5 + mixednum1).to_s)
    # Float + MixedNumber
    assert_equal("6.1", (3.5 + mixednum1).to_s)
    # MixedNumber + Rational
    assert_equal("3+1/5", (mixednum1 + Rational(3,5)).to_s)
    # Rational + MixedNumber
    assert_equal("74/15", (Rational(3,5) + mixednum1).to_s)
  end
end

実行結果

mmasa@debian:~/work/ruby/mixed_number/test$ ruby test_mixed_number.rb
Loaded suite test_mixed_number
Started
.
Finished in 0.001096 seconds.

1 tests, 7 assertions, 0 failures, 0 errors

学習のポイントは、

  • (良い題材かどうか分かりませんが、)「帯分数」を題材にして、coerce規約について理解する
  • Test::Unitフレームワークを使ってみる

でしたが、実際、作ってみて、coerce規約についての理解が少し進んだと思います。