以最简单的方式解决这个问题可能是最简单的,然后再想办法让它变得更简单或更高效。
如果您以递归方式执行此操作,请务必考虑基本案例是什么。这里一个合理的基本情况可能是 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。首先,您调用 generate-values 两次;调用一次并保存结果会更有效。如果您使用较大的 n 值调用此函数,这可能会在以后变得很重要,但这不会使函数不正确。但请记住 generate-values 返回什么;它返回不同组合的列表。这意味着您对 (cons n (generate-values ...)) 的调用将返回一个列表,其第一个元素是 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 '())。但是由于 cons 的第二个参数将是 (generate-values 0) 结果中的每个列表,因此您真的需要在由 (generate-values 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 的值(它们的列表),但是你使用 list 来组合它们。这将返回一个包含两个值的列表。相反,您需要一个列表,其中包含每个列表中的值。您想将它们与 append 结合起来(或者,由于所有结构都是新生成的,您可以使用 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 而不是 append 会节省一些内存,并且缓存递归调用的结果而不是重新计算它确实会很好。不影响正确性的风格问题可能是使用 if 而不是 cond,使用 list* 而不是 cons (表示我们正在使用列表,而不是 cons 单元格树),很高兴注意到您不必这样做 (- 0 n), -带有单个参数的 strong> 返回参数的否定。也就是说,(-n) = -n。