想象一下你已经有了你的功能。它得到什么?它必须产生什么?
给定一个原子,它返回什么?给定一个简单的原子列表,它应该返回什么?
(defun condense (x)
(typecase x
(number
; then what?
(condense-number x))
(list
; then what?
(if (all-atoms x)
(condense-list-of-atoms x) ; how to do that?
(process-further-somehow
(condense-lists-inside x))))
; what other clauses, if any, must be here?
))
必须condense-lists-inside
做什么?根据你的描述,就是把里面的嵌套列表--每个都压缩成一个数字,原子原封不动。所以它会留下一个数字列表。为了以某种方式进一步处理,我们已经“拥有”了一个功能,对吗?condense-list-of-atoms
现在,如何实施condense-lists-inside
?这很容易,
(defun condense-lists-inside (xs)
(mapcar #'dowhat xs))
做什么?为什么,condense
当然!请记住,我们想象我们已经拥有它。只要它得到了它想要得到的东西,它就会生产出它设计生产的东西。也就是说,给定一个原子或一个列表(内部可能有嵌套列表),它将产生一个 number。
所以现在,填空,简化。特别是,看看你是否真的需要all-atoms
检查。
编辑:实际上,使用typecase
是一个不幸的选择,因为它将 NIL 视为 LIST。我们需要区别对待 NIL,而是返回一个“零值”。所以最好使用通常的(cond ((null x) ...) ((numberp x) ...) ((listp x) ...) ... )
构造。
关于您的新代码:您犯了错误:要处理 之后返回的原子列表(mapcar #'condense x)
,我们有一个函数calculate
可以做到这一点,无需回溯到condense
自身。当您在那里替换calculate
时,很明显all-atoms
根本不需要检查;它只是一种教学设备,用于简化代码的开发。:) 在我们开发的时候做多余的选择是可以的,如果我们在达到正确的目标之后再把它们简化掉!
但是,删除all-atoms
检查会破坏您的要求 #2。然后计算将如下进行
(CONDENSE '(2 3 4 (3 1 1 1) (2 3 (1 2)) 5))
==
(calculate (mapcar #'condense '(2 3 4 (3 1 1 1) (2 3 (1 2)) 5)))
==
(calculate (list 2 3 4 (condense '(3 1 1 1)) (condense '(2 3 (1 2))) 5))
==
(calculate (list 2 3 4 (calculate '(3 1 1 1))
(calculate (list 2 3 (calculate '(1 2)))) 5))
==
(calculate (list 2 3 4 6 (calculate '(2 3 3)) 5))
==
(calculate (list 2 3 4 6 8 5))
==
28
即它将以从左到右的方式进行,而不是从最深的嵌套级别开始。将嵌套列表想象成一棵树(确实如此),这将在树的最深左角向上和向右“咀嚼”;带有all-atoms
检查的代码将严格按照级别进行。
所以最终的简化代码是:
(defun condense (x)
(if (listp x)
(reduce #'+ (mapcar #'condense x))
(abs x)))
备注:查看归约序列的最后一个插图,出现了一幅清晰的画面 -用计算应用程序替换参数树中的每个节点。这是 fold 的一个明显例子,就像是在树上而不是在普通列表上完成的那样。reduce
这可以直接用所谓的“car-cdr 递归”编码,用组合函数对单元格的递归调用的两个结果和组件的cons
应用程序替换每个单元格:f
car
cdr
(defun condense (x) (reduce-tree x #'+ 0))
(defun reduce-tree (x f z)
(labels ((g (x)
(cond
((consp x) (funcall f (g (car x)) (g (cdr x))))
((numberp x) x)
((null x) z)
(T (error "not a number")))))
(g x)))
正如你所看到的,这个版本是高度递归的,这不是很好。