Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I use assoc when the keys are strings?

I have a data set like this: '(("red" 3 5)("blue" 6 8)...)

Is it possible to use assoc when the keys are strings? None of the obvious attempts have worked for me in this simple test:

CL-USER> (defparameter ggg (list '("foot" 2) '(bar 5)))
GGG
CL-USER> ggg
(("foot" 2) (BAR 5))
CL-USER> (assoc 'bar ggg)
(BAR 5)
CL-USER> (assoc "foot" ggg)
NIL
CL-USER> (assoc '"foot" ggg)
NIL
CL-USER> (assoc 'foot ggg)
NIL
like image 875
johnbakers Avatar asked Dec 06 '13 12:12

johnbakers


1 Answers

If you are sure that your list contains only strings, you can use the type-specific functions string= (case sensitive) or string-equal (case insensitive).

However, these functions also accept symbols, and mixtures of symbols and strings.

Thus (assoc "ABC" list :test #'string=) will find not only the key "ABC" but also any symbol whose name is "ABC", such as the symbol :abc or cl-use:abc or mypackage:abc.

The generic equal and equalp functions for comparing any two objects do not have this behavior. Like the aforementioned two, equal and equalp are, respectively, case sensitive and insensitive. However, they also compare other kinds of objects.

Unlike string= and string-equal, equal and equalp do not consider strings and symbols to be equivalent; that is, (equalp "FOO" 'FOO) -> nil. They also do not consider symbols having the same name to be equivalent: (equalp 'foo :foo) -> nil. When both arguments are symbols, equal and equalp apply the same test as the eq function.

So I would argue that an appropriate test for your associative list, since you have a mixture of string and symbolic keys, is one of the two functions equal and equalp.

These functions will also allow your list to have other kinds of keys like numbers. equalp will compare numbers by value, so that 1 and 1.0 are the same key, whereas equal is tighter. Both these functions recurse into lists. The lists (1 2) and (1 2) are equal even if they are not the same object (separately consed), whereas (1 2) and (1 2.0) are not equal, but are equalp (unless you have a very weird floating-point system). Also vector objects are not compared element-by-element by equal, but they are by equalp.

Even if you had strings only in the list, it's still better to use these two functions. You are not going to get much, if any, performance benefit. string= still has to validate the types of the arguments to make sure they are supported types, and dispatch according to what combination of string and symbol the arguments are. equal dispatches according to numerous type possibilities, but this can be done efficiently.

Using overly type-specific functions, or inappropriately strict equality, as a matter of habit, is a poor practice in Lisp.

string= is used deliberately, however, not for saving machine cycles, but in situations when symbols must be compared as strings, or mixtures of symbols and strings. For instance, if you were implementing the loop macro, you might use string= for detecting the loop clause words, which according to the ANSI Common Lisp spec are treated as equivalent based on symbol name. Users can write (loop :for x below 42 ...) or (loop mypackage:for x below 42 ...). However (loop "FOR" ...) is not valid! So you could not rely only on string=; you'd have to validate that the clause word is a symbol.

like image 169
Kaz Avatar answered Oct 11 '22 00:10

Kaz