列表中没有括号。您从包含五个元素的列表(a b c d e) 开始,然后返回包含两个元素的列表(a (b (c (d (e)))))。第一个元素是a,第二个元素是另一个列表(b (c (d (e))))。
使用reduce 很容易接近:
CL-USER> (reduce 'list '(a b c d e) :from-end t)
(A (B (C (D E))))
您可以将reduce 视为将函数list“注入”到(a b c d e) 中以产生
(list a (list b (list c (list d e))))
这几乎就是你想要的。你实际上想要:
(list a (list b (list c (list d (list e)))))
你将如何制作它?您可以递归列表,对于每个子列表(x . ys),您想要返回(list x (recurse ys)),但ys 是() 时除外。你不想递归到(),因为你不想要一个包含两个元素的列表,你实际上什么都不想要。因此,诀窍是比您通常使用列表更早地停止递归。因此:
(defun parens (l)
(cond
((endp l) '())
((endp (rest l)) l)
((list (first l) (parens (rest l)))))) ; *
CL-USER> (parens '(a b c d e))
(A (B (C (D (E)))))
CL-USER> (parens '(a b))
(A (B))
CL-USER> (parens '(a))
(A)
CL-USER> (parens '())
NIL
*在最后一个子句中省略 t 测试是有意的。如果cond 子句中没有正文形式,则返回测试值。因此(list …) 既是测试形式又是值形式。
我们实际上可以稍微清理一下。 ((endp l) '()) 的情况可能是 ((endp l) l),因为 l 是空列表。但这意味着在第一种和第二种情况下,我们都可以返回l。我们可以在 Common Lisp 中调用 (rest '()) 并返回 (),所以当 l 类似于 (e) 时,(rest l) 将是 () 并且 当 l 是 @987654353 时@。这意味着我们可以使用:
(defun parens (l)
(cond
((endp (rest l)) l)
((list (first l) (parens (rest l))))))
如果我们只有一个测试,我们不妨使用if:
(defun parens (l)
(if (endp (rest l))
l
(list (first l) (parens (rest l)))))