在 Lisp 中,用于计算余数的函数是 %。该函数返回第一个参数除以第二个参数的余数。巧合的是,% 是一个无法通过 apropos 查到的 Emacs Lisp 函数:输入 M-x apropos RET remainder RET 不会得到任何结果。了解 % 存在的唯一途径是阅读本书这类资料,或是 Emacs Lisp 源码。
你可以通过计算下面两个表达式来尝试 % 函数:
(% 7 5) (% 10 5)
第一个表达式返回 2,第二个返回 0。
要判断返回值是否为 0,我们可以使用 zerop 函数。当参数(必须为数字)为 0 时,该函数返回 t。
(zerop (% 7 5))
⇒ nil
(zerop (% 10 5))
⇒ t
因此,若图表高度可以被 5 整除,下面的表达式会返回 t:
(zerop (% height 5))
(当然,height 的值可以通过 (apply 'max numbers-list) 得到。)
另一方面,如果 height 的值不是 5 的倍数,我们希望将其重置为更大的下一个 5 的倍数。这是简单的算术运算,使用我们已经熟悉的函数即可实现。首先用 height 除以 5,得到 5 能被除几次。例如 5 除 12 商 2。将商加 1 再乘以 5,就得到比该高度更大的下一个 5 的倍数。5 除 12 商 2,2 加 1 再乘以 5,结果是 15,也就是比 12 大的下一个 5 的倍数。对应的 Lisp 表达式为:
(* (1+ (/ height 5)) 5)
例如计算下面表达式,结果为 15:
(* (1+ (/ 12 5)) 5)
在整个讨论中,我们一直使用 5 作为纵轴标注的间距;但我们也可能使用其他数值。为了通用性,应该用一个可赋值的变量代替 5。我能想到的最合适的变量名是 Y-axis-label-spacing。
使用该变量并配合 if 表达式,我们得到如下代码:
(if (zerop (% height Y-axis-label-spacing))
height
;; else
(* (1+ (/ height Y-axis-label-spacing))
Y-axis-label-spacing))
如果高度恰好是 Y-axis-label-spacing 值的整数倍,该表达式直接返回 height;否则计算并返回比当前高度更大的下一个 Y-axis-label-spacing 倍数。
现在我们可以将该表达式加入 print-graph 函数的 let 表达式中(需先设置 Y-axis-label-spacing 的值):
(defvar Y-axis-label-spacing 5 "纵轴相邻两个标注之间的行数。")
...
(let* ((height (apply 'max numbers-list))
(height-of-top-line
(if (zerop (% height Y-axis-label-spacing))
height
;; else
(* (1+ (/ height Y-axis-label-spacing))
Y-axis-label-spacing)))
(symbol-width (length graph-blank))))
...
(注意这里使用了 let* 函数:(apply 'max numbers-list) 先计算出 height 初始值,再用该结果计算其最终值。关于 let* 的更多内容,参见 See The let* expression。)