如何以Common Lisp惯用方式优化进制转换函数避免代码重复?
十进制转任意进制的Common Lisp实现优化
手动计算示例
250 : 8 = 31 + 2 31 : 8 = 3 + 7 ∴ (372)₈
原实现函数
对应的Common Lisp函数如下:
(defun dec->opns (n base) (do* ((lst nil (append lst (list pos))) ; this is also not so nice (m n (truncate m base)) (pos (rem m base) (rem m base)) ) ; <<<<<<<< ((< m base) (reverse (append lst (list m)))) ))
该函数运行正常,测试示例:
CL-USER> (dec->opns 2500000 8) (1 1 4 2 2 6 4 0)
优化目标与背景
希望避免代码中重复出现的(rem m base),同时找到更优雅的惯用实现。另外,作为小学教师资格备考者,希望结合Papert《Mindstorms》的思路,从算法实现反思数学教学案例,中期计划开发Common Lisp版Logo环境cλogos(可通过quickload加载)。
优化方案:消除重复计算,提升代码优雅性
Common Lisp的truncate函数会同时返回商和余数两个值,利用这个特性可以一次性获取所需结果,避免重复调用rem。同时,原代码中用append累积列表效率较低,改用push后反转列表是更惯用的写法。
1. 迭代式优化实现(高效且惯用)
(defun dec->opns (n base) (do ((lst nil) (current n)) ((< current base) (reverse (cons current lst))) (multiple-value-bind (quotient remainder) (truncate current base) (push remainder lst) (setf current quotient))))
- 用
multiple-value-bind一次性绑定商和余数,消除重复计算 - 用
push向列表头部添加余数(O(1)操作,比append高效) - 循环结束时将最终的商加入列表,再反转得到正确顺序
2. 递归式实现(贴合手动计算逻辑,适合教学反思)
如果更看重逻辑的直观性,递归实现完全复刻手动“除基取余”的步骤,非常适合用来拆解教学中的算法逻辑:
(defun dec->opns (n base) (if (< n base) (list n) (multiple-value-bind (quotient remainder) (truncate n base) (append (dec->opns quotient base) (list remainder)))))
- 当数值小于进制数时,直接返回单元素列表(对应手动计算的最后一步商)
- 否则计算商和余数,递归处理商后将余数追加到结果末尾,完全对应手动计算的流程
教学视角的延伸思考
从Papert的建构主义教学思路来看:
- 递归版本的代码逻辑和手动计算步骤完全对齐,每一次递归调用就是一次除法运算,能帮助理解“进制转换”的核心是逐步分解数值,累积余数
- 迭代版本则展示了“循环累积结果”的思路,适合引导学生理解“重复操作-收集结果-调整顺序”的过程
- 两种实现都能用来反思:如何把抽象的数学算法拆解成学生能理解的分步操作,比如用Logo的海龟绘图来模拟“除基取余”的过程,让学生直观看到每一步的结果
内容的提问来源于stack exchange,提问作者Demihm Seinname
相关产品推荐
相关产品推荐

