如何返回对递归Lisp函数中的列表所做的更改?

时间:2019-02-12 20:51:15

标签: common-lisp

问题标题并不完美,但这是我解释问题的最通用方法。我有一个“评估”逻辑方程式的函数。看起来像这样:

(defun evaluate (f)
  (let ((VARS (variables-in-list f)))
    (if (endp VARS)
      (evalutate-boolean-eq f)
      (or (evaluate (substitute f (first VARS) t))
          (evaluate (substitute (first VARS) nil))))))

该函数将获取一个列表,该列表本质上是一个带有变量的布尔方程,例如'(x & y)'((~x) & y),并查看是否有任何方法可以通过替换变量来使布尔表达式为真其中包含tnil并撤消表达式。因此,如果我们将'(x & y)传递给此函数,它将完成几件事:

  1. 它将首先使用variables-in-list提取列表中的所有变量。 VARS将等于'(x y)

  2. 然后它将用t替换列表中的第一个变量,将其传递到evaluate function,然后使用nil进行相同的操作。这将导致两个evaluate函数,第一个为f= '(t & y),第二个为f = '(nil & y)

  3. 它将再次获得所有变量-此图块VARS仅等于'(y)。它将对两个不同的f方程重复步骤2,得到'(t & t)'(t & nil)'(nil & t)'(nil & nil)

  4. 由于第三次迭代中的VARS为空(var中没有f),因此我运行了一个evaluate-boolean-eq函数,该函数将计算纯布尔方程式,并且返回true或false。

这只是一个例子-据我所知,如果公式不太大,则可以正常运行。不过,我现在要做的是跟踪所做的替换,并从我的evaluate function中将其返回。我想知道将哪些变量更改为布尔值,以便可以将函数评估为true。

因此,对于我们的'(x & y)示例,我想返回类似'((x t) (y t))的内容。在此功能内可能吗?我花了很长时间尝试修改,但是我仍然无法弄清楚如何用Lisp来完成。

或者,由于似乎不太可能在一个函数中执行这种复杂的操作,因此我只想返回f中的布尔值列表。当(endp VARS)的计算结果为true时,我想对(evaluate-boolean-eq f)进行评估,如果它为true,则仅返回ff中的所有布尔值。我只是不知道怎么用现在的结构只返回f。如何返回f的最终结果(在所有替换和全部之后?)

任何帮助将不胜感激,谢谢

2 个答案:

答案 0 :(得分:3)

如何?

(defun evaluate (f)
  "(evaluate F) returns values SAT, ASSIGNMENTS, where SAT is T iff F is satisfiable,
and ASSIGNMENTS, is an alist of (VAR . VAL) to satisfy F"
  (let ((VARS (variables-in-list f)))
    (if (endp VARS)
      (values (evalutate-boolean-eq f) nil)
      (multiple-value-bind (sat assign) (evaluate (substitute f (first vars) t)
        (if sat
            (values t (acons (first vars) t assign))
            (multiple-value-bind (sat assign) (evaluate (substitute f (first vars) nil)
              (if sat
                  (values t (acons (first vars) nil assign))
                  (values nil nil))))))))

对于天真的实现而言,这仍然很糟糕,因为它无法短路故障。例如。对于像这样的表达式:

((x & ~x) & (y1 | ... | y10))

您将需要进行2048次评估以表明这是不满意的

答案 1 :(得分:1)

如果目标是保持evaluate函数的参数不变,即,不添加建议的@jkiiski这样的环境,则以下内容可能对您有用。

由于您需要跨所有evaluate递归调用来“跟踪”当前路径的状态,因此您可以凭直觉知道需要在evaluate函数周围存储一个内存。

也就是说,您需要一个对象或至少一个封闭对象。由于我不知道您的确切需求(例如您的软件设计),因此我将继续讲第二个。这是您的简化案例:

(let ((r ())
      (temp ()))
  (defun my-let-test (list)
    (format t "temp: ~a~%" temp)
    (if (endp list)
      (progn
        (push temp r) ;; add to the result variable.
        (setf temp ())) ;; clear the temp variable.
      (progn
        (setf temp (append temp `(,(car list)))) ;; add the element at the end of the temp variable.
        (my-let-test (cdr list)))))

  (my-let-test '(a b c d))
  (my-let-test '(a b c))
  r)

执行let表单输出:

temp: NIL
temp: (A)
temp: (A B)
temp: (A B C)
temp: (A B C D)
temp: NIL
temp: (A)
temp: (A B)
temp: (A B C)

并返回:((A B C) (A B C D)),即存储在r词法变量中的值。

最重要的是行(setf temp ())),如果您忘记清除temp变量,您将在r中得到像((A B C D A B C) (A B C D))这样的串联结果。 但是,r也包含两个调用的结果并不令人满意。由于每个调用都需要一个r和一个temp变量,因此您再次需要一个“更高的步骤”:

(defun my-new-let-test (list)
  (let ((r ())
        (temp ()))
    (labels ((my-local-let-test (list)
           (format t "temp: ~a~%" temp)
           (if (endp list)
             (progn
               (push temp r)
               (setf temp ()))
             (progn
               (setf temp (append temp `(,(car list))))
               (my-local-let-test (cdr list))))))
      (my-local-let-test list))
    r))

(my-new-let-test '(a b c d))
(my-new-let-test '(a b c))

我只是将my-let-test函数的代码以my-local-let-test的形式放在labels中,因为它是递归函数。

my-new-let-test的第一次调用输出:

temp: NIL
temp: (A)
temp: (A B)
temp: (A B C)
temp: (A B C D)

并返回((A B C D))。第二个调用输出:

temp: NIL
temp: (A)
temp: (A B)
temp: (A B C)

并返回((A B C))

请注意以下形式:

(progn
  (push temp r)
  (setf temp ()))

可以重写:(push temp r),因为您不再需要清除temp变量(因为它对于每个调用都是唯一的)。


但是,它不能说明以下事实:使用此代码,您无法像在代码中那样进行“条件分支”(用于布尔替换),例如:

(if (some condition)
    (my-local-let-test arg1)
    (my-local-let-test arg2))

您必须使用堆栈来处理此类问题。

作为练习,让我们尝试执行以下操作:将所有元素都用数字分隔(两个数字之间至少有一个字母)。

例如:

  • (A B C D E F)返回((A B C D E F))
  • (A B 5 C D 6 E F 7)返回((A B 5) (C D 6) (E F 7))
  • (A B 5 C D 6 E F 7 G)返回((A B 5) (C D 6) (E F 7) (G))

这是一个解决方案:

(defun my-new-let-test (list)
  (let ((r '())
        (temp '(()))) ;; here temp is a list containing an empty list.
    (labels ((my-local-let-test (list)
           (format t "temp: ~a~%" temp)
           (if (endp list)
               (push (pop temp) r) ;; pop the stack
               (progn
                 ;; /!\ See explanations below. /!\
                 (if (null (first temp))
                     (setf temp `((,(car list))))
                     (setf (first temp) (append (first temp) `(,(car list)))))
                 ;; /!\ End of warning. /!\
                 (if (numberp (car list))
                     (progn
                       (push (pop temp) r) ;; pop the stack
                       (when (cdr list)
                         (push '() temp) ;; push the stack
                     (my-local-let-test (cdr list))))
                 (my-local-let-test (cdr list)))))))
      (my-local-let-test list))
    (reverse r)))

(my-new-let-test '(A B 5 C D 6 E F 7 G))的输出:

temp: (NIL)
temp: ((A))
temp: ((A B))
temp: (NIL)
temp: ((C))
temp: ((C D))
temp: (NIL)
temp: ((E))
temp: ((E F))
temp: (NIL)
temp: ((G))

关于警告: 如果您执行以下操作,则可能会产生一个奇怪的“ bug”:

(defun my-new-let-test (list)
  (let ((temp '(())))
    (format t "temp: ~a~%~%" temp)
    ((lambda (list)
       (setf temp `(,(car list))))
     list)
    (format t "===========~%")))

(my-new-let-test '(a b c d e)) ;; Execute this form twice.

它应该第一次输出(NIL),第二次输出((A));因为未清除temp变量(是setf上的(())的问题)。在真实的代码中,您应该像这样进行测试以避免这种情况:

(if (null (first temp))
  (setf temp `(,(car list)))
  (push (car list) (first temp)))

REPL(SBCL)在示例中显示警告,但在练习代码中未显示。


根据上面的说明,解决您的问题的方法(我无法测试,但一定是这样的东西):

(defun evaluate (f)
  (let ((r ())
        (temp '(())))
    (labels ((local-evaluate (f)
       (let ((VARS (variables-in-list f)))
         (if (endp VARS)
             (when (evaluate-boolean-eq f)
               (push temp r)) ;; save the result
             (progn
               ;;; Replace by T.
               (if (null (first temp)) ;; Warning from above.
                 (setf temp `(,@(first temps) T)) ;; push the stack
                 (push `(,@(first temps) T) temp)) ;; push the stack
               (local-evaluate (substitute f (first VARS) T))
               (pop temp) ;; pop the stack
               ;;; Replace by NIL.
               (if (null (first temp)) ;; Warning from above.
                 (setf temp `(,@(first temps) NIL)) ;; push the stack
                 (push `(,@(first temps) NIL) temp)) ;; push the stack
               (local-evaluate (substitute (first VARS) NIL))
               (pop temp) ;; pop the stack
               )))))
      (local-evaluate f))
    r))

注意:

  • 如果不是这种情况,我强烈建议您对代码进行单元测试。
  • 您可以使用二叉树。
  • 您可以使用波兰语符号https://en.wikipedia.org/wiki/Polish_notation转换逻辑表达式,然后将&转换为and,将~转换为not,依此类推。;然后在代码上使用eval将其解析为Lisp。
  • 如@Dan Robertson所建议的那样,该符号可以帮助您“短路”故障。