The problem is at design-time: you choose key-value data structure for a non key-value entities storing. Strictly saying your entity should be something like this:
'(book . ((:title . "title") (:author . "author")))
but any instance of it will refer to the needless 'book
symbol. Another hand, you can build API for a work with anyone entity which looks like (name . description-alist)
. Ok, now we deal how to looks our data entity and can write simple API to use it:
(defun book-new (title author)
;; make new book entity such as
;; '(book ((:author . author) (:title . title)))
(let (description)
(push (cons :title title) description)
(push (cons :author author) description)
(cons 'book description)))
(defun bookp (data)
;; return true if our entity is book
(and (consp data)
(eq 'book (car data))))
(defun book-description (book)
;; return description of the book or nil
(cdr book))
(defun book-author (book)
;; return author of the book or nil
(and (bookp book)
(cdr (assoc :author (book-description book)))))
(defun book-title (book)
;; return title of the book or nil
(and (bookp book)
(cdr (assoc :title (book-description book)))))
Now you can build API to working with book
sequences:
(defparameter *books* nil)
(defun book-push (title author)
(push (book-new title author) *books*))
(defun book-filter (&key
(title nil title-supplied-p)
(author nil author-supplied-p)
(test #'equal))
(cond ((and title-supplied-p author-supplied-p)
(find-if #'(lambda (book)
(and (funcall test (book-title book) title)
(funcall test (book-author book) author)))
*books*))
(title-supplied-p
(find-if #'(lambda (book)
(funcall test (book-title book) title))
*books*))
(author-supplied-p
(find-if #'(lambda (book)
(funcall test (book-author book) author))
*books*))
(t *books*)))
Now you can use book-filter
with keys. Or without it, in this case function just return *books*
value. Let's try it:
;; from REPL add 3 new books
CL-USER> (mapcar #'(lambda (title author)
(book-push title author))
'("name 1" "name 2" "name 3")
'("author 1" "author 2" "author 3"))
...
CL-USER> *books*
((BOOK (:AUTHOR . "author 3") (:TITLE . "name 3"))
(BOOK (:AUTHOR . "author 2") (:TITLE . "name 2"))
(BOOK (:AUTHOR . "author 1") (:TITLE . "name 1")))
now try to find some book as we need:
CL-USER> (book-filter :test #'string= :title "name 3")
(BOOK (:AUTHOR . "author 3") (:TITLE . "name 3"))
CL-USER> (book-filter :test #'string= :title "name 3" :author "Carlos Castaneda")
NIL
CL-USER> (book-filter :test #'string= :title "name 3" :author "author 3")
(BOOK (:AUTHOR . "author 3") (:TITLE . "name 3"))
CL-USER> (book-filter :test #'string= :author "author 1")
(BOOK (:AUTHOR . "author 1") (:TITLE . "name 1"))
CL-USER> (book-filter)
((BOOK (:AUTHOR . "author 3") (:TITLE . "name 3"))
(BOOK (:AUTHOR . "author 2") (:TITLE . "name 2"))
(BOOK (:AUTHOR . "author 1") (:TITLE . "name 1")))
CL-USER>
It woks fine! Enjoy.