我正在尝试编写一个elisp函数来将文件中的每个单词读成一对。我希望该对中的第一项是按字典顺序排序的字符串,而第二项是不受影响的。
给出示例文件:
cat
cow
dog
我希望列表看起来像:
(act cat)
(cow cow)
(dgo dog)
我最好的解决方法是:
(defun get-file (filename)
(with-open-file (stream filename)
(loop for word = (read-line stream nil)
while word
collect ((sort word #'char-lessp) word))))
它在Emacs lisp交互模式下正确编译。但是,当我尝试 通过执行
来运行它(get-file "~/test.txt")
我最终进入了Emacs调试器,并没有告诉我任何有用的东西。 。
Debugger entered--Lisp error: (void-function get-file)
(get-file "~/test.txt")
eval((get-file "~/test.txt") nil)
eval-last-sexp-1(t)
eval-last-sexp(t)
eval-print-last-sexp(nil)
call-interactively(eval-print-last-sexp nil nil)
command-execute(eval-print-last-sexp)
我是一个初学者,并且不知道出了什么问题。
谢谢,
贾斯汀
答案 0 :(得分:3)
首先,让我们只使用Emacs的内置功能。没有内置函数来对Emacs中的字符串进行排序,因此您首先应该将字符串转换为列表,排序,然后将排序后的列表转换回字符串。这就是你convert a string to a list:
的方式(append "cat" nil) ; => (99 97 116)
转换为列表的字符串变为字符列表,并在Elisp中变为characters are represented as numbers。然后,您sort列表和convert it to a string:
(concat (sort (append "cat" nil) '<)) ; => "act"
没有内置函数可以将文件内容直接加载到变量中,但您可以load them加载到temporary buffer。然后你可以return the entire temporary buffer作为字符串:
(with-temp-buffer
(insert-file-contents-literally "file.txt")
(buffer-substring-no-properties (point-min) (point-max))
这将返回字符串"cat\ncow\ndog\n"
,因此您需要split它:
(split-string "cat\ncow\ndog\n") ; => ("cat" "cow" "dog")
现在您需要traverse此列表并将每个项目转换为一对已排序项目和原始项目:
(mapcar (lambda (animal)
(list (concat (sort (append animal nil) '<)) animal))
'("cat" "cow" "dog"))
;; returns
;; (("act" "cat")
;; ("cow" "cow")
;; ("dgo" "dog"))
完整代码:
(mapcar
(lambda (animal)
(list (concat (sort (append animal nil) '<)) animal))
(split-string
(with-temp-buffer
(insert-file-contents-literally "file.txt")
(buffer-substring-no-properties (point-min) (point-max)))))
其中一个Emacs内置软件包是cl.el
,并且没有理由不在您的代码中使用它。因此我撒了谎,当我说没有内置函数来排序字符串时,上面是使用内置函数完成任务的唯一方法。因此,让我们使用cl.el
。
(cl-sort "cat" '<) ; => "act"
cl-mapcar
比Emacs的内置mapcar
功能更多,但您可以使用其中任何一种。
cl-sort
存在问题,它是destructive,这意味着它会就地修改参数。我们在匿名函数中使用局部变量animal
两次,并且我们不想将原始animal
弄乱。因此,我们应该将copy序列传递给它:
(lambda (animal)
(list (cl-sort (copy-sequence animal) '<) animal))
结果代码变为:
(cl-mapcar
(lambda (animal)
(list (cl-sort (copy-sequence animal) '<) animal))
(split-string
(with-temp-buffer
(insert-file-contents-literally "file.txt")
(buffer-substring-no-properties (point-min) (point-max)))))
seq.el
在Emacs 25中添加了一个新的序列操作库seq.el
。 mapcar
的替代方案是seq-map
,替代CL&#39 {s} cl-sort
是seq-sort
。完整的代码变为:
(seq-map
(lambda (animal)
(list (seq-sort animal '<) animal))
(split-string
(with-temp-buffer
(insert-file-contents-literally "file.txt")
(buffer-substring-no-properties (point-min) (point-max)))))
通常,处理序列和文件的最佳解决方案是直接访问这3个第三方库:
他们的Github页面解释了如何安装它们(安装非常简单)。然而,对于这个特殊问题,它们有点不理想。例如, dash
中的-sort
仅对列表进行排序,因此我们必须返回我们的字符串 - &gt; list-&gt;字符串转换:
(concat (-sort '< (append "cat" nil))) ; => "act"
来自 s-lines
的 s
会在文件中留下空字符串。在GNU / Linux上,文本文件通常最后以换行符结尾,因此拆分文件将如下所示:
(s-lines "cat\ncow\ndog\n") ; => ("cat" "cow" "dog" "")
s-split
支持一个可选参数来省略空行,但它的分隔符参数是regex(请注意,您需要\n
和\r
portability):
(s-split "[\n\r]" "cat\ncow\ndog\n" t) ; => ("cat" "cow" "dog")
然而,有两个功能可以简化我们的代码。 -map
与mapcar
类似:
(-map
(lambda (animal)
(list (cl-sort (copy-sequence animal) '<) animal))
'("cat" "cow" "dog"))
;; return
;; (("act" "cat")
;; ("cow" "cow")
;; ("dgo" "dog"))
但是在 dash
中,有anaphoric个版本的函数接受函数作为参数,例如-map
。回指版本允许通过将局部变量公开为it
并以2个破折号开头来使用更短的语法。例如。以下是相同的:
(-map (lambda (x) (+ x 1)) (1 2 3)) ; => (2 3 4)
(--map (+ it 1) (1 2 3)) ; => (2 3 4)
f
的另一个改进是f-read-text
,它只是将文件内容作为字符串返回:
(f-read-text "file.txt") ; => "cat\ncow\ndog\n"
(--map (list (cl-sort (copy-sequence it) '<) it)
(split-string (f-read-text "file.txt")))
答案 1 :(得分:0)
在我的emacs上, C-j 或 C-x C-e 按照你的说法评估表格。当我尝试对(get-file "test")
执行相同操作时,调试器会抱怨with-open-file
未定义。我在with-open-file
(或cl-lib
)个emacs包中找不到cl
。
你需要一些其他包吗?另外,我认为在Emacs中打开文件的惯用方法是在缓冲区中临时访问它们。
无论如何,如果代码是Common Lisp,那么除了collect ((sort ...) word)
之外你没有问题,你在不建立一个列表但在函数位置使用(sort ...)
。我改用(list (sort ...) word)
。