動的計画法で部屋の割り当て

Rubyによる情報科学入門」(久野靖著)にあった動的計画法をやってみた。またしてもコードがRubyらしくない気がしたので、書き換えてみた。

動的計画法は、ある条件を満たす解を見つけるのに、その解だけを求めるのじゃなくて、初期状態から順次計算していくことで大幅に計算量を減らすアプローチの一般的な呼び方らしい。Ruby情報科学本には、編集距離の計算やパターン認識動的計画法の応用例に挙がっている。

問題は1人部屋5000円、3人部屋1万2000円、7人部屋2万円の旅館があったとき、n人がもっと安く泊まるのに必要な部屋の組み合わせを求めよ、というもの。

=begin
Dynamic programming excercise

[Room charge]
Type A ...  5000yen   accomodates 1 person
Type B ... 12000yen   accomodates 3 persons
Type C ... 20000yen   accomodates 7 persons

Given the number of people(n < 100), what's the cheapest reservation
choice? 
=end

class Room

  attr_reader :price, :rooms

  def initialize(max)
    @price = Array.new(max, 0)
    @rooms = [0]
    @max = max
    make_table
  end

  def make_table
    (1...@max).each do |i|
      @price[i] = @price[i - 1] + 5000
      room = 1
      
      if @price[i] > @price[i - 3] + 12000
        @price[i] = @price[i - 3] + 12000
        room = 3
      end
      
      if @price[i] > @price[i - 7] + 20000
        @price[i] = @price[i - 7] + 20000
        room = 7
      end
      @rooms << room
    end
  end
end

if __FILE__ == $0

  r = Room.new(100)

  while true
    print "How many people? : "
    n = gets.to_i
    print "#{r.price[n]} yen\n"
    while (n > 0) do
      print "#{r.rooms[n]} "
      n = n - r.rooms[n]
    end
    puts
  end

end

ほとんど本にあるものの翻案だけど、やってみるとバグる。配列をインデックスアクセスするのって意味が分かりづらいから、@rooms << roomとしたのだけど、このとき、初期化のところで、

@price = Array.new(max, 0)
@rooms = Array.new(max, 0)

のままにしてしまった。必要な量を確保してpushすると、0,0,0,0,……,1,2,3,4のように最初のほうは0行進になるに決まってるのだけど、実行するまで気づかなかった。

次に、

@price = Array.new(max, 0)
@rooms = []

として失敗した。配列の添字は、1人のところから始めているので、結果がひとつずつずれた。正しくは、

@price = Array.new(max, 0)
@rooms = [0]

とする必要があった。