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

A*探索アルゴリズム

ocaml

最近、A*という単語を目にすることがあったので、ちょっとWikipediaで調べてみたところ、その正体は経路探索のアルゴリズムでした。

経路探索なんてチャレンジしたことも無いので、これも何かの縁と思い、頑張って書いてみることにしました。googleってみたのですが、何だか初心者向けの情報が少ないなぁ…ということで結局、A* - Wikipediaを参考に。

type field = O | X
let maze = [|
  [|O;O;O;O;O;O;O;O;O;O;O;O;O;O;O;O;O;X;O;O;O|];
  [|O;O;O;X;X;O;O;X;X;X;X;O;O;O;O;O;O;X;O;O;O|];
  [|O;X;O;O;X;O;O;X;O;O;X;O;X;X;X;X;X;X;O;O;O|];
  [|O;X;X;X;X;O;O;O;O;O;X;O;O;O;O;O;O;O;O;O;O|];
  [|O;O;O;O;O;O;O;O;O;O;X;O;O;O;O;O;X;X;X;X;O|];
  [|O;O;O;X;X;X;O;X;O;O;X;O;O;O;O;O;O;O;O;X;O|];
  [|O;O;O;O;O;X;O;X;X;X;X;O;O;O;O;O;O;O;O;X;O|];
  [|O;X;O;O;O;X;O;O;O;O;O;O;O;O;O;X;X;X;X;X;O|];
  [|O;X;X;X;X;X;O;O;O;X;X;X;X;X;O;O;O;O;O;O;O|];
  [|O;O;O;O;O;O;O;O;O;O;O;O;O;X;O;O;X;X;X;X;X|];
  [|O;O;O;O;O;O;O;O;O;O;O;O;O;X;O;O;O;O;O;O;O|];
  |]

type pos  = {x: int; y: int}
type node = {pos: pos; cost: float; parent: pos}

let max_x = Array.length maze.(0) - 1
let max_y = Array.length maze     - 1
let start = {x = 0; y = 0}
let goal  = {x = max_x; y = max_y}

(* for debug *)
let strpos pos = Printf.sprintf "pos_x[%02d] pos_y[%02d]" pos.x pos.y
let plist list =
  List.iter (fun a -> Printf.printf "<%s cost[%f] parent[%s]>\n"
                        (strpos a.pos) a.cost (strpos a.parent)) list

let distance src dst =
  let sx = float_of_int src.x in
  let sy = float_of_int src.y in
  let dx = float_of_int dst.x in
  let dy = float_of_int dst.y in
  sqrt((dx -. sx) ** 2.0 +. (dy -. sy) ** 2.0)

let cost pos =
  (distance start pos) +. (distance pos goal) +. 1.0

let valid_pos pos =
  pos.y >= 0 && pos.x >= 0 && pos.y <= max_y && pos.x <= max_x &&
  maze.(pos.y).(pos.x) = O

let remove_list list pos =
  List.filter (fun a -> a.pos <> pos) list

let sort_list list =
  List.sort (fun a b -> if a.cost > b.cost then 1 else -1) list

let find_list list pos =
  try Some (List.find (fun a -> pos = a.pos) list)
  with Not_found -> None

let search o c pos parent =
  if valid_pos pos
  then (
    let cost = cost pos in
    match find_list o pos with
    | Some n when cost < n.cost -> (n::(remove_list o pos), c)
    | Some n -> (o, c)
    | None -> (
      match find_list c pos with
      | Some n when cost < n.cost -> (n::o, remove_list c pos)
      | Some n -> (o, c)
      | None ->
          let node = {pos = pos; cost = cost; parent = parent} in
          (node::o, c)
    )
  )
  else (o, c)

let rec engine o c =
  let o = sort_list o in
  match o with
  | hd::tl when hd.pos = goal ->
      print_endline "GOOOOAL!"; (o, c)
  | hd::tl ->
      let pos = hd.pos in
      Printf.printf "pos => %s\n" (strpos pos); flush stdout;
      let (o, c) = (tl, hd::c) in
      let (o, c) = search o c {x = pos.x; y = pos.y + 1} pos in
      let (o, c) = search o c {x = pos.x; y = pos.y - 1} pos in
      let (o, c) = search o c {x = pos.x + 1; y = pos.y} pos in
      let (o, c) = search o c {x = pos.x - 1; y = pos.y} pos in
      engine o c
  | _ -> failwith "no route"

let print_route c =
  let rec loop node rt = function
    | [] -> node::rt
    | hd::tl when hd.pos = node.parent -> loop hd (node::rt) tl
    | hd::tl -> loop node rt tl in
  match c with
  | [] -> failwith "empty route"
  | hd::tl ->
      let rt = loop hd [] tl in
      plist rt; flush stdout

let main () =
  let (opn, cls) =
    engine [{pos = start; cost = (cost start); parent = start}] [] in
  print_route cls

let _ = main ()

こんな感じにしておいて実行させると

mitsu@garlic$ ocaml astar.ml
pos => pos_x[00] pos_y[00]
pos => pos_x[01] pos_y[00]
pos => pos_x[01] pos_y[01]
pos => pos_x[02] pos_y[01]
pos => pos_x[02] pos_y[02]
pos => pos_x[03] pos_y[02]
pos => pos_x[02] pos_y[00]
pos => pos_x[03] pos_y[00]
pos => pos_x[04] pos_y[00]
pos => pos_x[00] pos_y[01]
pos => pos_x[05] pos_y[00]
pos => pos_x[05] pos_y[01]
pos => pos_x[05] pos_y[02]
pos => pos_x[05] pos_y[03]
pos => pos_x[06] pos_y[03]
pos => pos_x[07] pos_y[03]
pos => pos_x[07] pos_y[04]
pos => pos_x[08] pos_y[04]
pos => pos_x[09] pos_y[04]
pos => pos_x[09] pos_y[05]
pos => pos_x[08] pos_y[05]
pos => pos_x[08] pos_y[03]
pos => pos_x[06] pos_y[04]
pos => pos_x[06] pos_y[02]
pos => pos_x[09] pos_y[03]
pos => pos_x[05] pos_y[04]
pos => pos_x[08] pos_y[02]
pos => pos_x[06] pos_y[05]
pos => pos_x[06] pos_y[01]
pos => pos_x[04] pos_y[04]
pos => pos_x[09] pos_y[02]
pos => pos_x[03] pos_y[04]
pos => pos_x[06] pos_y[06]
pos => pos_x[06] pos_y[00]
pos => pos_x[07] pos_y[00]
pos => pos_x[02] pos_y[04]
pos => pos_x[06] pos_y[07]
pos => pos_x[07] pos_y[07]
pos => pos_x[08] pos_y[07]
pos => pos_x[09] pos_y[07]
pos => pos_x[10] pos_y[07]
pos => pos_x[11] pos_y[07]
pos => pos_x[11] pos_y[06]
pos => pos_x[12] pos_y[06]
pos => pos_x[11] pos_y[05]
pos => pos_x[13] pos_y[06]
pos => pos_x[13] pos_y[07]
pos => pos_x[14] pos_y[07]
pos => pos_x[12] pos_y[05]
pos => pos_x[12] pos_y[07]
pos => pos_x[14] pos_y[06]
pos => pos_x[14] pos_y[08]
pos => pos_x[15] pos_y[08]
pos => pos_x[16] pos_y[08]
pos => pos_x[17] pos_y[08]
pos => pos_x[11] pos_y[04]
pos => pos_x[18] pos_y[08]
pos => pos_x[13] pos_y[05]
pos => pos_x[15] pos_y[06]
pos => pos_x[15] pos_y[09]
pos => pos_x[12] pos_y[04]
pos => pos_x[14] pos_y[05]
pos => pos_x[14] pos_y[09]
pos => pos_x[16] pos_y[06]
pos => pos_x[11] pos_y[03]
pos => pos_x[13] pos_y[04]
pos => pos_x[19] pos_y[08]
pos => pos_x[15] pos_y[05]
pos => pos_x[12] pos_y[03]
pos => pos_x[17] pos_y[06]
pos => pos_x[15] pos_y[10]
pos => pos_x[16] pos_y[10]
pos => pos_x[17] pos_y[10]
pos => pos_x[18] pos_y[10]
pos => pos_x[19] pos_y[10]
GOOOOAL!
<pos_x[00] pos_y[00] cost[23.360680] parent[pos_x[00] pos_y[00]]>
<pos_x[01] pos_y[00] cost[23.470911] parent[pos_x[00] pos_y[00]]>
<pos_x[02] pos_y[00] cost[23.591260] parent[pos_x[01] pos_y[00]]>
<pos_x[03] pos_y[00] cost[23.723083] parent[pos_x[02] pos_y[00]]>
<pos_x[04] pos_y[00] cost[23.867962] parent[pos_x[03] pos_y[00]]>
<pos_x[05] pos_y[00] cost[24.027756] parent[pos_x[04] pos_y[00]]>
<pos_x[05] pos_y[01] cost[23.591875] parent[pos_x[05] pos_y[00]]>
<pos_x[05] pos_y[02] cost[23.385165] parent[pos_x[05] pos_y[01]]>
<pos_x[05] pos_y[03] cost[23.383897] parent[pos_x[05] pos_y[02]]>
<pos_x[06] pos_y[03] cost[23.360680] parent[pos_x[05] pos_y[03]]>
<pos_x[06] pos_y[04] cost[23.442649] parent[pos_x[06] pos_y[03]]>
<pos_x[06] pos_y[05] cost[23.676318] parent[pos_x[06] pos_y[04]]>
<pos_x[06] pos_y[06] cost[24.045501] parent[pos_x[06] pos_y[05]]>
<pos_x[06] pos_y[07] cost[24.537366] parent[pos_x[06] pos_y[06]]>
<pos_x[07] pos_y[07] cost[24.241159] parent[pos_x[06] pos_y[07]]>
<pos_x[08] pos_y[07] cost[23.999463] parent[pos_x[07] pos_y[07]]>
<pos_x[09] pos_y[07] cost[23.803509] parent[pos_x[08] pos_y[07]]>
<pos_x[10] pos_y[07] cost[23.646862] parent[pos_x[09] pos_y[07]]>
<pos_x[11] pos_y[07] cost[23.525238] parent[pos_x[10] pos_y[07]]>
<pos_x[11] pos_y[06] cost[23.378822] parent[pos_x[11] pos_y[07]]>
<pos_x[12] pos_y[06] cost[23.360680] parent[pos_x[11] pos_y[06]]>
<pos_x[13] pos_y[06] cost[23.380079] parent[pos_x[12] pos_y[06]]>
<pos_x[13] pos_y[07] cost[23.380596] parent[pos_x[13] pos_y[06]]>
<pos_x[14] pos_y[07] cost[23.360680] parent[pos_x[13] pos_y[07]]>
<pos_x[14] pos_y[08] cost[23.449071] parent[pos_x[14] pos_y[07]]>
<pos_x[15] pos_y[08] cost[23.385165] parent[pos_x[14] pos_y[08]]>
<pos_x[15] pos_y[09] cost[23.591875] parent[pos_x[15] pos_y[08]]>
<pos_x[15] pos_y[10] cost[24.027756] parent[pos_x[15] pos_y[09]]>
<pos_x[16] pos_y[10] cost[23.867962] parent[pos_x[15] pos_y[10]]>
<pos_x[17] pos_y[10] cost[23.723083] parent[pos_x[16] pos_y[10]]>
<pos_x[18] pos_y[10] cost[23.591260] parent[pos_x[17] pos_y[10]]>
<pos_x[19] pos_y[10] cost[23.470911] parent[pos_x[18] pos_y[10]]>

あんまり、しっかり確認してないのだけど、最短ルートの表示を見る限り、それらしく動いているみたい。と思ったら、ちょい回り道してた… orz

実のところ最初は、「計算中のノードを格納しておくためのリスト(Openリスト)と、計算済みのノードを格納しておくリスト(Closeリスト)」は参照リストを置いておいて、代入しまくりのコードを書いていて動作確認まで終わったのだけど、どうにもこうにもコードが汚くなってしまったので、泣きながら参照しないように修正したのでした。

そしたら、ずっと分かりやすくなって、これからは参照とかしないよ絶対と思いました。