Lisp отфильтровать результаты из списка, не соответствующего предикату
Я пытаюсь изучить lisp, используя диалект emacs, и у меня есть вопрос.
скажем, в списке есть некоторые члены, для которых предикат оценивается как false. как мне создать новый список без этих членов? что-то вроде { A in L: p(A) is true }
. в python есть функция фильтра, есть ли что-то эквивалентное в lisp? если нет, как это сделать?
Спасибо
Ответы
Ответ 1
Эти функции находятся в пакете CL, вам нужно (require 'cl)
использовать их:
(remove-if-not #'evenp '(1 2 3 4 5))
Это вернет новый список со всеми четными числами из аргумента.
Также найдите delete-if-not
, который делает то же самое, но изменяет список своих аргументов.
Ответ 2
Я искал ту же самую прошлую ночь и наткнулся на Elisp Cookbook на EmacsWiki. В разделе о списках/последовательностях содержатся теги фильтрации и показано, как это можно сделать с помощью 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.
Ответ 3
Если вы сильно манипулируете списками в своем коде, используйте 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-expression и используйте it
вместо этого локальной переменной, например x
в предыдущих примерах. Соответствующие анафорические макросы начинаются с двух тире вместо одного:
(--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)
Ответ 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
С общим 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
Удивительно, что нет встроенной версии фильтра без cl
или (или seq
, что является очень новым).
Реализация filter
, упомянутая здесь (что вы видите в поваренной книге Elisp и в другом месте), неверна. Он использует 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))))
Ответ 7
Существует множество способов фильтрации или выбора материала из списка с использованием встроенных модулей, которые намного быстрее, чем циклы. Встроенный remove-if может использоваться таким образом. Например, предположим, что я хочу удалить элементы с 3 по 10 в список MyList. Выполните следующий код в качестве примера:
(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)
Вы можете использовать все, что вам нужно для предиката, который вы должны предоставить для удаления - если. Единственный предел - ваше воображение о том, что использовать. Вы можете использовать функции фильтрации последовательностей, но они вам не нужны.
В качестве альтернативы вы также можете использовать mapcar или mapcar * для циклического перехода по списку, используя некоторую функцию, которая превращает определенные записи в nil, а использование (remove-if nil...) для удаления nils.