我正在尝试使用 emacs 方言学习 lisp,但我有一个问题。假设列表有一些成员,谓词评估为假。如何在没有这些成员的情况下创建新列表?类似的东西{ A in L: p(A) is true }
。在 python 中有过滤功能,在 lisp 中有没有等价的东西?如果没有,我该怎么做?
谢谢
这些函数在 CL 包中,您需要(require 'cl)
使用它们:
(remove-if-not #'evenp '(1 2 3 4 5))
这将返回一个新列表,其中包含参数中的所有偶数。
还要查找delete-if-not
,它的作用相同,但修改了它的参数列表。
如果您在代码中大量操作列表,请使用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 个破折号而不是 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)
昨晚我正在寻找同样的东西,并在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.
Emacs 现在自带库seq.el
,使用seq-remove
.
seq-remove (pred sequence)
"Return a list of all the elements for which (PRED element) is nil in SEQUENCE."
使用 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)))
有很多方法可以使用比循环快得多的内置函数从列表中过滤或选择内容。内置的 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 来循环列表。
令人惊讶的是,没有内置版本的过滤器没有cl
or (或者seq
是非常新的)。
这里提到的实现filter
(你在 Elisp Cookbook 和其他地方看到的)是不正确的。它nil
用作要删除的项目的标记,这意味着如果您nil
的列表中有 s 开头,即使它们满足谓词,它们也会被删除。
为了更正这个实现,nil
标记需要被替换为一个未隐藏的符号(即 gensym)。
(defun my-filter (pred list)
(let ((DELMARKER (make-symbol "DEL")))
(delq
DELMARKER
(mapcar (lambda (x) (if (funcall pred x) x DELMARKER))
list))))