- 浏览: 236980 次
文章分类
最新评论
-
sunyukun8888:
多谢啦!
重新整理后的Oracle OAF学习笔记——离线版本 -
singlespider:
很不错啊,谢楼主
重新整理后的Oracle OAF学习笔记——离线版本 -
000fuli:
000fuli 写道请问各位学长:你们可以下载吗?能下载的烦请 ...
重新整理后的Oracle OAF学习笔记——离线版本 -
000fuli:
请问各位学长:你们可以下载吗?能下载的烦请发一份到fuli.w ...
重新整理后的Oracle OAF学习笔记——离线版本 -
goodscript:
确实是不错的好文章!
重新整理后的Oracle OAF学习笔记——离线版本
计数:重复和正则表达式
重复执行和正则表达式是Emacs Lisp中非常强大的工具。这章讲解使用while循环和递归结合正则表达式进行查找进行字数统计。
字数统计
标准的Emacs发行版中包含了一个统计region中行数的函数。但没有统计字数的函数。
count-words-region 函数
字数统计函数可以统计行、段落、region、或者整个缓冲区。到覆盖范围该多大?Emacs的鼓励使用弹性的方式。可以将函数设计为处理region。这样即使需要统计整个缓冲区,也可以先用C-x h(mark-whole-buffer)先选定整个缓冲区。
统计字数是一个重复的动作:从region的开始位置,开始统计第一个词,然后是第二个,然后第三个,如此继续直到缓冲区的结束位置。这意味着单词统计的工作适合于使用递归或者while循环。
设计count-words-region函数
首先,我们将使用while循环实现单词统计,然后是递归。当然,这个命令需要交互。
交互式函数定义如下:
(defun name-of-function (argument-list)
"documentation..."
(interactive-expression...)
body...)
我们所要做的就是填空。
函数名应该是自描述的与已存在的count-lines-region类似。这可以让命令名容易被记住。count-words-region是一个较好的名称。
这个函数统计region中的字数。这说明参数列表中需要两个符号,分别绑定到region的开始位置和结束位置。这两个位置可以被称为 beginning和end。文档字符串的第一行必须是一个完整的句子,因为有些命令将只打印文档的第一行,比如apropos命令。交互式语句 (interactive "r")将把缓冲区开始位置和结束位置放到参数列表中。
函数体需要完成三个任务:第一,设置条件,在这个条件下while循环可以统计字数。第二,执行while循环。第三,向用户显示信息。
当用户调用count-words-region时point可能位于region的开始位置或结束位置。但是,计数处理只能从region的开始 位置到结束位置计数。这意味着如果point没有在region的开始位置,则我们需要将point设置到region的开始位置,执行(goto- char beginning)。为了保证在函数执行完后,point可以恢复原来的位置,将需要用到save-excursion语句。
函数体的中心部分是由一个while循环组成,它内部有一个每次向前跳转一个单词的语句,另一个语句负责计数。while语句的true-or-false-test应该在point达到region结束位置时返回false,在此之前返回true。
我们可以使用(forward-word 1)作为向前移动一个单词的语句,如果我们使用正则表达式搜索就很容易明白Emacs中对于'word'的界定。
通过一个正则表达式查找到那个位置并把point设置在最后一个字符的后面。这表示成功的向前移了一个单词。
实际上还有一个问题,我们需要这个正则表达式跳过单词间的空格和标点符号。这表明正则表达式需要能匹配单词后面的空白和标点符号。(一个单词后面也可能没有空白和标点,因此正则表达式的这一部分应该是可选的)
因此,我们需要的正则表达式,要能匹配一个或多个构词字符(能构成单词的字符),后面跟一个可选的由一个或多个非构词字符(不能用于构成单词的字符)。正则表达式如下:
\w+\W*
缓冲区的语法表决定了哪些是构词字符。
查找语句如下:
(re-search-forward "\\w+\\W*")
(注意w和W前面的双斜线。单个斜线对于Emacs Lisp解释器来说有特殊意义。它表明后面一个字符需要不同的处理。比如,\n
表示换行。两个斜线表示斜线)
我们还需要一个计数器用于计数;这个变量初始时必须为0,然后在每次执行while循环体时增加。这个语句如下:
(setq count (1+ count))
最后我们需要告诉用户region中有多少个字符。message函数用于向用户显示信息。显示信息只需要一个短语,我们并不需要很复杂。到底是简 单还是复杂。我们可以用一个条件语句来解决定个问题。共有三种可能:region中没有单词,region只有一个单词,或者有多个单词。这时crond 比较合适。
初步的函数定义如下:
;;; First version; has bugs!这个函数能够工作,但并不是在所有的情况下。
(defun count-words-region (beginning end)
"Print number of words in the region.
Words are defined as at least one word-constituent
character followed by at least one character that
is not a word-constituent. The buffer's syntax
table determines which characters these are."
(interactive "r")
(message "Counting words in region ... ")
;;; 1. Set up appropriate conditions.
(save-excursion
(goto-char beginning)
(let ((count 0))
;;; 2. Run the while loop.
(while (< (point) end)
(re-search-forward "\\w+\\W*")
(setq count (1+ count)))
;;; 3. Send a message to the user.
(cond ((zerop count)
(message
"The region does NOT have any words."))
((= 1 count)
(message
"The region has 1 word."))
(t
(message
"The region has %d words." count))))))
count-words-region函数中空白处理的Bug
前面描述的count-words-region命令有两个Bug,或者说一个Bug的两个表现。首先,如果 region中只在某些文本间有空白,count-words-region命令将告诉你region中只包含了一个单词。第二,如果region中只有 缓冲区结束位置或者narrowed缓冲区的可访问域的结束位置有空白,命令在执行时将显示错误信息:
Search failed: "\\w+\\W*"
可以在Emacs中先安装这个函数,然后将它绑定到按键上:
(global-set-key "\C-c=" 'count-words-region)可以在设置region后按
C-c =
执行(如果没有绑定按键,可以用M-x count-words-region执行)。
对下面的内容执行时Emacs将告诉你,region有3个单词。
one two three
如果把mark设置在这行的开头位置,point放在one
的前面。重新执行C-c =
。Emacs应该要告诉你region中没有单词,因为region只有空白。但是,Emacs告诉你region中只有一个单词。
第三个测试,复制上面例的整行到*scratch*缓冲区中并在行的结束位置输入一些空格。将mark设置在单词three
的后面,然后point设置在行的结束位置(在这里即缓冲区的结束位置)。输入C-c =
。这次Emacs应该告诉你region中没有单词。但是Emacs这次却显示了一个错误信息Search failed
。
这两个bug来自于同一个问题。
思考这个Bug的第一个表现,命令告诉你行的开始位置的空白包含一个单词。它是这样产生的:count-words-region命令先将 point移到region的开始位置。然后测试当前point的位置是否小于end变量的值。结果为true。接下来,通过表达式查找第一个单词。它将 point设置在第一个单词的后面。count被设置为1。while循环重复,但这时point已经大于end的值了,循环退出;函数显示信息说在 region中有一个单词。简单来说就是由于正则表达式查询时,它查找到的单词的结束位置超过了region的区域。
Bug的第二个表现中,region是缓冲区结束位置的空白。Emacs说Search failed。这是由于在while的true-or-false-test返回true,search语句被执行。但是由于没找到匹配项,因此查询失败。
这两种情况都是由于查询时扩展或者试图扩展到region的外部。
解决办法就是限制查询的区域,一个很简单的动作,但并没有想像的那么简单。
前面在讲re-search-forward函数时,它接收四个参数。第一个参数是必需的,其它三个是可选参数。它的第二个参数是用于限定查询范围 的。第三个可选参数,如果为t,则函数将在查询失败时返回nil,而不显示错误信息。第四个可选参数是重复次数。(可以用C-h f查找函数的文档)
在count-words-region函数定义中,region的结束位置被以设置到end参数上,它将作为函数参数传入。因此我们可以把end作为正则表达式查询时的参数。
(re-search-forward "\\w+\\W*" end)如果只对count-words-region的定义作上面的修改,在遇到一些空白字符时,仍将得到Search failed的错误。
这是因为,有可能在限制的范围内,搜索不到构词字符。搜索将失败,并显示错误信息。但我们在这时并不想要获取错误信息,我们需要显示"The region does NOT have any words."。
解决这一问题的办法就是将re-search-forward的第三个参数设置为t,这样在函数在搜索失败时将返回nil。
如果你尝试运行程序,你将看到信息"Couting words in region..."并一直看到这条消息,直到你输入C-g(keyboard-quit)。
当在限制查询范围的region中搜索时,和前面一样,如果region中没有构词字符,搜索将失败。re-search-forward语句返回 nil。这时point也不会被移动,而循环中的下一条语句将被执行。这条语句将计数增加。然后循环继续。true-or-false-test将一直返 回true,因为point仍小于end参数,程序将陷入死循环。
count-words-region的定义还需要一些修改,以便在搜索失败时让true-or-false-test返回false。可以在 true-or-false-test中增加一个条件,true-or-false-test在增加计数前需要满足下面的条件:point必须在 region之内,且查询的语句必须找到了一个单词。
因为两个条件都必须为true。所以区域范围检查和搜索语句可以用and连接起来,都作为while循环的true-or-false-test:
(and (< (point) end) (re-search-forward "\\w+\\W*" end t))re-search-forward在成功搜索到单词后将返回t,并移动point,只要能找到单词,point将继续移动。当搜索失败或者point达 到region的结束位置时,true-or-false-test将返回false。while循环退出,count-words-region函数显 示一个或多个信息。
修改完后的count-words-region函数如下:
;;; Final version: while
(defun count-words-region (beginning end)
"Print number of words in the region."
(interactive "r")
(message "Counting words in region ... ")
;;; 1. Set up appropriate conditions.
(save-excursion
(let ((count 0))
(goto-char beginning)
;;; 2. Run the while loop.
(while (and (< (point) end)
(re-search-forward "\\w+\\W*" end t))
(setq count (1+ count)))
;;; 3. Send a message to the user.
(cond ((zerop count)
(message
"The region does NOT have any words."))
((= 1 count)
(message
"The region has 1 word."))
(t
(message
"The region has %d words." count))))))
递归方式统计单词数量
上一节已经编写过了通过while循环进行计数的函数。
在这个函数中,count-words-region函数完成了三个工作:为计数设置适当的条件;计算region中的字数;将字数显示给用户。
如果我们在一个递归函数中执行所有的操作,则我们将在每次递归调用时都会得到字数的消息。如果region中包含了13个单词,消息将显示13次。 这并不是我们需要的,我们需要写两个函数来做这个工作,一个函数(递归函数)将在另一个函数内部被使用。一个设置条件和显示信息,国一个返回字数。
开始编写函数。我们仍把这个函数叫作count-words-region。
根据前一个版本,我们可以描述出这个程序的结构:
;; Recursive version; uses regular expression search定义很直接,不同的地方是递返回的数字必须传递给message来显示。这可以用let语句来完成:我们可以用let语句把字数赋给一个变量,并把这个值作为递归部分的返回值。使用cond语句,用于设置变量和显示信息给用户。
(defun count-words-region (beginning end)
"documentation..."
(interactive-expression...)
;;; 1. Set up appropriate conditions.
(explanatory message)
(set-up functions...
;;; 2. Count the words.
recursive call
;;; 3. Send a message to the user.
message providing word count))
通常let语句总被作为函数的'次要工作'。但在这里,let将作为函数的主要工作,统计字数的工作就是在let语句中。
使用let时函数定义如下:
(defun count-words-region (beginning end)
"Print number of words in the region."
(interactive "r")
;;; 1. Set up appropriate conditions.
(message "Counting words in region ... ")
(save-excursion
(goto-char beginning)
;;; 2. Count the words.
(let ((count (recursive-count-words end)))
;;; 3. Send a message to the user.
(cond ((zerop count)
(message
"The region does NOT have any words."))
((= 1 count)
(message
"The region has 1 word."))
(t
(message
"The region has %d words." count))))))
接下来我们需要编写递归计数函数。
递归函数至少有三个部分:'do-again-test','next-step-expresssion'和递归调用。
do-again-test决定函数是否继续调用。因为我们在统计region中的单词时我们使用了移动point的函数,do-again- test可以检查point是否位于region中。do-again-test需要检查point是位于region结束位置的前面还是后面。我们可以 使用point函数获取point的位置信息,我们还需要传递将region的结束位置作为参数传递到递归计数函数里。
另外,do-again-test还需要检查是否找到了一个单词。如果没有,函数就不再需要继续调用它自己了。
next-step-expression修改某个值以便递归函数能在适当的时候停止递归调用。在这里next-step-expression可以是移动point的语句。
递归函数的第三个部分是递归调用。
在这个函数中我们也需要在某个地方执行计数工作。
这样,我们有了一个递归计数函数的原型:
(defun recursive-count-words (region-end)现在我们需要填空。首先我们从最简单的一种情况开始:point位于region结束位置或位于region之外,region中没有单词,因此函数需要返回0。同样,如果搜索失败,函数也需要返回0。
"documentation..."
do-again-test
next-step-expression
recursive call)
另一方面,如果point在region内部,并且搜索成功,函数应该再次调用它自己。
这样,do-again-test应该如下:
(and (< (point) region-end)注意,查找语句是do-again-test函数的一部分,在搜索成功时返回t,失败时返回nil。
(re-search-forward "\\w+\\W*" region-end t))
do-again-test是if语句的true-or-false子句。如果do-again-test成功,则if语句的then部分执行,如果失败,则应该返回0,因为不管point是位于region的外面还是搜索失败都表示region中没有单词。
另外,do-again-test返回t或nil时,re-search-forward将在搜索成功时移动point。这是修改point的值并 让递归函数在point移出region后停止递归调用的操作。因此,re-earch-foreard语句就是next-step- expression。
recursive-count-words函数如下:
(if do-again-test-and-next-step-combined
;; then
recursive-call-returning-count
;; else
return-zero)
怎样加入计数机制呢?
我们知道计数机制应该与递归调用联合起来。由于next-step-expression将point一个个单词的移动,因此,针对每个单词都会调用一次递归函数,计数机制必须有一个语句将recursive-count-words的返回值加1。
思考下面几种情况:
- 如果region中有两个单词,函数在遇到第一个单词时,需要返回region中其它单词数量(这里为1)加1的值。
- 如果region中只有一个单词,函数在遇到第一个单词时,需要返回region中其它单词数量(这里为0)加1的值。
- 如果region中没有单词,函数需要返回0。
从上面的描述中可以看出if语句的else部分在没有单词时返回0。而if语句的then部分必须返回1加上region中其它单词数量的值。
语句如下,使用了函数1+使它的参数加1。
(1+ (recursive-count-words region-end))整个recursive-count-words函数如下:
(defun recursive-count-words (region-end)
"documentation..."
;;; 1. do-again-test
(if (and (< (point) region-end)
(re-search-forward "\\w+\\W*" region-end t))
;;; 2. then-part: the recursive call
(1+ (recursive-count-words region-end))
;;; 3. else-part
0))
研究一下它是如何工作的:
当region中没有单词时,if语句的else部分被执行,函数返回0。
如果region中有一个单词,point的值小于region-end并且搜索成功。这时,if语句的true-or-false-test为true,if语句的then部分被执行。计数语句被执行。这个语句将返回(整个函数的返回值)递归调用的返回值加1的结果。
与此同时,next-step-expression将使point跳过region中的第一个单词。这表示当(recursive-count- words region-end)在第二次时被执行,并作为递归调用的结果,point的值将等于或大于region的结束位置。这样,recursive- count-words将返回0。最初的recursive-count-words将返回0+1,计数正确。
如果region中有两个单词,第一次调用recursive-count-words将返回1加上在包含其它单词的region上调用recursive-count-words的返回值,这里将是1加1,2是正确的返回值。
类似地,如果region中包含有3个单词,第一次调用recursive-count-words将返回1加上在包含其它单词的region上调用recursive-count-words的返回值,如此继继续。
整个程序包含了两个函数:
递归函数:
(defun recursive-count-words (region-end)
"Number of words between point and REGION-END."
;;; 1. do-again-test
(if (and (< (point) region-end)
(re-search-forward "\\w+\\W*" region-end t))
;;; 2. then-part: the recursive call
(1+ (recursive-count-words region-end))
;;; 3. else-part
0))
包装函数:
;;; Recursive version
(defun count-words-region (beginning end)
"Print number of words in the region.
Words are defined as at least one word-constituent
character followed by at least one character that is
not a word-constituent. The buffer's syntax table
determines which characters these are."
(interactive "r")
(message "Counting words in region ... ")
(save-excursion
(goto-char beginning)
(let ((count (recursive-count-words end)))
(cond ((zerop count)
(message
"The region does NOT have any words."))
((= 1 count)
(message "The region has 1 word."))
(t
(message
"The region has %d words." count))))))
发表评论
-
emacs中使用semantic实现c代码自动完成功能
2008-11-25 16:29 9852环境: windows xp emacs 23 自已编译的cv ... -
Emacs Lisp中的hash table
2008-03-10 16:30 2307(defun zj-hash-test () "h ... -
Emacs Lisp与Shell的交互
2008-03-10 16:27 4569一直以来对于w3m、tramp、dired等与shell关系 ... -
Programming in Emacs Lisp笔记(十八) 终结
2007-07-20 11:34 2722笔记连载完毕。感谢大家的支持! 离线版本可以从这里下载。 -
Programming in Emacs Lisp笔记(十七) 调试
2007-07-20 11:11 5459调试 GNU Emacs中有两个高度器,debug和edeb ... -
Programming in Emacs Lisp笔记(十六).emacs文件
2007-07-20 11:10 6539.emacs文件 Emacs的缺省 ... -
Programming in Emacs Lisp笔记(十五)准备图表
2007-07-19 16:36 2419准备图表 我们的目标 ... -
Programming in Emacs Lisp笔记(十四)统计defun中的单词数量
2007-07-19 16:36 2880统计defun中的单词数量 我们的下一个计划是统计函数定义中 ... -
Programming in Emacs Lisp笔记(十二)正则表达式查询
2007-07-19 16:26 4572正则表达式查询 在Emacs中正则表达式查询使用得很广泛。在 ... -
Programming in Emacs Lisp笔记(十一)循环和递归
2007-07-04 18:18 3596循环和递归 Emacs Lisp有 ... -
Programming in Emacs Lisp笔记(十)Yanking Text Back
2007-07-04 17:59 2980Yanking Text Back 当使用'kill'命令剪 ... -
Programming in Emacs Lisp笔记的离线版本(2007年7月20日更新,完整版)
2007-07-03 15:45 5247使用muse生成了这个笔记的html版本。里面有带链接的索引, ... -
Programming in Emacs Lisp笔记(九)List的实现
2007-07-03 14:20 2219List的实现 Lisp中list使 ... -
Programming in Emacs Lisp笔记(八)剪切和存储文本
2007-07-02 12:04 2725剪切和存储文本 当使用'kill'命令剪切文本时,Emacs ... -
Programming in Emacs Lisp笔记(七)基础函数:car, cdr, cons
2007-06-29 10:09 3819基础函数:car, cdr, cons Lisp中car,c ... -
Programming in Emacs Lisp笔记(六) Narrowing and Widening
2007-06-28 10:41 2336Narrowing and Widening Narrowi ... -
Programming in Emacs Lisp笔记(五)一些更复杂的函数
2007-06-27 13:04 2726一些更复杂的函数 copy-to-buffer的函数定义 ... -
Programming in Emacs Lisp笔记(四)与缓冲区有关的函数
2007-06-26 13:38 3193部分与缓冲区有关的函数 查找更多信息 可以通过C-h f查看 ... -
Programming in Emacs Lisp笔记(三)编写函数
2007-06-25 15:01 4172编写函数 关于基本函数 ... -
Programming in Emacs Lisp笔记(二)实践
2007-06-25 15:01 2435实践 执行代码 通过C-x C-e执行代码 缓冲区名称 b ...
相关推荐
- **计数:重复与正则表达式(Counting: Repetition and Regexps)**:通过示例展示了如何结合循环和正则表达式来计算某些模式出现的次数。 ##### 16. 计数单词数量(Counting Words in a defun) - **计数单词数量...
13. **计数:重复和正则表达式(Counting: Repetition and Regexps)**:结合正则表达式和循环结构来解决问题。 14. **在defun中计数单词(Counting Words in a defun)**:通过实例展示了如何统计函数体中的单词...
Programming in Emacs Lisp英文版
综上所述,《An Introduction to Programming in Emacs Lisp》一书全面介绍了Emacs Lisp的基础知识和核心概念,适合初学者学习和掌握Emacs Lisp编程技巧。通过深入理解上述知识点,读者能够更好地利用Emacs Lisp进行...
9. **实例应用**:正则表达式广泛应用于文本编辑器(如vim、emacs)、编程语言(如JavaScript、Python、Java)和搜索引擎(如grep、findstr)。例如,用于验证邮箱格式、手机号码、提取URL等。 通过《精通正则...
### Python正则表达式操作指南知识点详解 #### 1. 简介 - **re模块**: Python自1.5版本起引入了`re`模块,该模块支持Perl风格的正则表达式模式。与之前的`regex`模块提供的Emacs风格相比,`re`模块更加功能强大且...
1. 正则表达式的引擎:正则表达式的引擎已经被许多 Unix 工具实现,包括 grep、awk、vi 和 Emacs 等。许多脚本语言也支持正则表达式,例如 Python、Tcl、JavaScript 和 Perl。 2. Java 正则表达式的历史:Java 一直...
正则表达式的概念始于文本编辑器,如vi和emacs,后来被广泛应用于各种编程语言和工具中,如Perl、Python、JavaScript、Java等。它们在数据验证、搜索替换、文件查找、网页爬虫等领域有着广泛的应用。 1. **基础概念...
#### 三、正则表达式的重复与分组 - **重复**: - `*`(零次或多次重复):如`a*`表示匹配`a`的零次或多次重复。 - `+`(一次或多次重复):如`a+`表示至少匹配一次`a`。 - `{n}`(指定重复次数):如`a{2}`表示...
正则表达式支持的工具众多,包括但不限于grep、awk、vi和Emacs文本编辑器。脚本语言如Python、Tcl、JavaScript和Perl也都支持正则表达式,其中Perl在文本处理方面的应用尤为出色。 在Java语言中,正则表达式的支持...
An Introduction to Programming in Emacs Lisp [3.10].chm
- 文本编辑器:如Vim、Emacs等,提供了强大的正则表达式搜索和替换功能。 - 命令行工具:如Unix/Linux的grep、sed、awk等,利用正则表达式处理文本文件。 四、正则表达式实例 1. 验证邮箱格式:`/^[a-zA-Z0-9._%+-...
早期版本的Python使用的是`regex`模块,该模块支持Emacs风格的正则表达式,但在Python 2.5之后已被完全移除。 #### 二、简单的模式 ##### 2.1 匹配字符 在正则表达式中,最基本的单元是单个字符的匹配。例如: - ...
- 在文本编辑器(如vim、emacs)中,正则表达式常用于查找、替换和多行操作。 - 在Web开发中,正则表达式用于表单验证,确保用户输入的数据格式正确。 9. **效率优化** - 理解并避免回溯,合理使用贪婪与非贪婪...
正则表达式是一种强大的文本处理工具,用于匹配、查找、替换和分析字符串模式。它在编程、数据分析、网页抓取等多个领域都有广泛应用。这本"正则表达式书籍"很可能包含以下关键知识点: 1. **基础概念**:正则...
- **regex模块**:Python 1.5 之前的版本提供的 Emacs 风格的正则表达式模式,功能较少且可读性较差,新代码建议避免使用。 #### 2. 简单模式 ##### 2.1 字符匹配 - **普通字符匹配**:大多数字母和数字等普通...
正则表达式是一种文本模式,包括普通字符(例如,字母和数字)和特殊字符(称为“元字符”)。它使用单个字符串来描述、匹配一系列符合某个句法规则的字符串。在Shell脚本中,正则表达式可以与grep、sed、awk等工具...
Emacs样式的正则表达式(模块Re.Emacs ); Shell样式的文件Re.Glob (模块Re.Glob )。 也可以通过组合更简单的正则表达式(模块Re )来构建正则表达式。 最显着的缺失功能是反向引用和先行/后向断言。 Re.Pcre...