lisp отфильтровывает результаты из списка, не соответствующего предикату - PullRequest
35 голосов
/ 10 февраля 2010

Я пытаюсь выучить язык, используя диалект emacs, и у меня есть вопрос. допустим, в списке есть несколько членов, для которых предикат оценивается как ложный. как мне создать новый список без этих участников? что-то вроде { A in L: p(A) is true }. в питоне есть функция фильтра, есть ли что-то эквивалентное в lisp? если нет, то как мне это сделать?

Спасибо

Ответы [ 7 ]

42 голосов
/ 10 февраля 2010

Эти функции находятся в пакете CL, вам потребуется (require 'cl), чтобы использовать их:

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

Это вернет новый список со всеми четными числами из аргумента.

Также ищите delete-if-not, который делает то же самое, но изменяет свой список аргументов.

21 голосов
/ 03 августа 2014

Если вы интенсивно манипулируете списками в своем коде, используйте современную библиотеку функционального программирования 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 голосов
/ 10 февраля 2010

Я искал то же самое вчера вечером и наткнулся на 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.
5 голосов
/ 03 февраля 2017

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 голос
/ 23 июня 2017

Существует множество способов отфильтровать или выбрать материал из списка, используя встроенные модули, которые намного быстрее, чем циклы. Таким образом можно использовать встроенный метод 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)

Вы можете использовать все, что вам нужно, для предиката, который вы должны предоставить для remove-if. Единственным ограничением является ваше воображение о том, что использовать. Вы можете использовать функции фильтрации последовательностей, но они вам не нужны.

В качестве альтернативы, вы также можете использовать mapcar или mapcar * для циклического перемещения по списку, используя некоторую функцию, которая превращает определенные записи в nil, и использование (remove-if nil ...) для удаления nils.

1 голос
/ 29 августа 2015

С помощью общего 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)))
0 голосов
/ 24 апреля 2017

Удивительно, что нет встроенной версии фильтра без cl или (или 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))))
...