Re: Is there a better way?



In article <1i4nj72.va9wjk15wk1z8N%wrf3@xxxxxxxxxxxxxxx>,
wrf3@xxxxxxxxxxxxxxx (Bob Felts) wrote:

I'm writing some code that uses Zach Bean's wonderful SKIPPY library to
create gif files to graph some secret data. I need to map genus and
species to colors and put together the following table. The names have
been changed to protect the guilty. And it really isn't a biology
project, but species/genus works as an equivalent abstraction.

(defparameter *color-table*
'(("Species1" . ((nil . (nil . ( 0 0 0)))))
("Species2" . (("Genus1" . (nil . (255 255 255)))
("Genus2" . (nil . (255 255 0)))
("Genus3" . (nil . ( 67 167 241)))
("Genus4" . (nil . (255 0 255)))
("Genus5" . (nil . ( 0 255 255)))
(nil . (nil . (255 100 100)))))
("Species3" . (("Genus1" . (nil . (255 230 108)))
("Genus2" . (nil . (128 0 128)))
("Genus3" . (nil . ( 0 128 128)))
(nil . (nil . ( 0 255 0)))))
(nil . ((nil . (nil . (255 128 0)))))))

Make sure that you understand that above is a literal constant
and should NOT (!!!) be modified. It is not allowed in ANSI CL
to modify literal constants. If you want kind of
a list that you can modify, you need to create one
(using LIST, COPY-LIST, COPY-TREE, ...).


I picked this form since is makes finding Species/Genus easy with
assoc(). nil matches anything, which is handled by #'assoc-string (not
shown here).

It might be a bit more idiomatic to use symbols instead of strings.

Instead of data with list accessors (CAR, CDR) use
a data type described with DEFSTRUCT or DEFCLASS.
If you want to use lists, then use DEFSTRUCT with the list
option. Like here:

? (defstruct (foo (:type list)) a b)
FOO
? (make-foo :a 'bar :b "baz")
(BAR "baz")
? (foo-a *)
BAR
?



(defun map-color (species genus)
(car (cdr (assoc genus (cdr (assoc species *color-table* :test
#'assoc-string)) :test #'assoc-string))))

The inner assoc finds the species and the outer assoc finds the genus.
The outer cdr returns the (nil . (R G B)) pair, and the car returns the
nil. The nil was earlier initialized to be the index of the color in

CAR and CDR are bad data accessors. See above.

the color table:

(defun init-clut (clut)
(loop for species in *color-table*
do
(loop for genus in (rest species)
do
(let ((color-info (rest genus)))
(setf (car color-info)
(ensure-color (apply #'rgb-color (cdr color-info)) clut))))))

IF you use a primitive LOOP, DOLIST is as good.

Otherwise you can use LOOP like this:

(defun init-clut (clut)
(loop for (name . genus) in *color-table*
do (loop for (name . color-info) in genus
do (setf (car color-info)
(ensure-color (apply #'rgb-color (cdr color-info))
clut)))))

But then you have the implementation of your color table
in your other code.

I would write a function that maps a function over the
color-table. Similar to this usage:

(defun init-clut (clut)
(map-color-table-entries
(lambda (r g b)
(ensure-color (rgb-color r g b) clut))))




ensure-color is the SKIPPY function to make an RGB triplet into a color
table and return the index.

My guestion is: is there a better way to do this? I'm not quite happy
with using the (nil . (R G B)) constuct to handle associating a color
index with an RGB triplet, but I'm not sure why I don't like it. I
should be happy that everything is data driven and that the code to
handle colors is quite short, but something is making me restless.

Any comments?

See also

See the article "DEFTABLE: A Macro for Implementing Tables"
by Peter Norvig, in Lisp Pointers.
http://www-cgi.cs.cmu.edu/afs/cs/project/ai-repository/ai/lang/lisp/code/ext/tables/0.html
http://portal.acm.org/citation.cfm?id=382665

--
http://lispm.dyndns.org
.



Relevant Pages