Newer Version of A* Search
Note: Also see a text explanation of A* search
and the pseudo code version of the algorithm.

;;; ASTAR2000.LSP - Updated version
;;; A* Search for a shortest path.

;;; (C) Copyright 1995 by Steven L. Tanimoto.
;;; This program is described in Chapter 5 ("Search") of
;;; "The Elements of Artificial Intelligence Using Common Lisp," 2nd ed.,
;;; published by W. H. Freeman, 41 Madison Ave., New York, NY 10010.
;;; Permission is granted for noncommercial use and modification of
;;; this program, provided that this copyright notice is retained
;;; and followed by a notice of any modifications made to the program.

;;; Here is the representation of the adjacency distance data,
;;; plus functions for getting at it and at solution path info.
;;; This code is identical to that in UNIFCOST.CL
(let ((distance-info (make-hash-table :size 20))
      (path-predecessor-info (make-hash-table :size 20)) )
  (defun set-distances (x y)
    (setf (gethash x distance-info) y) )
  (defun get-distances (x)
    (gethash x distance-info) )
  (defun set-predecessor (x y)
    (setf (gethash x path-predecessor-info) y) )
  (defun get-predecessor (x)
    (gethash x path-predecessor-info) )
  )

;;; Here are actual inter-city distances from the Michelin map:
(set-distances 'brest '((rennes . 244)))
(set-distances 'rennes '((caen . 176)(paris . 348)
		(brest . 244)(nantes . 107)))
(set-distances 'caen '((calais . 120)(paris . 241)(rennes . 176)))
(set-distances 'calais '((nancy . 534)(paris . 297)(caen . 120)))
(set-distances 'nancy '((strasbourg . 145)(dijon . 201)
		(paris . 372)(calais . 534)))
(set-distances 'strasbourg '((dijon . 335)(nancy . 145)))
(set-distances 'dijon '((strasbourg . 335)(lyon . 192)
		(paris . 313)(nancy . 201)))
(set-distances 'lyon '((grenoble . 104)(avignon . 216)
		(limoges . 389)(dijon . 192)))
(set-distances 'grenoble '((avignon . 227)(lyon . 104)))
(set-distances 'avignon '((grenoble . 227)(marseille . 99)
		(montpellier . 121)(lyon . 216)))
(set-distances 'marseille '((nice . 188)(avignon . 99)))
(set-distances 'nice '((marseille . 188)))
(set-distances 'montpellier '((avignon . 121)(toulouse . 240)))
(set-distances 'toulouse '((montpellier . 240)(bordeaux . 253)
		(limoges . 313)))
(set-distances 'bordeaux '((limoges . 220)(toulouse . 253)
		(nantes . 329)))
(set-distances 'limoges '((lyon . 389)(toulouse . 313)
		(bordeaux . 220)(nantes . 329)(paris . 396)))
(set-distances 'nantes '((limoges . 329)(bordeaux . 329)
		(rennes . 107)))
(set-distances 'paris '((calais . 297)(nancy . 372)(dijon . 313)
		(limoges . 396)(rennes . 348)(caen . 241)))

;;; And here is the hash table F-VALUES to
;;; remember the heuristic value at each node visited.
;;; We also need a hash table for G-VALUES.
(let ((f-values (make-hash-table :size 20))
      (g-values (make-hash-table :size 20)) )
  (defun set-f-value (x y)
    (setf (gethash x f-values) y) )
  (defun get-f-value (x)
    (gethash x f-values) )
  (defun set-g-value (x y)
    (setf (gethash x g-values) y) )
  (defun get-g-value (x)
    (gethash x g-values) )
  )

;;; Next is the information about longitude, which is the same
;;; as that used in BESTFS2.CL
(let ((longitude-info (make-hash-table :size 20)))
  (defun set-longitude (x y)
    (setf (gethash x longitude-info) y) )
  (defun get-longitude (x)
    (gethash x longitude-info) )
 )

;;; The longitude of each city is stored in tenths of a degree.
;;; We again use a local function with a LAMBDA form, since
;;; SET-LONGITUDE takes two arguments but we want a
;;; function that takes one argument for this use with MAPCAR.
(mapcar #'(lambda (pair) (apply #'set-longitude pair))
	'((avignon 48)(bordeaux -6)(brest -45)(caen -4)
	  (calais 18)(dijon 51)(grenoble 57)(limoges 12)
	  (lyon 48)(marseille 53)(montpellier 36)
	  (nantes -16)(nancy 62)(nice 73)(paris 23)
	  (rennes -17)(strasbourg 77)(toulouse 14) ) )

;;; Now we are ready for the algorithm itself.

;;; A-STAR-SEARCH is the main searching procedure.
(defun a-star-search (start-node goal-node)
  "Performs a search with the A* algorithm."
  (set-goal goal-node)
  (let ((open (list start-node))                ;step1
        (closed nil)
        x
        successors)
    (set-predecessor start-node nil)
    (set-g-value start-node 0)
    (set-f-value start-node (f start-node))
    (loop
      (if (null open)(return 'failure))         ;step2
      (setf x (select-best open))               ;step3
      (setf open (remove x open))               ;step4
      (push x closed)
      (if (eql x (get-goal))
          (return (extract-path x)) )           ;step5
      (setf successors (get-successors x))      ;step6
      (dolist (y successors)                    ;step7
        (if (not (or (member y open)
                     (member y closed) ))
          (progn
            (increment-count)
            (set-g-value y (g y x))
            (set-f-value y (f y))             
            (setf open (insert y open))    
            (set-predecessor y x)      ;; y is not in open or closed  
   ;; For Tracing:
   ;; Use a trace "format" statement here for
   ;; values of Y not on open or closed
   ;; Print the value of Y, X, Open, Closed, 
   ;; f(y)=g(y)+h(y) and f(x)=g(x)+y(x) - for g use get-g-value
   ;; Also print the successors of X.
         )  ;;end of progn
          (let* ((z (get-predecessor y))    ;; y is in open or closed
                (temp (if z
                        (+ (- (get-f-value y)
                              (get-g-value z)
                              (arc-dist z y) )
                           (get-g-value x)
                           (arc-dist x y) )
                        (get-f-value y) ) ) )
            (if (< temp (get-f-value y))
              (progn
   ;; For Tracing:
   ;; Use a trace "format" statement here for Y when Y is in open or
   ;; closed.  Print values for:
   ;; open, closed, Y, Z, X, temp
   ;; f(y)=g(y)+h(y), f(z)=g(z)+h(z), f(x)=g(x)+h(x)
   
   ;; Also use a format to print the values of:
   ;; distance to Y through Z (this is the current f of Y)
   ;; and the distance to Y through X (this is temp)

                (set-g-value y
                      (+ (- (get-g-value y)
                            (get-f-value y) )
                         temp) )
                (set-f-value y temp)
                (set-predecessor y x)
                (if (member y open)
                  (progn
                    (setf open (remove y open))
                    (setf open (insert y open)) ) )
                (if (member y closed)
                  (progn
                    (setf open (insert y open))
                    (setf closed
                          (remove y closed) ) 
                  ) 
                ) 
              ) ;;end of prog 
            ) ;; end of if
   ;; For Tracing:
   ;; At the end of the dolist loop (for Y's not in open/closed), print:
   ;; X, Y, f(y)=g(y)+h(y)  f(x)=g(x)+h(x)
   ;; and print the successors of X
         ) ;; end of let
       ) ;; end of dolist
      )  ;; end of loop

      ; end of loop -------- this is implicitly  step8
       ) ) )

;; The supporting functions:

;;; Use local variable to keep track of the goal.
(let (goal)
  (defun set-goal (the-goal) (setf goal the-goal))
  (defun get-goal () goal) )

;;; F is the sum of G and H.
(defun f (n)
  "Computes F value for node N."
  (+ (get-g-value n) (h n)) )

;;; G computes the distance from the start node to NODE
;;; by adding the distance from X to NODE to X's distance.
(defun g (node x)
  "Returns distance from START-NODE to NODE"
  (+ (get-g-value x) (arc-dist x node)) )

;;; H evaluates the difference in longitude between
;;; the current node N and the goal node.
(defun h (n)
  "Returns an estimate of the distance from N
   to the goal."
  (* 10 (longitude-diff n (get-goal))) )

;;; LONGITUDE-DIFF returns the absolute value of the
;;; difference in longitudes between nodes N1 and N2
;;; in tenths of a degree.
(defun longitude-diff (n1 n2)
  "Computes difference in longitudes."
  (abs (- (get-longitude n1) (get-longitude n2))) )

;;; SELECT-BEST chooses a node in step 3...
(defun select-best (lst)
  "Returns the best node on LST for expansion."
  (if (eql (first lst) (get-goal))
      (first lst)
    (better (first lst)(rest lst)) ) )

;;; The helping function BETTER for SELECT-BEST checks
;;; to see if there is a goal node on LST with FVALUE
;;; as low as that of ELT.  If so, it returns the goal node.
;;; If not, it returns ELT.
(defun better (elt lst)
  "Returns a goal-node on LST if it has an equal value,
   otherwise ELT."
  (cond ((null lst) elt)
        ((< (get-f-value elt)(get-f-value (first lst)))
         elt)
        ((eql (first lst) (get-goal))
         (first lst) )
        (t (better elt (rest lst))) ) )

;;; INSERT puts NODE onto LST, which is ordered
;;; by FVALUE.
(defun insert (node lst)
  "Inserts NODE onto LST, according to FVALUE ordering."
  (cond ((null lst)(list node))
        ((< (get-f-value node)
            (get-f-value (first lst)) )
         (cons node lst) )
        (t (cons (first lst)
                 (insert node (rest lst)) )) ) )

;;; EXTRACT-PATH returns the sequence of cities found.
(defun extract-path (n)
  "Returns the path from START-NODE to N."
  (cond ((null n) nil)
        (t (append (extract-path (get-predecessor n))
                   (list n) )) ) )

;;; GET-SUCCESSORS retrieves the list of cities adjacent
;;; to N from the hash table.
(defun get-successors (n)
  "Returns a list of cities adjacent to N."
  (mapcar #'first (get-distances n)) )

;;; Let BIG-DISTANCE represent an impossibly large distance
;;; for this problem:
(defconstant big-distance 9999)

;;; ARC-DIST retrieves the distance between N1 and N2.
(defun arc-dist (n1 n2) 
  "Returns the distance along arc N1 N2. If no such arc
   exists, returns BIG-DISTANCE."
  (or (rest (assoc n1 (get-distances n2))) big-distance) )

;;; Use a local variable EXPANSION-COUNT for counting the
;;; number of nodes expanded by the algorithm.
(let (expansion-count)
  (defun initialize-count () (setf expansion-count 0))
  (defun increment-count () (incf expansion-count))
  (defun get-count () expansion-count) )

;;; TEST sets EXPANSION-COUNT to 0 and
;;; begins a search from RENNES to AVIGNON.
(defun test ()
  "Runs a test of ASTAR."
  (initialize-count)
  (format t "A-star-search solution: ~s.~%"
    (a-star-search 'rennes 'avignon) )
  (format t "Path-length: ~s.~%" 
    (get-f-value 'avignon) )
  (format t "~s nodes expanded.~%"
    (get-count) )
  )

;; Uncomment the following line if you want the test function
;; to run automatically when the program is loaded:

;; (test)