第30回オフラインリアルタイムどう書く「とある世界のタクシー料金」の ruby による実装


問題 http://nabetani.sakura.ne.jp/hena/ord30taxi/
実装リンク集 http://qiita.com/Nabetani/items/c70417d384720a3339d6
イベント https://yhpg.doorkeeper.jp/events/22168

で。
当日お見せした実装

DISTS=->(){
  raw="AB1090e  AC180e  AD540r  BC960e  BG1270e  CD400r  CF200e  DE720r  DF510r  EG1050r  FG230r"
  raw.scan( /([A-Z]{2})(\d+)([er])/ ).each.with_object({}){ |(path, dist, city),o|
    o[path]=[dist.to_i, city.to_sym].freeze
  }
}.().freeze

def solve_impl( paths, dist, cost )
  return cost if paths.empty?
  curpos=paths[0]
  if curpos[0]<dist
    solve_impl( paths.drop(1), dist-curpos[0], cost )
  else
    solve_impl( paths, 200+dist, cost+{e:60, r:50}[curpos[1]] )
  end
end

def solve( src )
  paths=src.chars.each_cons(2).map{ |a,b|
    DISTS[a+b] || DISTS[b+a]
  }
  start =  "ABC".include?(src[0]) ? [995,400] : [845,350]
  solve_impl( paths, *start ).to_s
end

DATA.each.map do |line|
  ix, src, expected = line.split(/\s+/)
  next unless src
  actual = solve( src )
  ok = actual==expected
  puts [ ok ? nil : "**NG**", ix, src, actual, ok ? nil : expected ].compact.join("  ")
  ok
end.all?.tap{ |all_ok|
  puts "Everything is Okay!" if all_ok
}
__END__
0 ADFC  510
1 CFDA  500

いつも通りテストデータの大半を省略。

スタックの消費が気になるところだけど、再帰呼び出しで実装。

計算のロジックは見ての通り簡単。solve_impl が主要部。paths の経路を進むんだけど、距離 dist までは料金計算済み。計算済みの料金は cost。という状況を進めている。
初乗りの部分は再帰呼び出しにはいらないので外に出して、 solve が担当。

配列に対して drop(1) を繰り返しているのがちょっと残念で、計算量を考えたら C++ の std::deque のようなものに載せたいところだけど、そういうものは手軽には ruby にはないので仕方ないし、そんなことを気にする前に割り算をして計算量を減らすべき。

難易度としては普通ぐらいになったみたい。
出題者の気分としては、前回の問題( http://nabetani.sakura.ne.jp/hena/ord29devdice/ )よりもちょっと面倒で書きにくい感じもするんだけど。

ciel さんが http://qiita.com/cielavenir/items/14b9f8b38af1c61aca12 に書いている通り、初乗りの距離の1の位が 5 で、町と町の間の距離の1の位がゼロなのは、境界条件で楽をするため。
出題側も厳密に定義しなくて済むので楽だし、実装側も適当に書いても合うので楽。

ちなみに。
町の名前「円来」「炭州」は、エンライテンド と レジスタンス に由来しているので「えんらい」「たんす」と読むんだけど、私自身はかのゲームのプレイヤーではない。