C.2.1 插曲:计算余数

在 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。)