Как сократить время выполнения головоломки A* с восьмеркой

Я пытаюсь реализовать стратегию эвристического поиска A * для головоломки «8-головоломка» в Лиспе.

Чтобы запустить поиск, я использую команду: (run-best '(0 1 2 3 4 5 6 B 7) '(0 1 2 3 4 5 6 7 B))

Где первое состояние является начальной целью, а второе — конечной целью.

Тем не менее, я заканчиваю тем, что моя программа работает в течение длительного времени. В конце концов, я предполагаю, что произойдет переполнение стека. * Редактировать: памяти не хватает, однако это заняло 30 минут, намного дольше, чем мой поиск в ширину.

Код алгоритма поиска:

;;; This is one of the example programs from the textbook:
;;;
;;; Artificial Intelligence: 
;;; Structures and strategies for complex problem solving
;;;
;;; by George F. Luger and William A. Stubblefield
;;;
;;; Corrections by Christopher E. Davis ([email protected])
;;; insert-by-weight will add new child states to an ordered list of 
;;; states-to-try.  
(defun insert-by-weight (children sorted-list)
  (cond ((null children) sorted-list)
        (t (insert (car children) 
           (insert-by-weight (cdr children) sorted-list)))))

(defun insert (item sorted-list)
  (cond ((null sorted-list) (list item))
        ((< (get-weight item) (get-weight (car sorted-list)))
         (cons item sorted-list))
        (t (cons (car sorted-list) (insert item (cdr sorted-list))))))


;;; run-best is a simple top-level "calling" function to run best-first-search

(defun run-best (start goal)
  (declare (special *goal*)
           (special *open*)
           (special *closed*))
  (setq *goal* goal)
  (setq *open* (list (build-record start nil 0 (heuristic start))))
  (setq *closed* nil)
  (best-first))

;;; These functions handle the creation and access of (state parent) 
;;; pairs.

(defun build-record (state parent depth weight) 
  (list state parent depth weight))

(defun get-state (state-tuple) (nth 0 state-tuple))

(defun get-parent (state-tuple) (nth 1 state-tuple))

(defun get-depth (state-tuple) (nth 2 state-tuple))

(defun get-weight (state-tuple) (nth 3 state-tuple))

(defun retrieve-by-state (state list)
  (cond ((null list) nil)
        ((equal state (get-state (car list))) (car list))
        (t (retrieve-by-state state (cdr list)))))


;; best-first defines the actual best-first search algorithm
;;; it uses "global" open and closed lists.

(defun best-first ()
  (declare (special *goal*)
           (special *open*)
           (special *closed*)
           (special *moves*))
  (print "open =") (print *open*)
  (print "closed =") (print *closed*)
  (cond ((null *open*) nil)
        (t (let ((state (car *open*)))
             (setq *closed* (cons state *closed*))
             (cond ((equal (get-state state) *goal*) (reverse (build-solution *goal*)))
                   (t (setq *open* 
                            (insert-by-weight 
                                    (generate-descendants (get-state state)
                                                          (1+ (get-depth state))
                                                          *moves*)
                                    (cdr *open*)))
                      (best-first)))))))


;;; generate-descendants produces all the descendants of a state

(defun generate-descendants (state depth moves)
  (declare (special *closed*)
           (special *open*))
  (cond ((null moves) nil)
        (t (let ((child (funcall (car moves) state))
                 (rest (generate-descendants state depth (cdr moves))))
             (cond ((null child) rest)
                   ((retrieve-by-state child rest) rest)
                   ((retrieve-by-state child *open*) rest)
                   ((retrieve-by-state child *closed*) rest)
                   (t (cons (build-record child state depth 
                                          (+ depth (heuristic child))) 
                            rest)))))))


(defun build-solution (state)
  (declare (special *closed*))
  (cond ((null state) nil)
        (t (cons state (build-solution 
                        (get-parent 
                         (retrieve-by-state state *closed*)))))))

Эвристическая функция для 8puzzle:

(defun hole (grid)
  "Return integer index into GRID at which the 'hole' is located."
  (position '0 grid))

(defun col (pair)
  (car pair))

(defun row (pair)
  (cdr pair))

(defun coords (index1)
  "Transform INDEX, an integer index into the list, into an (X . Y)
coordinate pair for a 3x3 grid."
  (cons (second (multiple-value-list (floor index1 3)))
    (floor index1 3)))

(defun index1 (coords)
  "Transform COORDS, an (X . Y) coordinate pair for a 3x3 grid, into
an integer index."
  (+ (col coords)
     (* 3 (row coords))))

(defun swap (a b list)
  "Return a new list equivalent to LIST but with the items at indexes
A and B swapped."
  (let ((new (copy-seq list)))
    (setf (nth a new)
      (nth b list))
    (setf (nth b new)
      (nth a list))
    new))

(defun right1 (grid)
  "Move the 'hole' on the 3x3 GRID one space to the right.  If there
is no space to the right, return NIL."
  (let ((hole (coords (hole grid))))
    (if (= 2 (col hole))
    nil
    (swap (index1 hole)
          (index1 (cons (1+ (col hole)) (row hole)))
          grid))))

(defun left1 (grid)
  "Move the 'hole' on the 3x3 GRID one space to the left.  If there
is no space to the left, return NIL."
  (let ((hole (coords (hole grid))))
    (if (zerop (col hole))
    nil
    (swap (index1 hole)
          (index1 (cons (1- (col hole)) (row hole)))
          grid))))

(defun up (grid)
  "Move the 'hole' on the 3x3 GRID one space up.  If there is no space
up, return NIL."
  (let ((hole (coords (hole grid))))
    (if (zerop (row hole))
    nil
    (swap (index1 (cons (col hole) (1- (row hole))))
          (index1 hole)
          grid))))

(defun down (grid)
  "Move the 'hole' on the 3x3 GRID one space down.  If there is no
space down, return NIL."
  (let ((hole (coords (hole grid))))
    (if (= 2 (row hole))
    nil
    (swap (index1 (cons (col hole) (1+ (row hole))))
          (index1 hole)
          grid))))

;Moves
(setq *moves*
  '(right1 left1 up down))

;heuristics for puzzle8
 (defun heuristic (state)
  (declare (special *goal*))
  (heuristic-eval state *goal*))

 (defun heuristic-eval (state goal)
  (cond ((null state) 0)
        ((equal (car state) (car goal)) 
        (heuristic-eval (cdr state) (cdr goal)))
        (t (1+ (heuristic-eval (cdr state) (cdr goal))))))

person Asia x3    schedule 19.10.2015    source источник
comment
Мой поиск фактически завершен! (Через 30 минут ..) Есть идеи, как это исправить?   -  person Asia x3    schedule 19.10.2015
comment
Примечание: используйте defstruct или defclass вместо списков для состояния.   -  person coredump    schedule 19.10.2015
comment
Я бы удалил различные рекурсии и вместо этого использовал обычную итерацию.   -  person Rainer Joswig    schedule 19.10.2015
comment
также см. ОБЪЕДИНЕНИЕ и НАЙТИ...   -  person Rainer Joswig    schedule 19.10.2015
comment
@coredump Я исследовал defstruct и defclass, и я до сих пор не понимаю, что мне нужно делать .. Не могли бы вы объяснить немного больше? Извини :(   -  person Asia x3    schedule 19.10.2015
comment
@ Asiax3 Благодаря коду, предоставленному Райнером Джосвигом, средства доступа уже созданы для вас. Вам не нужно писать все функции get-*. Кроме того, вы можете создавать новые состояния с помощью (make-srecord :state s ...). И функции setf, такие как (setf (srecord-parent state) p), также определены, хотя вы, похоже, не мутируете слоты. Это упростит ваш код. Но хорошая сторона вашего кода заключалась в том, что вы предоставили функциональный интерфейс, который лучше, чем писать (nth ...) везде, где вы используете определенный слот. Не все это делают.   -  person coredump    schedule 19.10.2015
comment
Вам не нужно вручную переводить координаты. Lisp напрямую поддерживает многомерные массивы.   -  person Svante    schedule 19.10.2015
comment
@RainerJoswig Спасибо coredump и Rainer за помощь! Я уже некоторое время пытаюсь решить эту проблему, и мне очень трудно понять, как использовать ваш код Rainer Joswig для исправления моей программы. Извините за все неприятности :(   -  person Asia x3    schedule 19.10.2015
comment
О да, это код, над которым я работаю!   -  person Asia x3    schedule 19.10.2015
comment
@ Asiax3 Вы действительно должны были упомянуть об этом. Вы не должны размещать код, который вы не писали, но позволять ему выглядеть как ваш собственный. Могут быть проблемы с авторскими правами и лицензиями. Например, получили ли вы разрешение от оригинального автора, прежде чем размещать код здесь?   -  person Joshua Taylor    schedule 19.10.2015
comment
@JoshuaTaylor Прошу прощения, у меня есть код, указанный в моем редакторе. Сообщение отредактировано :)   -  person Asia x3    schedule 19.10.2015


Ответы (2)


Проблемы в коде:

  • рекурсия. писать циклы, чтобы избежать переполнения стека

  • возможно длинные открытые и закрытые списки. Открытый и закрытый списки могут быть довольно длинными. Одна операция — проверить, есть ли в списках запись с определенным состоянием. Я бы использовал хеш-таблицу для записи состояний, а затем использовал таблицу для проверки существования состояния.

Моя версия кода

Нет решения:

CL-USER 220 > (time (run-best '(0 1 2 3 4 5 6 7 8)
                              '(0 2 1 3 4 5 6 7 8)
                              '(right1 left1 up down)))
Timing the evaluation of (RUN-BEST (QUOTE (0 1 2 3 4 5 6 7 8))
                                   (QUOTE (0 2 1 3 4 5 6 7 8))
                                   (QUOTE (RIGHT1 LEFT1 UP DOWN)))

User time    =  0:01:05.620
System time  =        0.220
Elapsed time =  0:01:05.749
Allocation   = 115386560 bytes
22397 Page faults
NO-SOLUTION

Решение:

CL-USER 223 > (time (pprint (run-best '(2 1 5 3 4 6 0 8 7)
                                      '(0 1 2 3 4 5 6 7 8)
                                      '(right1 left1 up down))))
Timing the evaluation of (PPRINT (RUN-BEST (QUOTE (2 1 5 3 4 6 0 8 7))
                                           (QUOTE (0 1 2 3 4 5 6 7 8))
                                           (QUOTE (RIGHT1 LEFT1 UP DOWN))))

((2 1 5 3 4 6 0 8 7)
 (2 1 5 0 4 6 3 8 7)
 (2 1 5 4 0 6 3 8 7)
 (2 0 5 4 1 6 3 8 7)
 (0 2 5 4 1 6 3 8 7)
 (4 2 5 0 1 6 3 8 7)
 (4 2 5 1 0 6 3 8 7)
 (4 2 5 1 6 0 3 8 7)
 (4 2 5 1 6 7 3 8 0)
 (4 2 5 1 6 7 3 0 8)
 (4 2 5 1 0 7 3 6 8)
 (4 2 5 1 7 0 3 6 8)
 (4 2 0 1 7 5 3 6 8)
 (4 0 2 1 7 5 3 6 8)
 (0 4 2 1 7 5 3 6 8)
 (1 4 2 0 7 5 3 6 8)
 (1 4 2 3 7 5 0 6 8)
 (1 4 2 3 7 5 6 0 8)
 (1 4 2 3 0 5 6 7 8)
 (1 0 2 3 4 5 6 7 8)
 (0 1 2 3 4 5 6 7 8))
User time    =        0.115
System time  =        0.001
Elapsed time =        0.103
Allocation   = 2439744 bytes
194 Page faults
person Rainer Joswig    schedule 19.10.2015
comment
Вы не поверите... Я использовал неправильные стартовые и конечные состояния...! Я использовал что-то вроде (0 1 2 3 4 5 6 7 B), представляя B как пустое место, а не просто имея 0. Сразу после просмотра этого комментария. Теперь я также успешно реализовал defstruct, и все работает прекрасно! :D - person Asia x3; 20.10.2015

Попробуйте утилиту memoize. Вы можете найти связанный с этим вопрос здесь (Как мне запомнить рекурсивный функция в Лиспе?). Memoize отслеживает вызовы любой функции memoized и немедленно возвращает любые известные (ранее вычисленные) результаты, чтобы избежать их пересчета. Результаты в случае рекурсивной функции, подобной вашей, впечатляют.

person Leo    schedule 19.10.2015
comment
Привет, Лео, я пытался использовать утилиту запоминания, но я, должно быть, запоминаю не ту функцию. Я пытался использовать его на функциях herusitic-eval и best-first. - person Asia x3; 19.10.2015