`

Programming in Emacs Lisp笔记(十五)准备图表

阅读更多

准备图表

我们的目标是构造一个图表显示Emacs lisp源码中所有函数定义的长度范围。

在实际应用中,如果你要创建一个图表,你可能会使用gnuplot之类的程序来完成这个工作。(gnuplot与GNU Emacs集成得很好。)但在这里,我们将使用前面我们所学的知识来完成这个工作。

在这章,我们将先编写一个简单的图表打印函数。第一个版本将作为原型,在此基础上来增强。

打印图表列

由于Emacs被设计为能在各种终端上工作,包括字符终端,图表需要是可打印字符。我们可以使用星号来打印图表。

我们把这个函数命名为graph-body-print;它使用numbers-list作为参数。

graph-body-print函数根据numbers-list中的每个原素,分别插入垂直方向的星号列。每一列的高度取决于numbers-list上元素值的大小。

插入列是一个重复动作,因此函数可以用while循环或递归实现。

我们面临的第一个挑战就是如何打印星号列。通常,在Emacs我们打印字符的时候是横向打印的,一行一行的打印。我们有两个办法来实现:编写我们自己的列插入函数或者查找Emacs中是否有现成的方法。

为查找Emacs中的函数,我们可以使用M-x apropos命令。这个命令与C-h a(command-apropos)命令类似,但后者只查找作为命令的函数。而M-x apropos命令将列出所有匹配正则表达式的符号,包括那些非交互式的函数。

我们想找到那些可以打印或插入纵向列的命令。这个函数的名称肯定包含有'print'或'insert'或'column'等单词。因此,我们只要输入M-x apropos RET print\|insert\|column RET并查看结果。在我们系统上,这个命令执行需要一些时间,结果包含有79个函数和变量。查找这个列表,我们看到有个insert-rectangle函数有可能能完成这个工作。

这个函数文档如下:

insert-rectangle:
Insert text of RECTANGLE with upper left corner at point.
RECTANGLE's first line is inserted at point,
its second line is inserted at a point vertically under point, etc.
RECTANGLE should be a list of strings.

我们可以测试一下,以确认它是否如我们期望的那样工作。

把光标放在insert-rectange语句的后面按C-u C-x C-e(eval-last-sexp)。这个函数将在point的下面插入"first","second","third"。函数返回值为nil。

(insert-rectangle '("first" "second" "third"))first
second
third
nil
在绘制图表的程序中使用这上函数。我们需要先确保point位于需要插入的位置,然后用insert-rectangle函数插入列。

如果你是在Info中读取这个文档,你可以切换到另一个缓冲区,比如*scratch*,将point放在任何地方,输入M-:,在提示区输入insert-rectangle语句,然后回车。Emacs将执行输入的语句,交把*scratch*缓冲区中的point位置作为point的值。(M-:被绑定到eval-expression上。)

我们将发现当执行完成插入后,point被设置在了最后插入的那行,也就是说这个函数移动了point。如果我们重复执行这个命令,下次插入的内容将在上次插入内容的下面。我们并不需要这样,我们需要的是一个柱状图表,一列挨着一列。

我们看出每次while循环插入列时必须重新设置point的位置,这个位置必须在列的顶部,而不是在底部。并且,我们打印图表时,并不需要每个列 都一样高。这意味着每个列的顶部并不是一样高的。我们不能简单在一同一行上执行同一个操作,而是需要先将point移到正确的位置。

我们准备用星号来描述柱状图。星号的数量取决于当前numbers-list中元素的值。我们需要构造一个包含星号的列表以便insert- rectangle来画出正确高度的列。如果这个list只包含一定数量的星号,那我们就必须在绘制前将point设置到正确的高度。这比较困难。

我们可以想出另外一种方式,每次传递给insert-rectangle一个同样长度的list,它们可以在同一行插入,每次插入时只需要向右移动一列。比如,如果最高的高度为5,但实际高度只有3,则insert-rectangle需要的参数如下:

(" " " " "*" "*" "*")

最后一个需求不是很难,我们需要决定列的高度。有两种方法:我们可以使用任意的值或使用整个list中最大的数字作为最大高度值。Emacs中提供了内置的函数检查参数中的最大值。我们可以使用这个函数。这个函数被称为max它返回它所有参数中的最大值。例:

(max  3 4 6 5 7 3)

将返回7。(相反的函数是min它返回参数中最小的值)

但是,我们不能简单的在numbers-list上调用max;max函数需要数字类型的参数,而不是包含数字的list。因此,下面的语句:

(max  '(3 4 6 5 7 3))
将出错:
Wrong type of argument:  number-or-marker-p, (3 4 6 5 7 3)

我们需要一个函数将list拆开作为参数传递给函数。这个函数是apply。这个函数将其它的参数传递给它的第一个参数,它的最后一个参数可以是一个list。

例如:

(apply 'max 3 4 7 3 '(4 8 5))
将返回8。

(顺便说一句,我不知道你如何学习书本上没有介绍过的函数。可以根据函数名称,比如search-forward或insert-rectangle,根据他们的部分名称使用apropos查找函数的相关信息。)

传递给apply的第二个参数是可选参数,我们可以使用aplly调用一个函数并将list中的元素传递给这个函数,比如下面的代码也将返回8:

(apply 'max '(4 8 5))
后面我们将使用apply。函数recursive-lengths-list-many-files返回包含数字的list,我们对其调用max。

这样,查找图表中的最大数量的代码如下:

(setq max-graph-height (apply 'max numbers-list))

现在我们回到如何构造包含列图表字符串的list的问题上。知道图表的最大高度和星号的数量后,函数应该可以返回一个传递给insert-rectangle的list了。

每一列由星号或空格构成。因为函数传递了列高度和列中的星号数量两个参数,空白的数量应该是高度减去星号数量。给出空白数量和星号数量后,两个循环可以构造出这个list:

;;; First version.
(defun column-of-graph (max-graph-height actual-height)
"Return list of strings that is one column of a graph."
(let ((insert-list nil)
(number-of-top-blanks
(- max-graph-height actual-height)))

;; Fill in asterisks.
(while (> actual-height 0)
(setq insert-list (cons "*" insert-list))
(setq actual-height (1- actual-height)))

;; Fill in blanks.
(while (> number-of-top-blanks 0)
(setq insert-list (cons " " insert-list))
(setq number-of-top-blanks
(1- number-of-top-blanks)))

;; Return whole list.
insert-list))
安装这个函数后,执行下面的代码:
(column-of-graph 5 3)
将返回:
(" " " " "*" "*" "*")

如上面所写,column-of-graph包含一个瑕疵:用于标识空白和列的符号是硬编码的,使用了空白和星号。这是一个很好的原型,如果其它人 想换成其它的符号。比如用逗号代替空白,用加号代替星号等。程序应该更具弹性一些。应该使用两个变量来代替空白和星号:将graph-blank和 graph-symbol定义为两个独立的变量。

上面也没有编写文档。我们可以编写这个函数的第二个版本:

(defvar graph-symbol "*"
"String used as symbol in graph, usually an asterisk.")

(defvar graph-blank " "
"String used as blank in graph, usually a blank space.
graph-blank must be the same number of columns wide
as graph-symbol."
)

;;(For an explanation of defvar, see Initializing a Variable with defvar.)

;;; Second version.
(defun column-of-graph (max-graph-height actual-height)
"Return MAX-GRAPH-HEIGHT strings; ACTUAL-HEIGHT are graph-symbols.
The graph-symbols are contiguous entries at the end
of the list.
The list will be inserted as one column of a graph.
The strings are either graph-blank or graph-symbol."


(let ((insert-list nil)
(number-of-top-blanks
(- max-graph-height actual-height)))

;; Fill in graph-symbols.
(while (> actual-height 0)
(setq insert-list (cons graph-symbol insert-list))
(setq actual-height (1- actual-height)))

;; Fill in graph-blanks.
(while (> number-of-top-blanks 0)
(setq insert-list (cons graph-blank insert-list))
(setq number-of-top-blanks
(1- number-of-top-blanks)))

;; Return whole list.
insert-list))

如果需要,我们可以再次重写column-of-graph,使用线型图表代替柱状图表。这不会很困难。其中一个办法就是让柱状图中第一个星号以下的显示 为空白。在构造线型图表的一个列时,函数首先构造一个空的list,长度比元素的值小1,然后用cons将符号和列表连接;然后再次使用cons将顶部用 空白填充。

现在,我们终于完成第一个打印图表的函数。它只打印了图表的body部分,而没有水平和垂直方向的轴,因此我们把这个函数称为graph-body-print。

graph-body-print函数

上一节,graph-body-print函数完成了打印图表列的功能。这应该是一个重复执行的动作。我们可以使用递减的while循环或递归函数来完成这些操作。这节,我们使用while循环来编写函数定义。

column-of-graph函数需要图表高度作为参数,因此我们需要决定图表高度并将它保存到一个局部变量中。

我们的使用while循环的函数模板如下:

(defun graph-body-print (numbers-list)
"documentation..."
(let ((height ...
...))

(while numbers-list
insert-columns-and-reposition-point
(setq numbers-list (cdr numbers-list))))

我们需要填空。

我们可以用(apply 'max numbers-list) 获取图表的高度。

while循环遍历numbers-list。并用(setq numbers-list (cdr numbers-list))截短它。每次list的CAR值,就是传递给column-of-graph的参数。

每个循环周期中,insert-rectangle函数使用column-of-graph插入list。由于insert-rectangle函 数将point移到了插入的矩形区域的右下解,我们需要保存当前point的位置,在插入矩形区域后恢复point的位置,然后将point水平移动到下 一个列,并再次调用insert-rectangle。

如果被插入的列是一个字符宽(比如星号或一个空格),这个命令比较简单(forward-char 1);但如果列宽超过1。这时命令需要写为(forward-char symbol-width)symbol-width是graph-blank的长度,可以使用(length graph-blank)。可以在let语句的变量列表中设置symbol-width变量。

函数定义如下:

(defun graph-body-print (numbers-list)
"Print a bar graph of the NUMBERS-LIST.
The numbers-list consists of the Y-axis values."


(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)
;; Draw graph column by column.
(sit-for 0)
(setq numbers-list (cdr numbers-list)))
;; Place point for X axis labels.
(forward-line height)
(insert "\n")
))

这里出现了一个新的函数(sit-for 0)。这个语句将使Emacs重绘屏幕。放在这里,Emacs将一列列的绘制。如果没有,Emacs在函数退出前都不会绘制。

我们可以使用一个较短的包含数字的list来测试graph-body-print。

  1. 安装graph-symbol,graph-blank,column-of-graph,graph-body-print。
  2. 复制下面的语句:
(graph-body-print '(1 2 3 4 6 4 3 5 7 6 5 2 3))
  1. 切换到*scratch*缓冲区并把光标放置在要绘制的开始位置。
  2. 输入M-:(eval-expresion)
  3. Yank(C-Y) graph-body-print语句到缓冲区中。
  4.  回车执行graph-body-print语句。

Emacs将打印出下面的图表:

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

recursive-graph-body-print函数

graph-body-print函数也可以用递归来编写。递归分解为两个部分:外部使用let包装,决定几个变量的值,比如图表最大高度,内部的函数调用是递归调用,用于打印图表,

包装部分不复杂:

(defun recursive-graph-body-print (numbers-list)
"Print a bar graph of the NUMBERS-LIST.
The numbers-list consists of the Y-axis values."

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

递归函数部分有点复杂。它有四个部分:'do-again-test'打印操作的代码,递归调用,'next-step-expression'。 'do-again-test'是一个if语句用于检查numbers-list是否还有元素,如果有函数将使用打印操作的代码打印一个列,并再次调用自 身。函数调用自身时'next-step-expressin'将截短numbers-list。

(defun recursive-graph-body-print-internal
(numbers-list height symbol-width)
"Print a bar graph.
Used within recursive-graph-body-print function."


(if numbers-list
(progn
(setq from-position (point))
(insert-rectangle
(column-of-graph height (car numbers-list)))
(goto-char from-position)
(forward-char symbol-width)
(sit-for 0) ; Draw graph column by column.
(recursive-graph-body-print-internal
(cdr numbers-list) height symbol-width))))

在安装这个函数后,可以用下面的例子测试:

(recursive-graph-body-print '(3 2 5 6 7 5 3 4 6 4 3 2 1))
结果如下:
                *
** *
**** *
**** ***
* *********
************
*************
评论

相关推荐

    Programming in Emacs Lisp

    Programming in Emacs Lisp英文版

    An Introduction to Programming in Emacs Lisp

    - **标题**:“An Introduction to Programming in Emacs Lisp”(Emacs Lisp编程入门) - **描述**:该资源是基于Emacs官方文档的重编版本,旨在提供更易阅读的字体样式。 #### 知识点详解 ##### 1. Emacs Lisp...

    An Introduction to Programming in Emacs Lisp Second Edition

    - **标题**:“An Introduction to Programming in Emacs Lisp Second Edition” - **描述**:本书是关于Emacs Lisp编程的入门教程,被誉为“经典中的经典”。 该书由Robert J. Chassell撰写,由自由软件基金会出版...

    Robert Chassell:An Introduction to Programming in Emacs Lisp

    ### Robert Chassell:An Introduction to Programming in Emacs Lisp #### 知识点概览 - **Lisp Lists**: 介绍Lisp列表的概念及其在Emacs Lisp中的应用。 - **Lisp Atoms**: 解释Lisp原子的基本概念以及它们在...

    An Introduction to Programming in Emacs Lisp [3.10].chm

    An Introduction to Programming in Emacs Lisp [3.10].chm

    GNU Emacs Lisp编程入门(文本org)

    Programming in Emacs Lisp: An Introduction (美)Robert J.Chassell 著 毛文涛、吕芳 译 洪峰 审校 本书的作者罗伯特·卡塞尔是自由软件基金会的合创人之一,也是理查德·斯托曼博士青年时期结交的挚友,他...

    GNU Emacs Lisp编程入门

    GNU Emacs Lisp编程入门(清晰版) 英文名:An Introduction to Programming in Emacs Lisp

    Atom-language-emacs-lisp,emacs lisp和yasnippet支持atom和github。.zip

    在“Atom-language-emacs-lisp.zip”这个压缩包中,主要关注的是对Emacs Lisp的支持。Emacs Lisp是一种用于扩展和定制GNU Emacs编辑器的Lisp方言。它提供了丰富的API和交互式编程环境,使得开发者可以编写自定义的...

    Programming-in-Emacs-LISP.pdf.rar_LISP PDF_emacs lisp_emacs lisp

    GNU emacs Lisp manual This is a very interesting text, useful to write and program in several languages. Emacs is a editor made in Lisp, a artificial intelligence language.

    GNU EMACS lisp编程入门.djvu

    GNU EMACS lisp编程入门.djvuGNU EMACS lisp编程入门.djvuGNU EMACS lisp编程入门.djvuGNU EMACS lisp编程入门.djvuGNU EMACS lisp编程入门.djvuGNU EMACS lisp编程入门.djvuGNU EMACS lisp编程入门.djvu

    gnu emacs lisp 编程入门

    本书的英文版原名是《Programming in Emacs Lisp: An Introduction》,由自由软件基金会出版,享有复制与分发的权限。机械工业出版社在2001年5月出版了中文版,ISBN为7-111-08862-6。书籍的定价为38.00元,并承诺如...

    GNU Emacs Lisp Reference Manual For Emacs Version 22.1 Revision 2.9, April 2007

    ### GNU Emacs Lisp 参考手册知识点总结 #### 1. 引言 GNU Emacs Lisp 参考手册为Emacs版本22.1提供了详细的指导,该版本为修订版2.9,发布于2007年4月。该手册由Bil Lewis、Dan LaLiberte、Richard Stallman以及...

    GNU Emacs Lisp 编程入门

    gnu emacs lisp 编程指南, 很好的一本书

    emacs-lisp编程入门

    Emacs Lisp(简称Elisp)是GNU Emacs编辑器的核心编程语言,它允许用户自定义、扩展和控制这个功能丰富的文本编辑器。作为一个强大的脚本语言,Elisp在程序员和Emacs爱好者中广受欢迎,不仅适合初学者,也满足高级...

    emacs lisp函数手册,版本24.3

    #### 四十、准备Lisp代码发布 - **如何打包和分发Elisp代码**。 - **版本控制与文档的编写**。 - **代码审查与测试**。 #### 附录 - **Emacs 23反新闻**:Emacs 23版本的一些已知问题和改进。 - **GNU自由文档许可...

Global site tag (gtag.js) - Google Analytics