tags:

views:

566

answers:

4

I find myself doing this sort of thing all the time. I've been considering writing a macro/function to make this sort of thing easier, but it occurs to me that I'm probably reinventing the wheel.

Is there an existing function that will let me accomplish this same sort of thing more succinctly?

(defun remove-low-words (word-list)   
  "Return a list with words of insufficient score removed."
  (let ((result nil))
    (dolist (word word-list)  
      (when (good-enough-score-p word) (push word result)))                                      
    result))
A: 

There are a couple ways you can do this. First, and probably most easily, you can do it recursively.

(defun remove-low-words (word-list)
  (if (good-enough-score-p (car word-list))
      (list word (remove-low-words (cdr word-list)))
      (remove-low-words (cdr word-list))))

You could also do it with mapcar and reduce, where the former can construct you a list with failing elements replaced by nil and the latter can be used to filter out the nil.

Either would be a good candidate for a "filter" macro or function that takes a list and returns the list filtered by some predicate.

Chris Hanson
I believe your version has no base case, among other problems.
Luís Oliveira
+14  A: 

There are several built-in ways of doing this. One way would be:

(remove-if-not 'good-enough-score-p word-list)

And another:

(loop for word in word-list  
      when (good-enough-score-p word)
      collect word)

And yet another:

(mapcan (lambda (word)
          (when (good-enough-score-p word)
            (list word)))
        word-list)

Etc... There's also SERIES and Iterate. The Iterate version is identical to the LOOP version, but the SERIES version is interesting:

(collect (choose-if 'good-enough-score-p (scan word-list))))

So, yes, you're very likely to reinvent some wheel. :-)

Luís Oliveira
Thank you -- I've never really come up with a reason to use mapcan before, but this shows me the way. For this particular example, remove-if / remove-if-not is better, but still, very nice.
khedron
A: 

In the perl and unix world this is called grep, but it is really just syntax sugar over array iteration. For example, in perl, your solution would look like this:

my @results = grep {good_enough_score_p($_)} @word_list;

The Lisp equivalent is, as mentioned before, a (loop ... when cond collect ...) and/or (remove-if-not cond ...).

You could wrap this up in a function or macro and have a lisp grep (tho its pointless):

(defmacro grep (fn list)
    (let    ((word (intern (format nil "~a" (gensym)))))
        `(loop ;` // I really don't like this syntax hilighter
            for ,word in ,list
            when (funcall ,fn ,word)
            collect ,word)))

or

(defun grep (fn list)
    (remove-if-not fn list))
dsm
this code is very bogus and should be deleted from this article.
Luís Oliveira
I know you don't like the code, but bogus? If you can back that up I'll be happy to delete the response.
dsm
What are intern and format there for? Why not just "(let ((word (gensym))) ..."?
Svante
Bogus was perhaps too harsh. (Looking at this thread a year later, heh.) Your first implementation of GREP should be a function, and even if a macro made sense, that INTERN/FORMAT is reintroducing the hygiene problem GENSYMs are supposed to fix.
Luís Oliveira
+4  A: 

The function you want is remove-if-not, which is built-in.

(defun remove-low-words (word-list)
  (remove-if-not #'good-enough-score-p word-list))

If you feel like you are re-inventing something to do with lists, you probably are. Check the Hyperspec to see.

Nathan Sanders