Rubyで最短経路問題に挑戦してみた
人生を書き換える者すらいた。- 人材獲得作戦・4 試験問題ほかで採用に使ったという最短経路を求める問題にRubyで挑戦してみました。
というかこれはダイクストラ法を知ってるかどうかなような...
自分も中学からプログラミングやってたけどダイクストラ法を知ったのは大学生になってからだから、高校のときだったら解けなかったでしょう。
一般レベルのプログラマーを探すだけなら他の方もいろんなところでコメントしているように問題も選択式だったらいいかもしれませんね。
もちろんこの問題を所定の時間内に解ける人は一般以上の力量があると思いますが。
自分も数学的なアルゴリズムを組むのは苦手なので恥ずかしいですが...
マップデータはSerendip Web Studio - 最短経路探索プログラムの試験問題を解いてみたさんから拝借しちゃいました。
※追記: 以下のプログラムはダイクストラ法ではなく、単なる枝狩りありの深さ優先探索な気がしてきた
※さらに追記:ん〜やっぱり確定フラグを使ってないだけのダイクストラ法の亜種な感じもしてきた...
class Map attr_reader :start attr_reader :goal # map文字列から文字列の二次元配列としてマップをロードして返す def self.parse(str) map = self.new map.parse(str) return map end # コンストラクタ def initialize() @map = [] @start = [nil, nil] @goal = [nil, nil] @cost = {} # [x,y] => [from_x, from_y, n] (@startからのコスト) end # map文字列から文字列の二次元配列としてマップをロード def parse(str) @map = [] lines = str.split(/\n/) lines.each_with_index do |line, y| line.split(//).each_with_index do |char, x| @map[x] = [] if @map[x].nil? @map[x][y] = char @start = [x, y] if char == "S" @goal = [x, y] if char == "G" end end end # 指定座標が壁かどうかを調べる def block?(x, y) return @map[x][y] == "*" end # 指定座標が道かどうかを調べる def path?(x, y) return (@map[x][y] == " " || @map[x][y] == "G") end # マップの幅 def width return @map.size end # マップの高さ def height return @map[0].size end # スタート地点から, 全ての点への距離をダイクストラ法で求める def dijkstra! sx = @start[0] sy = @start[1] @cost[ [sx, sy] ] = [nil, nil, 0] nodes_around_start = available_around_nodes(sx, sy) dijkstra_from(sx, sy) end # @costに記録されている最短コストと, # その場合の経路を逆にたどりひっくり返してstartからの経路にする def shortest_path_to_goal path = [ [@goal[0], @goal[1]] ] current_x = @goal[0] current_y = @goal[1] while (current_x != nil && current_y != nil) tmpx, tmpy, dummy_cost = @cost[ [current_x, current_y] ] path << [tmpx, tmpy] current_x = tmpx current_y = tmpy end path.pop # [nil, nil]をなくす return path.reverse end # 結果を表示する def draw_with_shortest_path copy_map = @map.dup s_path = shortest_path_to_goal s_path[1..-2].each do |(x, y)| copy_map[x][y] = "$" end (0..height-1).each do |draw_y| (0..width-1).each do |draw_x| print copy_map[draw_x][draw_y] end print "\n" end end protected # コスト表を元に再帰的にダイクストラしていく def dijkstra_from(x, y) nodes = available_around_nodes(x, y) dummy_x, dummy_y, my_cost = @cost[ [x, y] ] nodes.each do |(nx, ny)| dummy_x, dummy_y, node_cost = @cost[ [nx, ny] ] node_cost = width*height if node_cost.nil? # 一度もコストが計算されて設定されてない if my_cost+1 < node_cost then @cost[ [nx, ny] ] = [x, y, my_cost+1] dijkstra_from(nx, ny) end end end # 座標(x,y)の上下左右のノードのうち, ちゃんと道になってるやつを返す def available_around_nodes(x, y) return [ [x, y-1], [x, y+1], [x-1, y], [x+1, y] ].select{|(tx, ty)| path?(tx, ty) } end end $MAP1 = <<EOD ********** *S* * * * ** * * * ** * * *** * G* ********** EOD $MAP2 = <<EOD ************************** *S* * * * * * * ************* * * * * ************ * * * * ************** *********** * * ** *********************** * * G * * * *********** * * * * ******* * * * * * ************************** EOD # --- main program --- #map = Map.parse($MAP1) map = Map.parse($MAP2) puts "map parsed: width=#{map.width}, height=#{map.height}" puts "start=(#{map.start[0]}, #{map.start[1]}), goal=(#{map.goal[0]}, #{map.goal[1]})" puts "running dijkstra!()" map.dijkstra! puts "dijkstra!() finished" p map.shortest_path_to_goal puts "--------------------------" map.draw_with_shortest_path