lisp 过滤掉列表中不匹配谓词的结果

问题描述 投票:0回答:8

我正在尝试使用 emacs 方言学习 lisp,我有一个问题。 假设列表有一些成员,其谓词评估为 false。如果没有这些成员,如何创建新列表?像

{ A in L: p(A) is true }
之类的东西。 python中有filter函数,lisp中有类似的函数吗?如果没有,我该怎么办?

谢谢

list lisp filter elisp predicate
8个回答
50
投票

这些函数位于 CL 包中,您需要

(require 'cl)
才能使用它们:

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

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

另请查找

delete-if-not
,它执行相同的操作,但修改其参数列表。


25
投票

如果您在代码中大量操作列表,请使用

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
的伟大之处在于它支持照应宏。照应宏的行为类似于函数,但它们允许特殊语法使代码更加简洁。无需提供匿名函数作为参数,只需编写s-表达式并使用
it
代替局部变量,如前面示例中的
x
。相应的照应宏以 2 个破折号而不是一个破折号开头:

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

19
投票

我昨晚正在寻找同样的内容,并在 EmacsWiki 上发现了 Elisp Cookbook列表/序列部分包含过滤技术,并展示如何使用

mapcar
delq
来完成此操作。我必须修改代码才能将其用于我自己的目的,但这是原始代码:

;; 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.

7
投票

Emacs 现在附带了库

seq.el
,请使用
seq-remove

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

1
投票

使用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)))

1
投票

有很多方法可以使用比循环快得多的内置函数从列表中过滤或选择内容。内置的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 ...) 删除 nils。


0
投票

令人惊讶的是,没有内置版本的过滤器没有

cl
或(或
seq
这是非常新的)。

此处提到的

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

要纠正此实现,需要将

nil
标记替换为未嵌入的符号(即 gensym)。

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

0
投票

如果你想留在基本的 elisp 中,你可以输入类似的内容

(mapcan (lambda(x) (and (zerop (% x 2)) (list x))) '(1 2 3))

这将返回 (2)。

要玩这个技巧,你需要有一个 t/nil 返回函数来代表你的过滤条件。它利用了 nconc 的 mapcan 用法,并且事实

(nconc '(a) nil) (一)

(nconc nil '(a)) (一)

© www.soinside.com 2019 - 2024. All rights reserved.