我想定义一个输入&#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))))
对此问题的任何帮助都应该非常感谢!谢谢!
答案 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 。