Ex4.4 The Not Looking after You Don't Leap Problem. その2

ヒントの通りにachieveに引数を追加します。

これをappropriate-opsからappropriate-pまで伝播させます。
appropriate-pでいずれかのprotected-goalが削除される場合、適用出来ない様にします。
achieveはachieve-eachから呼ばれているので、achieve-eachも修正します。

(defun achieve-each (state goals goal-stack)
  "Achieve each goal, and make sure they still hold at the end."
  (let ((current-state state))
    (if (and (every #'(lambda (g)
                        (setf current-state
                              (achieve current-state g (remove g goals) goal-stack)))
                    goals)
             (subsetp goals current-state :test #'equal))
        current-state)))

(defun appropriate-p (goal protected-goals op)
  "An op is appropriate to a goal if it is in its add list."
  (and (member-equal goal (op-add-list op))
       (not (some #'(lambda (protected-goal)
		      (member-equal protected-goal (op-del-list op)))
		  protected-goals))))

(defun appropriate-ops (goal protected-goals state)
  "Return a list of appropriate operators, 
  sorted by the number of unfulfilled preconditions."
  (sort (copy-list (find-all goal *ops*
			     :test #'(lambda (goal op)
				       (appropriate-p goal protected-goals op))))  #'<
        :key #'(lambda (op) 
                 (count-if #'(lambda (precond)
                               (not (member-equal precond state)))
                           (op-preconds op)))))

(defun achieve (state goal protected-goals goal-stack)
  "A goal is achieved if it already holds,
  or if there is an appropriate op for it that is applicable."
  (dbg-indent :gps (length goal-stack) "Goal: ~a (~a)" goal protected-goals)
  (cond ((member-equal goal state) state)
        ((member-equal goal goal-stack) nil)
        (t (some #'(lambda (op) (apply-op state goal op goal-stack))
                 (appropriate-ops goal protected-goals state)))))

解決は出来た模様。

(gps '(son-at-home have-money car-works)
     '(son-at-school have-money) *school-ops*)

;; => ((PAIP.GPSV2::START) (PAIP.GPSV2::EXECUTING DRIVE-SON-TO-SCHOOL))

(gps '(son-at-home have-money car-works)
     '(son-at-school) *school-ops*)

;; => ((PAIP.GPSV2::START) (PAIP.GPSV2::EXECUTING TAXI-SUN-TO-SCHOOL))

achieveのみを修正するバージョン

ヒントを良く読むと、apply-opが返すリストを検査し、他のゴールが削除されていた場合、失敗させる方法らしいです。
確かにそっちの方が修正範囲が少なくてすみますね。
appropriate-opsとappropriate-pは修正前に戻しました。

intersectionでstateに含まれるother-goalsを取り出し、protected-goalsとします。
apply-op後に全てのprotected-goalがapply-opの結果に含まれていることを確認します。

(defun achieve (state goal other-goals goal-stack)
  "A goal is achieved if it already holds,
  or if there is an appropriate op for it that is applicable."
  (dbg-indent :gps (length goal-stack) "Goal: ~a (~a)" goal other-goals)
  (cond ((member-equal goal state) state)
        ((member-equal goal goal-stack) nil)
        (t (let ((protected-goals (intersection state other-goals :test #'equal)))
	     (some #'(lambda (op)
		       (let ((result (apply-op state goal op goal-stack)))
			 (when (every #'(lambda (protected-goal)
					  (member-equal protected-goal result))
				      protected-goals)
			   result)))
		   (appropriate-ops goal state))))))

(defun appropriate-ops (goal state)
  "Return a list of appropriate operators, 
  sorted by the number of unfulfilled preconditions."
  (sort (copy-list (find-all goal *ops* :test #'appropriate-p)) #'<
	:key #'(lambda (op) 
                 (count-if #'(lambda (precond)
                               (not (member-equal precond state)))
                           (op-preconds op)))))
(defun appropriate-p (goal op)
  "An op is appropriate to a goal if it is in its add list."
  (member-equal goal (op-add-list op)))

当然、同じ結果になりました。

(gps '(son-at-home have-money car-works)
     '(son-at-school have-money) *school-ops*)

;; => ((PAIP.GPSV2::START) (PAIP.GPSV2::EXECUTING DRIVE-SON-TO-SCHOOL))

(gps '(son-at-home have-money car-works)
     '(son-at-school) *school-ops*)

;; => ((PAIP.GPSV2::START) (PAIP.GPSV2::EXECUTING TAXI-SUN-TO-SCHOOL))

さらにヒントをよく見ると…

残った全てのゴールがapply-opの結果で達成できるかを確認すれば良かっただけ
の模様…。

(defun achieve (state goal other-goals goal-stack)
  "A goal is achieved if it already holds,
  or if there is an appropriate op for it that is applicable."
  (dbg-indent :gps (length goal-stack) "Goal: ~a (~a)" goal other-goals)
  (cond ((member-equal goal state) state)
        ((member-equal goal goal-stack) nil)
        (t (some #'(lambda (op)
		     (achieve-all (apply-op state goal op goal-stack) other-goals goal-stack))
		 (appropriate-ops goal state)))))

これでも解決。

(gps '(son-at-home have-money car-works)
     '(son-at-school have-money) *school-ops*)

;; => ((PAIP.GPSV2::START) (PAIP.GPSV2::EXECUTING DRIVE-SON-TO-SCHOOL))

(gps '(son-at-home have-money car-works)
     '(son-at-school) *school-ops*)

;; => ((PAIP.GPSV2::START) (PAIP.GPSV2::EXECUTING TAXI-SUN-TO-SCHOOL))

ヒントはちゃんと読むべきですね。