【问题标题】:lisp filter out results from list not matching predicatelisp 从不匹配谓词的列表中过滤掉结果
【发布时间】:2011-01-15 03:29:54
【问题描述】:

我正在尝试使用 emacs 方言学习 lisp,但我有一个问题。 假设列表有一些成员,谓词评估为假。如何在没有这些成员的情况下创建新列表?类似{ A in L: p(A) is true }。在 python 中有过滤功能,在 lisp 中有没有等价的东西?如果没有,我该怎么做?

谢谢

【问题讨论】:

    标签: list lisp filter elisp predicate


    【解决方案1】:

    这些函数在 CL 包中,您需要(require 'cl) 才能使用它们:

    (remove-if-not #'evenp '(1 2 3 4 5))
    

    这将返回一个包含参数中所有偶数的新列表。

    同时查找delete-if-not,它的作用相同,但会修改其参数列表。

    【讨论】:

    • 我想指出,函数 #'remove-if-not 在 Common Lisp¹ 中已被弃用,其中过滤器将被编写为 (remove-if (complement #'evenp) '(1 2 3 4 5)) 或简单地写为 (remove-if #'oddp '(1 2 3 4 5)) — 函数 complement 没有据我所知,它存在于 Emacs Lisp 中。
    • 请使用 cl-lib 包,并使用 cl-remove-if-not 函数作为替代。
    【解决方案2】:

    如果您在代码中大量操作列表,请使用dash.el 现代函数式编程库,而不是编写样板代码和重新发明轮子。它具有您可以想象的与列表、树、函数应用程序和流控制一起使用的所有功能。要保留与谓词匹配的所有元素并删除其他元素,您需要-filter

    (-filter (lambda (x) (> x 2)) '(1 2 3 4 5)) ; (3 4 5)
    

    其他感兴趣的函数包括-remove-take-while-drop-while

    (-remove (lambda (x) (> x 2)) '(1 2 3 4 5)) ; (1 2)    
    (-take-while (lambda (x) (< x 3)) '(1 2 3 2 1)) ; (1 2)
    (-drop-while (lambda (x) (< x 3)) '(1 2 3 2 1)) ; (3 2 1)
    

    dash.el 的优点在于它支持anaphoric macros。照应宏的行为类似于函数,但它们允许使用特殊语法使代码更简洁。无需提供anonymous function 作为参数,只需编写s-expression 并使用it 而不是局部变量,如前面示例中的x。相应的照应宏以 2 个破折号而不是 1 个破折号开头:

    (--filter (> it 2) '(1 2 3 4 5)) ; (3 4 5)
    (--remove (> it 2) '(1 2 3 4 5)) ; (1 2)
    (--take-while (< it 3) '(1 2 3 2 1)) ; (1 2)
    (--drop-while (< it 3) '(1 2 3 2 1)) ; (3 2 1)
    

    【讨论】:

      【解决方案3】:

      我昨晚也在寻找同样的东西,并在EmacsWiki 上遇到了Elisp CookbookThe section on Lists/Sequences 包含过滤技术并展示如何使用 mapcardelq 完成此操作。我不得不修改代码以将其用于我自己的目的,但这是原始代码:

      ;; Emacs Lisp doesn’t come with a ‘filter’ function to keep elements that satisfy 
      ;; a conditional and excise the elements that do not satisfy it. One can use ‘mapcar’ 
      ;; to iterate over a list with a conditional, and then use ‘delq’ to remove the ‘nil’  
      ;; values.
      
         (defun my-filter (condp lst)
           (delq nil
                 (mapcar (lambda (x) (and (funcall condp x) x)) lst)))
      
      ;; Therefore
      
        (my-filter 'identity my-list)
      
      ;; is equivalent to
      
        (delq nil my-list)
      
      ;; For example:
      
        (let ((num-list '(1 'a 2 "nil" 3 nil 4)))
          (my-filter 'numberp num-list))   ==> (1 2 3 4)
      
      ;; Actually the package cl-seq contains the functions remove-if and remove-if-not. 
      ;; The latter can be used instead of my-filter.
      

      【讨论】:

        【解决方案4】:

        Emacs 现在自带库seq.el,使用seq-remove

        seq-remove (pred sequence) 
        "Return a list of all the elements for which (PRED element) is nil in SEQUENCE."
        

        【讨论】:

          【解决方案5】:

          使用common lisp,可以实现如下功能:

          (defun my-filter  (f args)
              (cond ((null args) nil)
                  ((if (funcall f (car args))
                      (cons (car args) (my-filter  f (cdr args)))
                      (my-filter  f (cdr args))))))
          
          (print 
                (my-filter #'evenp '(1 2 3 4 5)))
          

          【讨论】:

            【解决方案6】:

            有很多方法可以使用比循环快得多的内置函数从列表中过滤或选择内容。内置的 remove-if 可以这样使用。例如,假设我想删除列表 MyList 中的元素 3 到 10。以执行以下代码为例:

            (let ((MyList (number-sequence 0 9))
                  (Index -1)
                  )
              (remove-if #'(lambda (Elt)
                              (setq Index (1+ Index))
                              (and (>= Index 3) (<= Index 5))
                              )
                          MyList
                       )
             )
            

            你会得到'(0 1 2 6 7 8 9)。

            假设你只想保留 3 到 5 之间的元素。你基本上翻转了我上面在谓词中写的条件。

            (let ((MyList (number-sequence 0 9))
                  (Index -1)
                  )
              (remove-if #'(lambda (Elt)
                               (setq Index (1+ Index))
                               (or (< Index 3) (> Index 5))
                              )
                          MyList
                       )
             )
            

            你会得到'(3 4 5)

            对于必须提供给 remove-if 的谓词,您可以使用所需的任何内容。唯一的限制是您对使用什么的想象力。您可以使用序列过滤功能,但您不需要它们。

            或者,您也可以使用 mapcar 或 mapcar* 来循环列表,使用一些函数将特定条目变为 nil 并使用 (remove-if nil ...) 删除 nil。

            【讨论】:

              【解决方案7】:

              令人惊讶的是没有内置版本的过滤器没有cl 或(或seq,这是非常新的)。

              这里提到的filter 的实现(您可以在 Elisp Cookbook 和其他地方看到)是不正确的。它使用nil 作为要删除的项目的标记,这意味着如果您的列表中有nils,即使它们满足谓词也会被删除。

              要更正此实现,需要将nil 标记替换为未嵌入的符号(即 gensym)。

              (defun my-filter (pred list)
                (let ((DELMARKER (make-symbol "DEL")))
                  (delq
                    DELMARKER
                    (mapcar (lambda (x) (if (funcall pred x) x DELMARKER))
                            list))))
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2018-11-17
                • 1970-01-01
                • 2018-02-05
                • 1970-01-01
                • 1970-01-01
                • 2018-08-06
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多