Can I use assoc when the keys are strings?

common-lisp, lisp

Solution

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.

Problem

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 ```

Original source