为LISP中的n个给定变量生成所有可能的true或false组合的列表

时间:2016-10-26 10:53:03

标签: recursion lisp common-lisp

我想定义一个输入&#34; n&#34; (变量的数量)并返回所有可能的真值。这里,我表示变量i的真值(1 <= i <= n),其中+ i表示真,而-i表示假。

例如:

(generate-values 2)

应该返回:

((2 1)(2 -1)(-2 1)(-2 -1))

(generate-values 3)

应该返回:

((3 2 1)(3 2 -1)(3 -2 1)(3 -2 -1)(-3 2 1)(-3 2 -1)(-3 -2 1)(-3 -2 -1))

这是我的错误尝试:

(defun generate-values (n)
  (cond
   ((equal n 0) nil)
   (t (list (cons n (generate-values (- n 1)))
            (cons (- 0 n) (generate-values (- n 1)))))))

我知道为什么这是不正确的,但我无法找到生成(3 2 1)的方法,然后转到(3 2 -1)。我的节目输出:

 ((3 (2 (1) (-1)) (-2 (1) (-1))) (-3 (2 (1) (-1)) (-2 (1) (-1))))

对此问题的任何帮助都应该非常感谢!谢谢!

1 个答案:

答案 0 :(得分:3)

以最简单的方式处理此问题可能最容易,然后找出如何使其更简单或更有效。

如果您以递归方式执行此操作,请务必考虑基本情况。这里合理的基本情况可能是 n = 0 。该函数始终应返回列表列表。在 n = 0 的情况下,没有“变量”,因此结果必须是空列表的列表:(())

对于 n 的情况,请考虑该函数为 n-1 返回的内容。它是 n-1 “变量”的所有组合的列表。您需要做的就是在其中添加 n ,并在其中添加 -n ,然后确保最终列出所有这些内容

直接编码,我们最终得到类似的东西:

(defun table (n)
  (if (zerop n)
      '(())
      (let* ((table (table (1- n)))
             (plus-pos-n (mapcar (lambda (subtable)
                                   (list* n subtable))
                                 table))
             (plus-neg-n (mapcar (lambda (subtable)
                                   (list* (- n) subtable))
                                 table)))
        (nconc plus-pos-n plus-neg-n))))
CL-USER> (table 3)
((3 2 1) (3 2 -1) (3 -2 1) (3 -2 -1) (-3 2 1) (-3 2 -1) (-3 -2 1) (-3 -2 -1))

现在,让我们看一下当前实现的不同之处,注意它当然没有 完全相同的算法。

(defun generate-values (n)
  (cond
    ((equal n 0)
     nil)
    (t
     (list (cons n
                   (generate-values (- n 1)))
             (cons (- 0 n)
                   (generate-values (- n 1)))))))

在风格上,由于只有两个分支,我更喜欢 if cond ,但这不是问题。在攻击基础案例之前,让我们看看递归情况,当 n≠0 时。首先,您要两次调用生成值;调用它一次并保存结果会更有效。如果您使用 n 的大值调用此函数,那么最终可能会变得很重要,但它不会使函数不正确。但请记住 generate-values 返回的内容;它返回不同组合的列表。这意味着您对(cons n(生成值...))的调用将返回一个列表,其第一个元素是 n ,其剩余元素是 N-1 即可。例如,你正在做类似的事情:

CL-USER> (table 1)
((1) (-1))
CL-USER> (cons 2 (table 1))
(2 (1) (-1))

但那不是你想要的。您真的想在每个列表中添加 n

CL-USER> (mapcar (lambda (x)
                   (cons 2 x))
                 (table 1))
((2 1) (2 -1))

这是递归案例中的问题。基本情况也存在问题。在递归的情况下,您希望将 n -n 添加到 n-1 案例中的每个子列表中。那么当你有 n = 1 时会发生什么?你想得到(cons 1'())(cons -1'())。但是,由于缺点的第二个参数将是(生成值0)结果中的每个列表,您真的需要在(生成值0)返回的列表中 。需要什么?空列表需要在那里。因此,基本案例需要返回(()),而不是()。因此,在进行这些更改后,您的代码将是:

(defun generate-values (n)
  (cond
    ((equal n 0)
     '(()))
    (t
     (list (mapcar (lambda (x)
                     (cons n x))
                   (generate-values (- n 1)))
           (mapcar (lambda (x)
                     (cons (- 0 n) x))
                   (generate-values (- n 1)))))))
CL-USER> (generate-values 3)
(((3 (2 (1)) (2 (-1))) (3 (-2 (1)) (-2 (-1))))
 ((-3 (2 (1)) (2 (-1))) (-3 (-2 (1)) (-2 (-1)))))

这更接近,但它仍然不太正确。在递归的情况下还有另一个。您最终会生成开头 n 的值(它们的列表),以及开头有 -n 的值(它们的列表),但是你正在使用列表来组合它们。返回一个包含两个值的列表。相反,您需要一个包含每个列表的值的列表。您希望将它们与追加组合(或者,由于所有结构都是新生成的,您可以使用 nconc ):

(defun generate-values (n)
  (cond
    ((equal n 0)
     '(()))
    (t
     (append (mapcar (lambda (x)
                       (cons n x))
                     (generate-values (- n 1)))
             (mapcar (lambda (x)
                       (cons (- 0 n) x))
                     (generate-values (- n 1)))))))
CL-USER> (generate-values 3)
((3 2 1) (3 2 -1) (3 -2 1) (3 -2 -1) (-3 2 1) (-3 2 -1) (-3 -2 1) (-3 -2 -1))

这个最终实现并不完全是我开始的,但它在算法方面基本相同。差异主要是风格,但也有一些效率问题。使用 nconc 而不是追加可以节省一些内存,最好是从递归调用中缓存结果,而不是重新计算它。不影响正确性的文体问题可能是使用 if 而不是 cond ,使用 list * 而不是 cons (表示我们正在使用列表,而不是利弊细胞的树),并且很高兴地注意到你不必做( - 0 n) - 使用单个参数返回参数的否定。也就是说,( - n)= -n

相关问题