;;; Here we combine the adjacency data and the distance data
;;; and represent it in one hash table DISTANCE-INFO.
(let ((distance-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) )
  )

;;; The second hash table, PATH-PREDECESSOR-INFO, is used
;;; as in DEPTHFS.CL, etc.
(let ((path-predecessor-info (make-hash-table :size 20)) )
  (defun set-predecessor (x y)
    (setf (gethash x path-predecessor-info) y) )
  (defun get-predecessor (x)
    (gethash x path-predecessor-info) )
  )

(set-distances 'paris '((calais . 297)(nancy . 372)(dijon . 313)
		(limoges . 396)(rennes . 348)(caen . 241)))

;;; And here is the hash table F-VALUE to
;;; remember the heuristic value at each node visited.
(let ((f-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) )
  )

;;; UNIFORM-COST is the main searching procedure.
(defun uniform-cost (start-node goal-node)
  "Performs a uniform-cost search from START-NODE
   to GOAL-NODE."
  (set-goal goal-node)
  (let ((open (list start-node))                ;step1
        (closed nil)
        n l)
    (set-predecessor start-node nil)
    (set-f-value start-node 0)
    (loop
      (if (null open)(return 'failure))         ;step2
      (setf n (select-best open))               ;step3
      (setf open (remove n open))               ;step4
      (push n closed)
      (if (eql n goal-node)                     ;step5
          (return (extract-path n)) )
      (setf l (get-successors n))               ;step6
      (dolist (m (list-difference l closed))
        (let ((temp (f m n)))
          (if (member m open)
            (if (< temp (get-f-value m))
              (progn
                (set-f-value m temp)
                (set-predecessor m n)
                (setf open (insert m (remove m open)))
                (increment-count) ) )
            (progn
              (set-f-value m temp)
              (set-predecessor m n)
              (setf open (insert m open))
              (increment-count) ) ) ) )
      ; end of loop -------- this is implicitly  step7
       ) ) )

;; 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) )

;;; EXTRACT-PATH returns the sequence of cities found.
(defun extract-path (n)
  "Returns the list of nodes from the Start node to N."
  (if (null n) nil
    (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)) )

;;; LIST-DIFFERENCE is like the built-in Lisp function
;;; named SET-DIFFERENCE but it preserves the ordering in LST1"
(defun list-difference (lst1 lst2)
  "Returns a list of those elements of LST1 that do not
   occur on LST2."
  (dolist (elt lst2 lst1)
    (setf lst1 (remove elt lst1)) ) )

;;; 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 9999."
  (or (rest (assoc n1 (get-distances n2))) 9999) )

;;; F computes the distance from the start node to NODE
;;; by adding the distance from N to NODE to N's distance.
(defun f (node n)
  "Returns the F value for NODE using N."
  (+ (get-f-value n) (arc-dist n node)) )

;;; 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 returns ELT."
  (cond ((null lst) elt) ; no more possibilities, return ELT.
        ((< (get-f-value elt)
            (get-f-value (first lst)) )
         elt) ; ELT is better.
        ((eql (first lst) (get-goal))
         (first lst) ) ;return a goal node.
        (t (better elt (rest lst))) ; same value and not
                                    ; goal -try next on LST.
    ) )

;;; INSERT puts NODE onto LST, which is ordered
;;; by FVALUE property.
(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)))) ) )

;;; 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 UNIFORM-COST."
  (initialize-count)
  (format t "Uniform-cost-search solution: ~s.~%"
    (uniform-cost 'rennes 'avignon) )
  (format t "~s nodes expanded.~%"
    (get-count) )
  )

(test)
