演習問題回答例

演習問題1

属性として身長と体重を追加しよう。体重は秘密にしよう。

仕様を決める。

  • initialize で height, weight を渡す
  • 省略時は nil
class Person
  @@variables = [:@name, :@born, :@height, :@weight]

  def initialize name, born = nil, height = nil, weight = nil
    @name, @born, @height, @weight = name, born, height, weight
  end

  attr_accessor :height
  attr_writer :weight

  def hash
    @@variables.map{|var| instance_variable_get(var)}.hash
  end

  def eql? o
    @@variables.all? do |var|
      instance_variable_get(var).eql? o.instance_variable_get(var)
    end
  end
end

属性が増えたので、それに合わせて hash, eql? も変更した。

演習問題2

BMI を計算するメソッドを追加しよう。
BMI = \frac{w}{t^2}
w = 体重[kg]
t = 身長[m]

仕様を決める。

  • height は Float で、単位は cm
  • weight は Float で、単位は kg
  • @height, @weight は Float でなくても、あるていど動くようにする
  • 計算できない場合は NaN を返す
class Person
  def bmi
    Float(@weight) / (Float(@height) / 100.0) ** 2
  rescue
    Float::NAN
  end
end

演習問題3

Person#<=> を書き直そう。
その妥当な仕様は?
p0 = Person.new('matz')
p1 = Person.new('Matz', Time.local(1965, 4, 14))

p0 <=> p1   # => ?

仕様を決める。

  • 単純に全ての属性に対して <=> を評価する
  • 比較対象が Person のインスタンスでなければ nil を返す
class Person
  def <=> o
    return nil unless o.kind_of? Person

    @@variables.each do |var|
      lhs = instance_variable_get(var)
      cmp = lhs <=> o.instance_variable_get(var)

      next if cmp == 0

      return cmp unless cmp.nil?
      return  -1 if lhs.nil?
      return   1
    end

    0
  end
end

演習問題4

クラス Person には、いくつかのバグがある。 それを見つけ出して修正しよう。

バグを探す。

  • @born に strftime メソッドがないと age で例外が発生する
    • 計算できなければ nil を返す
  • to_s が age を使用しているので、以下同文
    • age が nil なら、@name のみとする
class Person
  def age
    (Time.now.strftime('%Y%m%d').to_i -
     @born.strftime('%Y%m%d').to_i) / 1_00_00
  rescue
    nil
  end

  def to_s
    if a = age
      "#{name}(#{a})"
    else
      name
    end
  end
end

こればバグか?

matz = Person.new('matz')
matz.freeze
matz.name                       # => "matz"

matz.name.upcase!
matz.name                       # => "MATZ"

グループワークのときに質問してみたら、みんなこれは放置しているそうだ。

あえて対応するなら、

class Person
  def name
    @name.dup
  end
end

matz.name.upcase!       # => "MATZ"
matz.name               # => "matz"

間違いがあれば、指摘くださると助かります。