精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2007-07-19
准备图表我们的目标是构造一个图表显示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'等单词。因此,我们只要输入 这个函数文档如下: insert-rectangle: 我们可以测试一下,以确认它是否如我们期望的那样工作。 把光标放在insert-rectange语句的后面按C-u C-x C-e(eval-last-sexp)。这个函数将在point的下面插入"first","second","third"。函数返回值为nil。 (insert-rectangle '("first" "second" "third"))first在绘制图表的程序中使用这上函数。我们需要先确保point位于需要插入的位置,然后用insert-rectangle函数插入列。 如果你是在Info中读取这个文档,你可以切换到另一个缓冲区,比如 我们将发现当执行完成插入后,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.安装这个函数后,执行下面的代码: (column-of-graph 5 3)将返回: (" " " " "*" "*" "*") 如上面所写,column-of-graph包含一个瑕疵:用于标识空白和列的符号是硬编码的,使用了空白和星号。这是一个很好的原型,如果其它人 想换成其它的符号。比如用逗号代替空白,用加号代替星号等。程序应该更具弹性一些。应该使用两个变量来代替空白和星号:将graph-blank和 graph-symbol定义为两个独立的变量。 上面也没有编写文档。我们可以编写这个函数的第二个版本: (defvar graph-symbol "*"如果需要,我们可以再次重写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) 我们需要填空。 我们可以用 while循环遍历numbers-list。并用 每个循环周期中,insert-rectangle函数使用column-of-graph插入list。由于insert-rectangle函 数将point移到了插入的矩形区域的右下解,我们需要保存当前point的位置,在插入矩形区域后恢复point的位置,然后将point水平移动到下 一个列,并再次调用insert-rectangle。 如果被插入的列是一个字符宽(比如星号或一个空格),这个命令比较简单 函数定义如下: (defun graph-body-print (numbers-list) 这里出现了一个新的函数 我们可以使用一个较短的包含数字的list来测试graph-body-print。
(graph-body-print '(1 2 3 4 6 4 3 5 7 6 5 2 3))
Emacs将打印出下面的图表: * recursive-graph-body-print函数graph-body-print函数也可以用递归来编写。递归分解为两个部分:外部使用let包装,决定几个变量的值,比如图表最大高度,内部的函数调用是递归调用,用于打印图表, 包装部分不复杂: (defun recursive-graph-body-print (numbers-list) 递归函数部分有点复杂。它有四个部分:'do-again-test'打印操作的代码,递归调用,'next-step-expression'。 'do-again-test'是一个if语句用于检查numbers-list是否还有元素,如果有函数将使用打印操作的代码打印一个列,并再次调用自 身。函数调用自身时'next-step-expressin'将截短numbers-list。 (defun recursive-graph-body-print-internal 在安装这个函数后,可以用下面的例子测试: (recursive-graph-body-print '(3 2 5 6 7 5 3 4 6 4 3 2 1))结果如下: * 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
浏览 2810 次