A* search algorithm: Difference between revisions
Content added Content deleted
Thundergnat (talk | contribs) (alphabetize, minor clean-up) |
(My solution in Common Lisp) |
||
Line 493: | Line 493: | ||
Path cost 11: (0, 0) (0, 1) (0, 2) (0, 3) (0, 4) (1, 5) (2, 6) (3, 6) (4, 6) (5, 6) (6, 7) (7, 7) |
Path cost 11: (0, 0) (0, 1) (0, 2) (0, 3) (0, 4) (1, 5) (2, 6) (3, 6) (4, 6) (5, 6) (6, 7) (7, 7) |
||
</pre> |
|||
=={{header|Common Lisp}}== |
|||
<lang lisp>;; * Using external libraries with quicklisp |
|||
(eval-when (:load-toplevel :compile-toplevel :execute) |
|||
(ql:quickload '("pileup" "iterate"))) |
|||
;; * The package definition |
|||
(defpackage :a*-search |
|||
(:use :common-lisp :pileup :iterate)) |
|||
(in-package :a*-search) |
|||
;; * The data |
|||
(defvar *size* 8 |
|||
"The size of the area.") |
|||
;; I will use simple conses for the positions and directions. |
|||
(defvar *barriers* |
|||
'((2 . 4) (2 . 5) (2 . 6) (3 . 6) (4 . 6) (5 . 6) (5 . 5) (5 . 4) (5 . 3) (5 . 2) |
|||
(4 . 2) (3 . 2)) |
|||
"The position of the barriers in (X Y) pairs, starting with (0 0) at the lower |
|||
left corner.") |
|||
(defvar *barrier-cost* 100 "The costs of a barrier field.") |
|||
(defvar *directions* '((0 . -1) (0 . 1) (1 . 0) (-1 . 0) (-1 . -1) (1 . 1)) |
|||
"The possible directions left, right, up, down and diagonally.") |
|||
;; * Tha data structure for the node in the search graph |
|||
(defstruct (node (:constructor node)) |
|||
(pos (cons 0 0) :type cons) |
|||
(path nil) |
|||
(cost 0 :type fixnum) ; The costs so far |
|||
(f-value 0 :type fixnum) ; The value for the heuristics |
|||
) |
|||
;; * The functions |
|||
;; ** Printing the final path |
|||
(defun print-path (path start end &optional (barriers *barriers*) |
|||
&aux (size (+ 2 *size*))) |
|||
"Prints the area with the BARRIERS." |
|||
;; The upper boarder |
|||
(format t "~v@{~A~:*~}~%" size "█") |
|||
;; The actual area |
|||
;; The lines |
|||
(iter (for y from (1- *size*) downto 0) |
|||
(format t "█") |
|||
;; The columns |
|||
(iter (for x from 0 below *size*) |
|||
(format t "~A" |
|||
(cond ((member (cons y x) barriers :test #'equal) "█") |
|||
((equal (cons y x) start) "●") |
|||
((equal (cons y x) end) "◆") |
|||
((Member (cons y x) path :test #'equal) "▪") |
|||
(t " ")))) |
|||
;; The last column and jump to the next line |
|||
(format t "█~%")) |
|||
;; The lower boarder |
|||
(format t "~v@{~A~:*~}~%" size "█") |
|||
(iter |
|||
(for position in path) |
|||
(format t "(~D,~D)" (car position) (cdr position)) |
|||
(finally (terpri)))) |
|||
;; ** Generating the next positions |
|||
;; *** Check if a position is possible |
|||
(defun valid-position-p (position) |
|||
"Returns T if POSITION is a valid position." |
|||
(let ((x (car position)) |
|||
(y (cdr position)) |
|||
(max (1- *size*))) |
|||
(and (<= 0 x max) |
|||
(<= 0 y max)))) |
|||
;; *** Move from the current position in direction |
|||
(defun move (position direction) |
|||
"Returns a new position after moving from POSITION in DIRECTION assuming only |
|||
valid positions." |
|||
(let ((x (car position)) |
|||
(y (cdr position)) |
|||
(dx (car direction)) |
|||
(dy (cdr direction))) |
|||
(cons (+ x dx) (+ y dy)))) |
|||
;; *** Generate the possible next positions |
|||
(defun next-positions (current-position) |
|||
"Returns a list of conses with possible next positions." |
|||
(remove-if-not #'valid-position-p |
|||
(mapcar (lambda (d) (move current-position d)) *directions*))) |
|||
;; ** The heuristics |
|||
(defun distance (current-position goal) |
|||
"Returns the Manhattan distance from CURRENT-POSITION to GOAL." |
|||
(+ (abs (- (car goal) (car current-position))) |
|||
(abs (- (cdr goal) (cdr current-position))))) |
|||
;; ** The A+ search |
|||
(defun a* (start goal heuristics next &optional (information 0)) |
|||
"Returns the shortest path from START to GOAL using HEURISTICS, generating the |
|||
next nodes using NEXT." |
|||
(let ((visited (make-hash-table :test #'equalp))) |
|||
(flet ((pick-next-node (queue) |
|||
;; Get the next node from the queue |
|||
(heap-pop queue)) |
|||
(expand-node (node queue) |
|||
;; Expand the next possible nodes from node and add them to the |
|||
;; queue if not already visited. |
|||
(iter |
|||
(with costs = (node-cost node)) |
|||
(for position in (funcall next (node-pos node))) |
|||
(for cost = (1+ costs)) |
|||
(for f-value = (+ cost (funcall heuristics position goal) |
|||
(if (member position *barriers* :test #'equal) |
|||
100 |
|||
0))) |
|||
;; Check if this state was already looked at |
|||
(unless (gethash position visited)) |
|||
;; Insert the next node into the queue |
|||
(heap-insert |
|||
(node :pos position :path (cons position (node-path node)) |
|||
:cost cost :f-value f-value) |
|||
queue)))) |
|||
;; The actual A* search |
|||
(iter |
|||
;; The priority queue |
|||
(with queue = (make-heap #'<= :name "queue" :size 1000 :key #'node-f-value)) |
|||
(with initial-cost = (funcall heuristics start goal)) |
|||
(initially (heap-insert (node :pos start :path (list start) :cost 0 |
|||
:f-value initial-cost) |
|||
queue)) |
|||
(for counter from 1) |
|||
(for current-node = (pick-next-node queue)) |
|||
(for current-position = (node-pos current-node)) |
|||
;; Output some information each counter or nothing if information |
|||
;; equals 0. |
|||
(when (and (not (zerop information)) |
|||
(zerop (mod counter information))) |
|||
(format t "~Dth Node, heap size: ~D, current costs: ~D~%" |
|||
counter (heap-count queue) |
|||
(node-cost current-node))) |
|||
;; If the target is not reached continue |
|||
(until (equalp current-position goal)) |
|||
;; Add the current state to the hash of visited states |
|||
(setf (gethash current-node visited) t) |
|||
;; Expand the current node and continue |
|||
(expand-node current-node queue) |
|||
(finally (return (values (nreverse (node-path current-node)) |
|||
(node-cost current-node) |
|||
counter))))))) |
|||
;; ** The main function |
|||
(defun search-path (&key (start '(0 . 0)) (goal '(7 . 7)) (heuristics #'distance)) |
|||
"Searches the shortest path from START to GOAL using HEURISTICS." |
|||
(multiple-value-bind (path cost steps) |
|||
(a* start goal heuristics #'next-positions 0) |
|||
(format t "Found the shortest path from Start (●) to Goal (◆) in ~D steps with cost: ~D~%" steps cost) |
|||
(print-path path start goal)))</lang> |
|||
{{out}} |
|||
<pre>A*-SEARCH> (search-path) |
|||
Found the shortest path in 323 steps with cost: 11 |
|||
██████████ |
|||
█ ▪▪▪▪◆█ |
|||
█ ▪ █ |
|||
█ ▪█████ █ |
|||
█ ▪█ █ █ |
|||
█ ▪█ █ █ |
|||
█ ▪ ███ █ |
|||
█ ▪ █ |
|||
█● █ |
|||
██████████ |
|||
(0,0)(1,1)(2,1)(3,1)(4,1)(5,1)(6,2)(7,3)(7,4)(7,5)(7,6)(7,7) |
|||
</pre> |
</pre> |
||