15.1 函数 graph-body-print

在前一节的准备工作完成后,graph-body-print 函数就很简单了。该函数将按列打印星号和空格,使用数字列表中的元素来指定每列中星号的数量。这是一个重复的操作,这意味着我们可以使用递减的 while 循环或递归函数来完成这项工作。在本节中,我们将使用 while 循环来编写定义。

column-of-graph 函数需要图表的高度作为参数,因此我们应该确定并记录它作为一个局部变量。

这导致了以下 while 循环版本的函数模板:

(defun graph-body-print (numbers-list)
  "文档…"
  (let ((height  …
         …))

    (while numbers-list
      插入列并重新定位点
      (setq numbers-list (cdr numbers-list)))))

我们需要填写模板的空白部分。

显然,我们可以使用表达式 (apply 'max numbers-list) 来确定图表的高度。

while 循环将逐个元素循环遍历 numbers-list。由于它被 (setq numbers-list (cdr numbers-list)) 表达式缩短,列表的每个实例的 CAR 是传递给 column-of-graph 的参数值。

while 循环的每个循环中,insert-rectangle 函数插入由 column-of-graph 返回的列表。由于 insert-rectangle 函数将点移动到插入的矩形的右下角,我们需要保存矩形插入时点的位置,在插入矩形后返回到该位置,然后水平移动到下一个调用 insert-rectangle 的位置。

如果插入的列只有一个字符宽,就像使用单个空格和星号时一样,重新定位命令就是简单的 (forward-char 1);然而,列的宽度可能大于一个字符。这意味着重新定位命令应该写为 (forward-char symbol-width)symbol-width 本身是一个 graph-blank 的长度,可以使用表达式 (length graph-blank) 找到。将 symbol-width 变量绑定到图列的宽度值的最佳位置是在 let 表达式的 varlist 中。

这些考虑导致了以下函数定义:

(defun graph-body-print (numbers-list)
  "打印 NUMBERS-LIST 的条形图。
numbers-list 由 Y 轴的值组成。"

  (let ((height (apply 'max numbers-list))
        (symbol-width (length graph-blank))
        from-position)

    (while numbers-list
      (setq from-position (point))
      (insert-rectangle
       (column-of-graph height (car numbers-list)))
      (goto-char from-position)
      (forward-char symbol-width)
      ;; 逐列绘制图形。
      (sit-for 0)
      (setq numbers-list (cdr numbers-list)))
    ;; 为 X 轴标签定位点。
    (forward-line height)
    (insert "\n")
))

这个函数中唯一意外的表达式是 (sit-for 0) 表达式在 while 循环中。这个表达式使得图形打印操作比没有它更有趣。该表达式导致 Emacs 在零时间内“坐下”或什么都不做,然后重新绘制屏幕。放在这里,它导致 Emacs 逐列重新绘制屏幕。如果没有它,Emacs 将在函数退出时才重新绘制屏幕。

我们可以使用一组短数字列表测试 graph-body-print

  1. 安装 graph-symbolgraph-blankcolumn-of-graph,它们在 打印图表的列, 以及 graph-body-print
  2. 复制以下表达式:
    (graph-body-print '(1 2 3 4 6 4 3 5 7 6 5 2 3))
    
  3. 切换到 *scratch* 缓冲区,并将光标放在要开始绘制图表的位置。
  4. 输入 M-: (eval-expression).
  5. 使用 C-y (yank) 将 graph-body-print 表达式粘贴到迷你缓冲区中。
  6. RET 以评估 graph-body-print 表达式。

Emacs 将打印一个类似于以下的图形:

                    *
                *   **
                *  ****
               *** ****
              ********* *
             ************
            *************