Kinney
21
字数统计我用的这个函数:
;; count words
(defvar wc-regexp-chinese-char-and-punc
(rx (category chinese)))
(defvar wc-regexp-chinese-punc
"[。,!?;:「」『』()、【】《》〈〉※—]")
(defvar wc-regexp-english-word
"[a-zA-Z0-9-]+")
(defun geekblog-word-count ()
"「較精確地」統計中/日/英文字數。
- 文章中的註解不算在字數內。
- 平假名與片假名亦包含在「中日文字數」內,每個平/片假名都算單獨一個字(但片假
名不含連音「ー」)。
- 英文只計算「單字數」,不含標點。
- 韓文不包含在內。
※計算標準太多種了,例如英文標點是否算入、以及可能有不太常用的標點符號沒算入等
。且中日文標點的計算標準要看 Emacs 如何定義特殊標點符號如ヴァランタン・アルカン
中間的點也被 Emacs 算為一個字而不是標點符號。"
(interactive)
(let* ((v-buffer-string
(progn
(if (eq major-mode 'org-mode) ; 去掉 org 文件的 OPTIONS(以#+開頭)
(setq v-buffer-string (replace-regexp-in-string "^#\\+.+" ""
(buffer-substring-no-properties (point-min) (point-max))))
(setq v-buffer-string (buffer-substring-no-properties (point-min) (point-max))))
(replace-regexp-in-string (format "^ *%s *.+" comment-start) "" v-buffer-string)))
; 把註解行刪掉(不把註解算進字數)。
(chinese-char-and-punc 0)
(chinese-punc 0)
(english-word 0)
(chinese-char 0))
(with-temp-buffer
(insert v-buffer-string)
(goto-char (point-min))
;; 中文(含標點、片假名)
(while (re-search-forward wc-regexp-chinese-char-and-punc nil :no-error)
(setq chinese-char-and-punc (1+ chinese-char-and-punc)))
;; 中文標點符號
(goto-char (point-min))
(while (re-search-forward wc-regexp-chinese-punc nil :no-error)
(setq chinese-punc (1+ chinese-punc)))
;; 英文字數(不含標點)
(goto-char (point-min))
(while (re-search-forward wc-regexp-english-word nil :no-error)
(setq english-word (1+ english-word))))
(setq chinese-char (- chinese-char-and-punc chinese-punc))
(+ chinese-char english-word)))
其他的感觉用到的比较少,反正在emacs中总有办法解决。
3 个赞
是啊,@kinney 大佬说过 emacs没啥不能的。
把emacs组装成一个文秘编辑,比现在都用word好多了,也符合当前国产软件替代的大环境。
(setq repl-list '((“old” “new”)
("." "。") ;中文标点替換
(" " "") ;刪除空格
("," ","))) ;中文标点替換
第一個.和。替換時,會將所有字替換成。。。。
原因應該是把「.」當作通配符了,測試了「.」,但問題沒有解決,請問如何替換通配符呢?謝謝!
Kinney
26
因为我用的是 re-search-forward
函数进行搜索匹配,匹配的正则表达式,不是纯字符串。在正则中点号需要转义,正确的写法是 "\\."
。我应该把 replall写成两个函数 replall-string
和 replall-regexp
, 这样更清晰。
Kinney
27
我修改了一下,现在使用 replall-string
函数可以直接匹配字符串,不用考虑正则特殊字符。加了一个 replall-regexp
函数可以匹配正则,不过只能设置 repl-regexp-list
变量。文件中的值只适用于纯字符串匹配。代码在 这里。
1 个赞
多数句子能看懂了,这两句最长的能用汉语翻译一下吗?谢谢!
(setq repl-pair (split-string (thing-at-point 'line) “[ \f\t\n\r\v]+” t “[ \f\t\n\r\v]+”))
和
(setq repl-list (append repl-list (list repl-pair)))
Demos
#+BEGIN_SRC elisp
(split-string “The quick brown fox.” " +")
#+END_SRC
#+RESULTS:
: (“The” “quick” “brown” “fox.”)
例子很簡單,都是空格分割的,這樣也行啊。爲何用[ \f\t\n\r\v]這樣好怪的正則表達式啊。
Kinney
32
这个正则考虑的情况更全面吧,因为你不能保证中间的一定是空格,可能是一个tab,或其他制表符。你写的也够用了,不过写全一点不更好吗。
1 个赞
这几天学习了lisp的基本语法,模仿着上面的程序,第一次写一个程序。
想达成的效果是:把一个ceshi.yaml文件拆分成若干个.org文件。
ceshi.yaml是这样的:
日: # No.1
字源: 象形-甲骨文
字意: [體]太陽。
組合字:
昌: # No.2
字源: 象形-甲骨文
字意: [用]說太陽之光,明亮,光大,美好。
根据有「#」的行,分解为 日 .org 昌.org等等
(setq new-file new-file-title new-file-cont) ;新文件、文件名、文件内容
(defun mydo-yaml-file (file)
(interactive "fchoose a file to do:") ;;ceshi.yaml
(with-temp-buffer
(insert-file-contents file)
(goto-char (point-min))
(while (< (point) (point-max))
(if (search-forward "#" nil t)
(progn
(beginning-of-line)
(setq new-file-title (current-word))
(next-line)
(region-beginning))
(next-line))
(region-end)
(setq new-file-cont(buffer-substring-no-properties (region-beginning) (region-end)))
(find-file (concat "~/org/" new-file-title ".org"))
(with-output-to-temp-buffer
(insert new-file-title "\n" new-file-cont)
(write-file new-file)))))
错误提示说:没有定义mark,但我想这只是一个小错误,可能上面的结构都有问题,但不知道如何改。请大佬们不吝赐教,谢谢!
Kinney
34
这种实际问题的难点不在于如何写代码,而在于解决问题的思路和算法。在写代码之前要先把每一步需要做什么想清楚,不能只是大概知道。
我看了你的代码,大概的思路是没有问题的,但是其中的逻辑没有想清楚。
逻辑上的问题:根据的你的代码逻辑,搜索到"#"后读取文件名,然后移到下一行的开头,然后就开始写文件了?
有一些代码上的问题:
-
局部变量用let绑定,不要写在函数外面。
-
search-forward的第二个参数为nil时表示在整个buffer搜索,这里需要的应该是在一行内搜索,使用(line-end-position)
-
(region-beginning)和(region-end)是用来获取region的beg和end的position的,一般它赋值给变量,单独写没有意义。建议不要用region,因为从代码看出你对它的概念不是很清晰。
-
find-file是交互函数,一般不在代码中使用,写文件用with-temp-file或write-file。
我的代码:
(defun my-split-yaml-file (file)
(interactive "fchoose a file: ")
(let ((filedir "~/test-dir/")
filename beg end content) ;; 绑定局部变量
(with-current-buffer (get-buffer-create "*split-yaml*")
(insert-file-contents file)
(goto-char (point-min))
(while (< (point) (point-max))
(if (search-forward "#" (line-end-position) t) ;; 在一行内搜索 "#"
(progn
(when filename
(setq content
(string-join
(split-string (buffer-substring-no-properties beg end)
"[\n]+" t "[ \f\t\n\r\v]+")
"\n")) ;; 获取beg和end之间的内容,去掉每行首部的空格。
(with-temp-file (concat filedir filename ".org")
(insert content))) ;; 写文件
(setq filename (current-word))
(next-line)
(beginning-of-line)
(setq beg (point))) ;; 获取内容的起始位置beg
(save-excursion ;; 保存光标的位置:只判断下一行的情况,不改变实际要操作的光标。
(next-line)
(beginning-of-line)
(when (or (search-forward "#" (line-end-position) t)
(= (point) (point-max)))
(previous-line)
(end-of-line)
(setq end (point)))) ;; 如果当前行的下一行能够搜索到# 或 下一行到达buffer的尾部(最后一块的情形),则将当前行的结尾位置设为内容的end位置。
(next-line)
(beginning-of-line))))))
3 个赞
学习了一下午,有两个疑问,一个不懂。
疑问的是
(setq filename (current-word))
这句写在后面,为何在 (when filename
这句程序就知道filename是什么呢?这个问题真是小白了。
还有一个疑问:说两句话就能搞懂的事情,为何用那么多行语句才能让电脑明白且执行。
不懂的还是正则表达式,这个以后再系统学一下。
非常感谢!
Kinney
36
因为filename在let中绑定了。let绑定的局部变量在let块内都可以访问到。
第二个问题,哈哈有意思 。
说说我的理解:因为它是电脑不是人。本质上说,电脑硬件只能理解0和1(高低电平)的区别,通过0,1的组合构成了指令系统,在指令系统上有了更容易理解的汇编语言,高级语言更加接近人的逻辑思维。所以我们应该庆幸有高级语言的存在,不然程序员得回到写汇编的年代,甚至是01代码。再说lisp,实现同等的功能,lisp的代码量要比现在主流的高级语言还要简洁。
再说说函数式编程:把小的功能写成函数,然后就可以在其他的函数里面使用,这是函数式编程。你说的几句话搞定这个问题也不是不可以实现,前提是对于特定的功能有现成的函数实现。但问题是,对于不具有通用性的功能,lisp不可能有现成的函数,所以需要我们用通用的函数来实现这个功能。一旦实现了,我们就可以说:这个问题现在我可以用一行代码来解决,就是直接调用这个函数。同理,更复杂的功能也可以复用我们实现好的函数。
所谓通用的功能就是elisp内置的函数,api。不通用的功能就是我们个性化的需求,比如说你的这个问题。
有点啰嗦,希望对你的理解有帮助。
另外,这个问题里的正则不是重点,只是为了去掉每块内容行首的空格。这是split-string的功能,和之前代码里的是一样的。
是啊,有了这段函数,这个问题就能用一句语句解决了,解决的次数多了,函数也就越来越多,越来越简单了。
文笔真棒,听说文本好的人水平更高,我也在重新学习中文,向你学习。
继续折腾lisp,今天搞定一个正则表达匹配,感觉不错。接着是文本的行操作,学习了李杀网站上的相关内容,粗浅感觉elisp行操作方面的功能比较弱,也许是跟vim相比吧。
想达成的目标:把匹配“某汉字”的行,挪到文件的第二行。代码如下:
(with-temp-buffer
(insert-file-contents file)
(goto-char (point-min))
(search-forward "某汉字" nil t)
(goto-char (point))
;; (evil-delete)
(kill-whole-line)
(goto-char (point-min))
;;(evil-paste-pop)
(insert ?\n)
;; (yank)
(evil-paste-after)
(write-file file))
问题:
1,kill-whole-line 可以删除当前行,但evil-delete evil-paste-after无法删除和粘贴,evil的命令好像在这里不起作用,用evil的vim的命令更方便一些;
2,yank 和yank-pop会粘贴数据,但是好像yank把kill-ring的数据都保存粘贴了。如何能只粘贴kill-whole-line删除的行呢?
请指教!谢谢!
Kinney
39
1.不要用evil,能用elisp内置的函数就不要用第三方库。这里evil函数不起作用是因为evil函数要在evil-mode为major-mode时才能用。
2.elisp确实没有太多直接操作行的内置函数。point,mark和region几乎可以解决所有buffer内文字的处理,行本质上也是一种region,而且关于行的操作也没多少。
3.(yank)
默认粘贴的就是最近一次的kill操作,所以是能够只粘贴(kill-whole-line)
的内容的。
(defun my-move-matched-text-line (file)
"move string match line to the second line."
(interactive "fchoose a file: ")
(let ((line 2)
(str "某汉字"))
(with-temp-file file
(insert-file-contents file)
(goto-char (point-min))
(search-forward str nil t)
(kill-whole-line)
(goto-line line)
(yank))))
另外,论坛回复是支持markdown语法的。你把代码放在代码块里好看一点。
Kinney
40
针对这个问题写了篇文章。其中的思路对理解应该有帮助。