Page 1 of 1

remove-duplicates

Posted: Sat Jun 04, 2011 9:06 pm
by I X Code X 1
Hello all,

I have a question about the remove-duplicates function.

I understand how it works, just want to ask a few things about it:

remove-duplicates sequence &key :from-end :test :test-not
:start :end :key


1. What exactly is the :test-not argument used for?

2. I wanted to use the remove-duplicates function to get rid of duplicate characters in a string. So I typed up something like this:

Code: Select all

 (remove-duplicates "heyyyy cooooool aaaaaa" :TEST #'CHAR-EQUAL :from-end t)
Now my intention was to get hey cool a, but of course this did not happen. I instead got hey coola. #\space is also being seen as a duplicate. My question is, is there anyway to say "remove-duplicates EXCEPT ... "? I assumed maybe this is what :test-not does, but I tried it out and got this error: Error: Invalid :TEST-NOT argument: #\Space



Thanks!

Re: remove-duplicates

Posted: Sat Jun 04, 2011 11:18 pm
by ramarren
I X Code X 1 wrote:1. What exactly is the :test-not argument used for?
The specification explains it here. In general the :test argument provides a two-argument predicate function which can be used to compare things. The :test-not argument has the same meaning, but the result of the predicate is reversed. The effect of providing both is undefined and likely an error.
I X Code X 1 wrote:My question is, is there anyway to say "remove-duplicates EXCEPT ... "
You can provide a predicate which always returns false when encountering a space:

Code: Select all

(remove-duplicates "heyyyy cooooool aaaaaa"
                   :test #'(lambda (x y)
                             (unless (eql x #\space)
                               (char-equal x y)))
                   :from-end t)
Which results in "hey col a", since obviously all #\o are duplicates.

The thing about REMOVE-DUPLICATES is that it works on fully general objects, which places limitations on its performance. If your objects are sortable or hashable it is generally better to use a custom function.

Re: remove-duplicates

Posted: Sun Jun 05, 2011 12:12 am
by edgar-rft
[Ramarren was faster than me, he answered while I was still typing ... :D]

The :test and :test-not keyword parameters are intended to be used as either :test or :test-not but not both at the same time. Altough it sometimes works [it's unspecified and therefore implementation dependent if an error will be signalled or not you give both] the result will usually be very confusing.

I think the main reason why there are both :test and :test-not is that Lisp originally was designed by mathematicians and testing numerical results often requires heavy complicated :test functions and often it's easier to write a :test-not math-test to exclude the opposite of the desired numbers.

The typical Lisp solution for your problem is to use an anonymous lambda-function as :test or :test-not argument, so both solutions shown below will work:

Code: Select all

(remove-duplicates "heyyyy cooooool aaaaaa"
                   :test #'(lambda (x y)
                             (and (char-equal x y)
                                  (char/= x #\space)
                                  (char/= y #\space))))

(remove-duplicates "heyyyy cooooool aaaaaa"
                   :test-not #'(lambda (x y)
                                 (or (char-not-equal x y)
                                     (and (char= x #\space)
                                          (char= y #\space)))))
The :from-end keyword parameter is not necessarily needed here and can be omitted.

More code examples to manipulate strings [subtype of sequence] can be found in:
- edgar

Re: remove-duplicates

Posted: Sun Jun 05, 2011 8:18 am
by I X Code X 1
Okay, that all makes sense now. Thanks!

Re: remove-duplicates

Posted: Sun Jun 05, 2011 1:48 pm
by findinglisp
edgar-rft wrote: I think the main reason why there are both :test and :test-not is that Lisp originally was designed by mathematicians and testing numerical results often requires heavy complicated :test functions and often it's easier to write a :test-not math-test to exclude the opposite of the desired numbers.
If you had a function FOO, but not NOT-FOO, then if all you had was :TEST and wanted NOT-FOO, you'd have to define it specifically for this purpose or use a LAMBDA form to create it. Both of these work, but are a bit clumsy. :TEST-NOT makes this simple and clean.

Note that if you use :TEST-NOT a lot, then you might be better off defining NOT-FOO and using it with :TEST, for readability.