lisp filter out results from list not matching predicate
Asked Answered
N

8

43

I am trying to learn lisp, using emacs dialect and I have a question. let us say list has some members, for which predicate evaluates to false. how do I create a new list without those members? something like { A in L: p(A) is true }. in python there is filter function, is there something equivalent in lisp? if not, how do I do it?

Thanks

Natashianatassia answered 10/2, 2010 at 6:25 Comment(0)
A
50

These functions are in the CL package, you will need to (require 'cl) to use them:

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

This will return a new list with all even numbers from the argument.

Also look up delete-if-not, which does the same, but modifies its argument list.

Ahimsa answered 10/2, 2010 at 6:58 Comment(3)
I would like to point out that the function #'remove-if-not are deprecated in Common Lisp¹ where the filter would be written (remove-if (complement #'evenp) '(1 2 3 4 5)) or simply (remove-if #'oddp '(1 2 3 4 5)) — the function complement does not exist in Emacs Lisp to my knowledge though.Burgener
Pls use cl-lib package, and use cl-remove-if-not function as replacement.Hardspun
The gigamonkeys book claims that "If the standard is ever revised, it's more likely the deprecation will be removed than the -IF-NOT functions."Muscatel
B
27

If you manipulate lists heavily in your code, please use dash.el modern functional programming library, instead of writing boilerplate code and reinventing the wheel. It has every function to work with lists, trees, function application and flow control you can ever imagine. To keep all elements that match a predicate and remove others you need -filter:

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

Other functions of interest include -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)

What is great about dash.el is that it supports anaphoric macros. Anaphoric macros behave like functions, but they allow special syntax to make code more concise. Instead of providing an anonymous function as an argument, just write an s-expression and use it instead of a local variable, like x in the previous examples. Corresponding anaphoric macros start with 2 dashes instead of one:

(--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)
Barahona answered 3/8, 2014 at 16:32 Comment(0)
F
19

I was looking for the very same last night and came across the Elisp Cookbook on EmacsWiki. The section on Lists/Sequences contains filtering teqniques and show how this can be done with mapcar and delq. I had to mod the code to use it for my own purposes but here is the original:

;; 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.
Ferdinand answered 10/2, 2010 at 8:40 Comment(0)
K
7

Emacs now comes with the library seq.el, use seq-remove.

seq-remove (pred sequence) 
"Return a list of all the elements for which (PRED element) is nil in SEQUENCE."
Kneel answered 3/2, 2017 at 15:31 Comment(0)
H
1

With common lisp, you can implement the function as follows:

(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)))
Haman answered 28/8, 2015 at 23:55 Comment(0)
C
1

There are a ton of ways to filter or select stuff from a list using built-ins which are much faster than loops. The built-in remove-if can be used this way. For example, suppose I want to drop the elements 3 through 10 in list MyList. Execute the following code as an example:

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

You will get '(0 1 2 6 7 8 9).

Suppose you want to keep only elements between 3 and 5. You basically flip the condition I wrote above in the predicate.

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

You will get '(3 4 5)

You can use whatever you need for the predicate that you must supply to remove-if. The only limit is your imagination about what to use. You can use the sequence filtering functions, but you don't need them.

Alternatively, you could also use mapcar or mapcar* to loop over a list using some function that turns specific entries to nil and the use (remove-if nil ...) to drop nils.

Calandracalandria answered 22/6, 2017 at 22:49 Comment(0)
S
1

If you want to stay in basic elisp you can type something like

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

This will return (2).

To play this trick you need to have a t/nil returning function that represents your filtering condition. It exploits mapcan usage of nconc, and the fact that both

(nconc '(a) nil)

and

(nconc nil '(a))

return

(a)

Schoolmistress answered 6/3 at 19:0 Comment(0)
H
0

It's surprising there's no builtin version of filter without cl or (or seq which is very new).

The implementation of filter mentioned here (which you see in the Elisp Cookbook and elsewhere) is incorrect. It uses nil as a marker for items to be removed, which means if you have nils in your list to start with, they're going to be removed even if they satisfy the predicate.

To correct this implementation, the nil markers need to be replaced with an uninterred symbol (ie. gensym).

(defun my-filter (pred list)
  (let ((DELMARKER (make-symbol "DEL")))
    (delq
      DELMARKER
      (mapcar (lambda (x) (if (funcall pred x) x DELMARKER))
              list))))
Hoffarth answered 24/4, 2017 at 15:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.