【问题标题】:Read a file into a list of pairs in elisp将文件读入elisp中的对列表
【发布时间】:2015-10-15 21:00:59
【问题描述】:

我正在尝试编写一个 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)

我是 lisp 初学者,不知道哪里出了问题。

谢谢,

贾斯汀

【问题讨论】:

    标签: list emacs lisp elisp


    【解决方案1】:

    原版 Emacs

    首先,让我们只使用 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)))))
    

    通用 Lisp 仿真

    Emacs 内置包之一是cl.el,没有理由不在您的代码中使用它。因此我撒了谎,当我说没有内置函数来对字符串进行排序时,上面是使用内置函数完成任务的唯一方法。所以让我们使用cl.el

    cl-sort 一个字符串(或任何sequence):

    (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.elmapcar 的替代品是seq-map,CL 的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)))))
    

    破折号、s、f

    通常使用序列和文件的最佳解决方案是直接访问这 3 个第三方库:

    • dash 用于列表操作
    • s 用于字符串操作
    • f 用于文件操作。

    他们的 Github 页面解释了如何安装它们(安装非常简单)。然而,对于这个特殊问题,它们有点不理想。例如,来自 dash-sort 仅对列表进行排序,因此我们必须回到我们的字符串->列表->字符串转换:

    (concat (-sort '< (append "cat" nil))) ; => "act"
    

    s-lines from s 在文件中留下空字符串。在 GNU/Linux 上,文本文件通常以换行符结尾,因此拆分文件如下所示:

    (s-lines "cat\ncow\ndog\n") ; => ("cat" "cow" "dog" "")
    

    s-split 支持省略空行的可选参数,但它的分隔符参数是 regex(请注意,portability 需要 \n\r):

    (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)
    

    另一个改进是来自 ff-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")))
    

    【讨论】:

    • 不确定为什么这不是一个可接受的答案。 @justinrixx 你能做到吗?
    【解决方案2】:

    在我的 emacs 上,C-jC-x C-e 都会按照您所说的评估表单。当我尝试对(get-file "test") 执行相同操作时,调试器会抱怨with-open-file 未定义。我在cl-lib(或cl)emacs 包中找不到with-open-file。 你需要一些其他的包吗?另外,我认为在 Emacs 中打开文件的惯用方式是在缓冲区中临时访问它们。 无论如何,如果代码是 Common Lisp,那么除了 collect ((sort ...) word) 之外都可以,您 不是 构建列表,而是在函数位置使用 (sort ...)。我会改用(list (sort ...) word)

    【讨论】:

    • 好吧,当我说它编译时,我的意思是,如果我在 lisp 交互模式下在函数的最后一行按CTRL+J,它会在下一行输出get-file。如果那没有编译,我犯了一个错误。
    • 抱歉,我真的不知道 lisp 方言的具体内容。我刚刚将答案修改为this question
    • @justinrixx 你想在 Emacs Lisp 中编码,你无事可做。如果你想使用 Emacs 与 Common Lisp 交互,你必须设置 Slime。 Lisp 方言可能看起来很相似,但实际上并非如此。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-07-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多