lisp filter out results from list not matching predicate

22,348

Solution 1

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.

Solution 2

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)

Solution 3

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.

Solution 4

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

Solution 5

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.

Share:
22,348
Anycorn
Author by

Anycorn

Andrey Asadchev in real life. Currently working in Silicon Valley. Previously was a PostDoc at VT (MPQC project), before that PhD at Iowa State. My interests are FISHING!!!, C++, Python, numerical algorithms, parallel and scientific programming, code optimization.

Updated on July 09, 2022

Comments

  • Anycorn
    Anycorn almost 2 years

    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

  • Iceland_jack
    Iceland_jack over 12 years
    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.
  • Aborn Jiang
    Aborn Jiang over 7 years
    Pls use cl-lib package, and use cl-remove-if-not function as replacement.