【问题标题】:(emacs) lisp: search anything in a ((nested) list)(emacs) lisp: 搜索 ((嵌套) 列表中的任何内容)
【发布时间】:2012-08-11 04:56:45
【问题描述】:

我需要找到一个特定的值,该值可以隐藏在一个深度嵌套的列表中,而且永远不会在同一个地方。甚至同样的深度;这是列表的一种形式:

(setq my-list '(((partnum . 1) (type (TEXT . plain)) (body (charset UTF-8))
                 (disposition nil) (transfer-encoding QUOTED-PRINTABLE))
                ((partnum . 2) (type (TEXT . html)) (body (charset UTF-8))
                 (disposition nil) (transfer-encoding QUOTED-PRINTABLE)))) 

现在我需要检索 "charset" 的值;如果有的话,第一个。在这种配置中,很容易:

(car (cdr (cadr (third (car my-list)))))
   => UTF-8

但这是我确切知道“身体”单元格在哪里的时候。

我尝试像这样递归地使用 mapcar:

(defun search-rec (list)
  (mapcar
     (lambda (x)
       (if (listp x)
           (search-rec x)
         (message "OY %s" x)))
     list))

但每次,当递归命中第一个 cons 单元的第一个原子时,我都会收到错误 (wrong-type-argument listp 1)。我想我的问题真的是它是什么:

如何在列表中搜索?

编辑 现在列表看起来像这样,“charset”仍在 (body) 中(告诉你这是唯一不变的东西)并且不再找到它:(

(setq my-list '(((partnum . 1)
                (1.1 (type (TEXT . plain)) (body (charset UTF-8))
                     (disposition nil) (transfer-encoding 7BIT))
                (1.2 (type (TEXT . html)) (body (charset UTF-8))
                     (disposition nil) (transfer-encoding 7BIT))
                (type . alternative) (body (boundary e89a8fb2067eba300404c63c5f7f))
                (disposition nil) (transfer-encoding nil))
               ((partnum . 1.1) (type (TEXT . plain)) (body (charset UTF-8))
                (disposition nil) (transfer-encoding 7BIT))
               ((partnum . 1.2) (type (TEXT . html)) (body (charset UTF-8))
                (disposition nil) (transfer-encoding 7BIT))
               ((partnum . 2) (type (IMAGE . x-xpixmap)) (body (name ladybug.xpm))
                (disposition nil) (transfer-encoding BASE64))))

这里编辑是更多 IRL 示例:

    (setq my-list haystack-list)
    (setq my-needle (tree-assoc 'charset my-list))
    (message "
-------------\n
- my-list: %s\n
- my-needle: %s\n
-------------\n" my-list my-needle)

生产:


  • my-list: ((TEXT plain (charset UTF-8) nil nil 7BIT 260 18 nil nil nil) (TEXT html (charset UTF-8) nil nil QUOTED-PRINTABLE 738 17 nil nil nil) 替代(边界 e89a8fb1f8061a6be404c70a24a0) nil nil)

  • 我的针:无


另一方面:

(tree-assoc 'charset '((TEXT plain (charset UTF-8) nil nil 7BIT 260 18 nil nil nil)
(TEXT html (charset UTF-8) nil nil QUOTED-PRINTABLE 738 17 nil nil nil) 
alternative (boundary e89a8fb1f8061a6be404c70a24a0) nil nil))
  =>(charset UTF-8)

真的,我不知道这里发生了什么:有人可能会争论“这个干草堆列表是什么,它来自哪里?”但它是否相关?我正在制作这个 haystack-list 的副本(我的列表),那么是什么给出了这些不同的结果?列表的引用?伙计们,我真的迷路了

NB(这种行为(在直接评估中有效,但不适用于 defun/let 生产情况)在给出的所有解决方案中发生)

编辑:我最终提取了找到的第一个列表,然后从该列表中提取(而不是搜索)元素。我证明更快;当然此时你可以说“我的元素总是在找到的第一个列表中);感谢大家,通过这一切我学到了很多。

【问题讨论】:

  • 生成这些列表的任何东西都应该生成字符串值而不是符号值。我怀疑您的部分问题源于那里,这将使进一步处理变得困难。
  • 我猜你是对的,瓦廷。几乎这里给出的每个解决方案都适用于引用的列表示例,但在 let 上下文中中断(在“listp”测试中。就像整个想法不是要区分列表和原子......我迷路了)。问题是,这个列表是由一个大的(imap.el)生成的,我不想编辑它......
  • 到目前为止,似乎我正在尝试做的事情(在“my-list”中搜索“something”,无论my-list到底是什么,只要它是一个语法正确的列表) 是不可能的。我不知道有些列表可能是“不适当的”......嗯。

标签: algorithm emacs lisp elisp


【解决方案1】:

看起来您想要Association Lists 的树模拟。通过遵循 assoc 函数的约定,该函数检索包含给定键作为其头部的列表元素,这是一个适用于树的 assoc 版本:

(defun tree-assoc (key tree)
  (when (consp tree)
    (destructuring-bind (x . y)  tree
      (if (eql x key) tree
        (or (tree-assoc key x) (tree-assoc key y))))))

例子:

(let ((my-list '(((partnum . 1)
                  (1.1 (type (TEXT . plain)) (body (charset UTF-8))
                   (disposition nil) (transfer-encoding 7BIT))
                  (1.2 (type (TEXT . html)) (body (charset UTF-8))
                   (disposition nil) (transfer-encoding 7BIT))
                  (type . alternative) (body (boundary e89a8fb2067eba300404c63c5f7f))
                  (disposition nil) (transfer-encoding nil))
                 ((partnum . 1.1) (type (TEXT . plain)) (body (charset UTF-8))
                  (disposition nil) (transfer-encoding 7BIT))
                 ((partnum . 1.2) (type (TEXT . html)) (body (charset UTF-8))
                  (disposition nil) (transfer-encoding 7BIT))
                 ((partnum . 2) (type (IMAGE . x-xpixmap)) (body (name ladybug.xpm))
                  (disposition nil) (transfer-encoding BASE64)))))
  (tree-assoc 'charset my-list))

=> (charset UTF-8)

【讨论】:

  • 对正确和标准的 car-cdr 递归表示赞赏。没有更基本的了。人们过于关注避免递归,因为 Common Lisp 不是 TCO。
【解决方案2】:

这取决于你想要做什么以及列表结构有多相似(也就是说,你总是有一个 HTML 部分的列表吗?字符集总是在 body 元素中吗?)

第一步可能是:

(defun list-query (list-of-keys data)
  (let ((data data))
    (while (and data list-of-keys)
      (setq data (assoc (car list-of-keys) data))
      (setq list-of-keys (cdr list-of-keys)))
    data))

调用(list-query '(body charset) (car my-list)) 会得到(charset UTF-8) 的结果。遍历 my-list 以查找正文列表中的第一个(或所有)字符集应该相对容易。

【讨论】:

  • 感谢您的回答,瓦廷。正如您在我的编辑中看到的,现在列表已更改并且 (list-query '(body charset) (car my-list)) 不再起作用;如果我必须遍历列表,那么我们又回到了我的问题,我似乎无法遍历列表并按顺序打印所有元素,而 Emacs 不会因一种类型谓词或另一种类型谓词而窒息;你能告诉我如何在我的列表上映射列表查询吗?
  • 如果您可以忽略隐藏在 (partnum . 1) 部分(在 1.1 和 1.2 位下)的那些,只需简单的 (mapcar (lambda (x) (list-query '(body charset) x)) my-list) 即可为您提供找到的所有字符集的列表。或者,(find-if 'identity (mapcar (lambda (x) (list-query '(body charset) x)) my-list)) 应该给你第一个非零。
【解决方案3】:

正如 Rainer 的回答暗示的那样,您遇到的问题是 cons 单元格的 cdr 可能指向一个列表,或者它可能指向某种其他类型的对象;您的 search-rec 函数无法防范后一种可能性。

这是您正在寻找的 Elisp 版本(未经彻底测试;适用于您的示例数据):

(defun find-charset (l)
  (catch 'my-result
    (find-charset-do l)))

(defun find-charset-do (l)
  (when (and (consp l) 
             (listp (cdr l)))
    (if (and (eq (car l) 'charset)
             (symbolp (cadr l)))
        (throw 'my-result (cadr l))
      (dolist (e l)
        (find-charset-do e)))))

【讨论】:

    【解决方案4】:

    这是我对这个问题的看法,也许你会发现它很有用:

    (defun depth-first-search (tree searched &optional comparator)
      "TREE is the nested list of elements to search, SEARCHED
    is the element to search for, COMPARATOR is the function used
    to compare elements of the tree to the searched element, if
    you don't provide any, then `equal' is used.
    Returns a list of subscripts to be used with `nth' to find the
    searched element. If the result is `nil', the list itself
    is the searched element. If the result is not a list,
    the `not-found' symbol, then the element was not found."
      (unless comparator (setq comparator #'equal))
      (let ((operations 'not-found))
        (labels ((%df-search
                  (item ops)
                  (if (funcall comparator item searched)
                      (setq operations (reverse ops))
                    (let ((offset 0))
                      (when (consp item)
                        (dolist (i item)
                          (%df-search i (cons offset ops))
                          (unless (eq operations 'not-found)
                            (return))
                          (incf offset)))))))
          (%df-search tree nil)
          operations)))
    
    (defun nth-repeat (subscripts tree)
      "Given the list of SUBSCRIPTS, will subsequently evaluate
    `nth' with every subscript on the result of the previous evaluation
     such as to find the element in the TREE."
      (let ((result tree))
        (dolist (i subscripts result)
          (setq result (nth i result)))))
    
    (nth-repeat 
     (depth-first-search '(1 (1 1 2) (1 1 1 3)) 3)
     '(1 (1 1 2) (1 1 1 3)))
    

    这将要求您使用cl,但这很常见,您可能甚至都不会注意到,很可能您已经拥有它。

    编辑:好的,这样您可以完全避免查看不正确列表的最后一个元素,但是,这意味着您也无法在那里搜索:

    (defun depth-first-search (tree searched &optional comparator)
      "TREE is the nested list of elements to search, SEARCHED
    is the element to search for, COMPARATOR is the function used
    to compare elements of the tree to the searched element, if
    you don't provide any, then `equal' is used.
    Returns a list of subscripts to be used with `nth' to find the
    searched element. If the result is `nil', the list itself
    is the searched element. If the result is not a list,
    the `not-found' symbol, then the element was not found."
      (unless comparator (setq comparator #'equal))
      (let ((operations 'not-found))
        (labels ((%df-search
                  (item ops)
                  (if (funcall comparator item searched)
                      (setq operations (reverse ops))
                    (let ((offset 0))
                      (when (consp item)
                        (block outer
                          (maplist
                           (lambda (x)
                             (%df-search (car x) (cons offset ops))
                             (when (or (not (eq operations 'not-found))
                                       (not (listp (cdr x))))
                               (return-from outer))
                             (incf offset))
                           item)))))))
          (%df-search tree nil)
          operations)))
    
    (defun nth-repeat (subscripts tree)
      "Given the list of SUBSCRIPTS, will subsequently evaluate
    `nth' with every subscript on the result of the previous evaluation
     such as to fint the element in the TREE."
      (let ((result tree))
        (dolist (i subscripts result)
          (setq result (nth i result)))))
    
    (defvar my-list '(((partnum . 1)
                       (1.1 (type (TEXT . plain)) (body (charset UTF-8))
                            (disposition nil) (transfer-encoding 7BIT))
                       (1.2 (type (TEXT . html)) (body (charset UTF-8))
                            (disposition nil) (transfer-encoding 7BIT))
                       (type . alternative) (body (boundary e89a8fb2067eba300404c63c5f7f))
                       (disposition nil) (transfer-encoding nil))
                      ((partnum . 1.1) (type (TEXT . plain)) (body (charset UTF-8))
                       (disposition nil) (transfer-encoding 7BIT))
                      ((partnum . 1.2) (type (TEXT . html)) (body (charset UTF-8))
                       (disposition nil) (transfer-encoding 7BIT))
                      ((partnum . 2) (type (IMAGE . x-xpixmap)) (body (name ladybug.xpm))
                       (disposition nil) (transfer-encoding BASE64))))
    
    (depth-first-search
     my-list '(charset UTF-8))              ; (0 1 2 1)
    
    (nth-repeat
     (depth-first-search
      my-list '(charset UTF-8)) my-list)    ; (charset UTF-8)
    

    可能不是解决问题的最佳方法,但更好的解决方案需要更改算法以记录cars 和cdrs 的序列,从而将您带到有问题的元素。在这种情况下,您还可以在列表的“不当”部分进行搜索。但是现在已经太晚了:)也许明天。

    编辑 2

    (defun tree-to-proper-tree (tree)
      (cond
       ((null tree) nil)
       ((consp tree)
        (let ((head
               (if (consp (car tree))
                   (tree-to-proper-tree (car tree))
                 (car tree))))
        (cons head
              (tree-to-proper-tree (cdr tree)))))
       (t (list tree))))
    
    (defun find-path-to (tree node &optional comparator)
      (unless comparator (setq comparator #'equal))
      (let ((operations 'not-found))
        (labels ((%df-search
                  (item ops)
                  (if (funcall comparator item node)
                      (setq operations (reverse ops))
                    (when (consp item)
                          (%df-search (car item) (cons 'car ops))
                          (%df-search (cdr item) (cons 'cdr ops))))))
          (%df-search tree nil)
          operations)))
    
    (defun c*r-path (path tree)
      (dolist (i path tree)
        (setq tree (funcall i tree))))
    
    (defvar my-list '(((partnum . 1)
                       (1.1 (type (TEXT . plain)) (body (charset UTF-8))
                            (disposition nil) (transfer-encoding 7BIT))
                       (1.2 (type (TEXT . html)) (body (charset UTF-8))
                            (disposition nil) (transfer-encoding 7BIT))
                       (type . alternative) (body (boundary e89a8fb2067eba300404c63c5f7f))
                       (disposition nil) (transfer-encoding nil))
                      ((partnum . 1.1) (type (TEXT . plain)) (body (charset UTF-8))
                       (disposition nil) (transfer-encoding 7BIT))
                      ((partnum . 1.2) (type (TEXT . html)) (body (charset UTF-8))
                       (disposition nil) (transfer-encoding 7BIT))
                      ((partnum . 2) (type (IMAGE . x-xpixmap)) (body (name ladybug.xpm))
                       (disposition nil) (transfer-encoding BASE64))))
    
    (tree-to-proper-tree my-list) ; the same lists as above but made into a proper lists
    
    (c*r-path (find-path-to my-list 'UTF-8) my-list) ; UTF-8
    (c*r-path (find-path-to my-list 'plain) my-list) ; plain
    

    好的,那么,tree-to-proper-tree,如果您选择它,它将以所有不正确的子树都将成为正确树的方式转换树。或者,您可以使用find-path-to 查找carcdr 的哪个序列会将您带到您搜索的元素,c*r-path 将评估该序列以返回以该方式记录的元素。

    请注意,以这种方式搜索重复出现的同一节点将非常具有挑战性。您必须提供一些比较器函数来计算找到该项目的次数。

    【讨论】:

    • wvxvw,我尝试了 nth-repeat 和 depth-first-search 的各种组合以及各种参数,但在我的列表中一无所获。你能告诉我如何使用它来查找第一个“字符集”的值吗?
    • 感谢您的 cmets,尤其是关于“正确列表”的那一点...如何使列表正确以 nil 结尾?我试过(追加我的列表()),但我在输出中看不到它..
    猜你喜欢
    • 1970-01-01
    • 2016-02-12
    • 2013-11-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多