views:

107

answers:

3

I want to filter out all elements of list 'a from list 'b and return the filtered 'b. This is my function:

(defun filter (a b)
  "Filters out all items in a from b"
    (if (= 0 (length a)) b
      (filter (remove (first a) a) (remove (first a) b))))

I'm new to lisp and don't know how 'remove does its thing, what kind of time will this filter run in?

A: 

Common Lisp does not support tail-call optimization (as per the standard) and you might just run out of memory with an abysmal call-stack (depending on the implementation).

Vijay Mathew
No, the standard does not _guarantee_ the support for TCO. Most implementations do, however.
Svante
@Svante, the standard does not say anything about TCO. It does not only 'not guarantee' it, but does not even take it into account if it were there. There is no consideration of effects or limits of TCO, when an implementation might provide it. So the standard ignores TCO and its consequences.
Rainer Joswig
+7  A: 

There are two ways to find out:

  • you could test it with data

  • you could analyze your source code

Let's look at the source code.

  • lists are built of linked cons cells

  • length needs to walk once through a list

  • for EVERY recursive call of FILTER you compute the length of a. BAD!

    (Use ENDP instead.)

  • REMOVE needs to walk once through a list

  • for every recursive call you compute REMOVE twice: BAD!

    (Instead of using REMOVE on a, recurse with the REST.)

  • the call to FILTER will not necessarily be an optimized tail call. In some implementations it might, in some you need to tell the compiler that you want to optimize for tail calls, in some implementations no tail call optimization is available. If not, then you get a stack overflow on long enough lists.

    (Use looping constructs like DO, DOLIST, DOTIMES, LOOP, REDUCE, MAPC, MAPL, MAPCAR, MAPLIST, MAPCAN, or MAPCON instead of recursion, when applicable.)

Summary: that's very naive code with poor performance.

Common Lisp provides this built in: SET-DIFFERENCE should do what you want.

http://www.lispworks.com/documentation/HyperSpec/Body/f_set_di.htm#set-difference

Rainer Joswig
Like I mentioned, I'm new to lisp so naivety should be expected. That said, thanks for the analyzation, I'll have to take a look at SET-DIFFERENCE, and switch out that REMOVE with REST. How would you write this function? I've found that diving into lisp is a little like diving into the ocean.
efnx clckclcks
+1  A: 

I would not write this function, becuase, as Rainer Joswig says, the standard already provides SET-DIFFERENCE. Nonetheless, if I had to provide an implementation of the function, this is the one I would use:

(defun filter (a b)
  (let ((table (make-hash-table)))
    (map 'nil (lambda (e) (setf (gethash e table) t)) a)
    (remove-if (lambda (e) (gethash e table)) b)))

Doing it this way provides a couple of advantages, the most important one being that it only traverses b once; using a hash table to keep track of what elements are in a is likely to perform much better if a is long.

Also, using the generic sequence functions like MAP and REMOVE-IF mean that this function can be used with strings and vectors as well as lists, which is an advantage even over the standard SET-DIFFERENCE function. The main downside of this approach is if you want extend the function with a :TEST argument that allows the user to provide an equality predicate other than the default EQL, since CL hash-tables only work with a small number of pre-defined equality predicates (EQ, EQL, EQUAL and EQUALP to be precise).

Pillsy
Ah, sweet, thanks.
efnx clckclcks