0

I've got a a-list like

(setf *books* '(
        (  
            (:title 'Titulo 1)
            (:autor (quote Autor 1) )
        ) 
        (
            (:title 'Título 2)
            (:autor (quote Autor 2) )
        )
       ; (...) 
))

I need to create a function that find a book based on its title. After reading the docs I try:

(defun string-include (string1 string2)
  (let* ((string1 (string string1)) (length1 (length string1)))
    (if (zerop length1)
        nil 
        (labels ((sub (s)
                   (cond
                    ((> length1 (length s)) nil)
                    ((string= string1 s :end2 (length string1)) string1)
                    (t (sub (subseq s 1))))))
          (sub (string string2)))))
)
(
    defun buscar (books text)
        (remove-if-not 
                (lambda (book)
                   ;(search cadena (assoc :title book))
                   ;(string-include (string (cddr (assoc :title book))) cadena)
                   (string= (format nil (cddr (assoc :title book))) (format nil book))
                )
        books)
)

I can't find the book, what am I doing wrong?

Note that none of the tests I have done (commented) have worked

Jose A. Matarán
  • 1,044
  • 3
  • 13
  • 33

3 Answers3

8

Your data structure isn't an associative list. Alist is a list of pairs, so you should create it like this:

(defparameter *books* '(((:title . "Titulo 1")
                         (:autor . "Autor 1"))
                        ((:title . "Titulo 2")
                         (:autor . "Autor 2"))))

Note three things:

  • You shouldn't use setf to initialize a global variable- you should use defparameter or defvar for global variables and let for local ones.

This can help you: What's difference between defvar, defparameter, setf and setq

  • You should use only one quote, because expressions inside quoted data structure are also quoted. Also, this call (quote Autor 1) doesn't make any sense- quote is called with only one argument.

This can help you: When to use ' (or quote) in Lisp?

  • You should format your code properly- including parentheses.

I won't comment string-include so much- but some calls seem strange. Why do you call (string string1) (converting string to string) or (string= string1 s :end2 (length string1)) (string= compares strings, but result of (length string1) will be number)? You should not only write smaller and testable functions, but also test your code continuously in REPL to make sure everything works as you expect.

That buscar function seems a little bit better (but it still contains some strange calls like (format nil book) you didn't test before) and you can change it into something like this:

(defun buscar (books title)
  (remove-if-not (lambda (book) (string= (cdr (assoc :title book)) title))
                 books))

Test:

> (buscar *books* "Titulo 1")
(((:TITLE . "Titulo 1") (:AUTOR . "Autor 1")))

Or you can use find:

(defun search-by-title (books title)
  (find title books :test #'string= :key (lambda (book) (cdr (assoc :title book)))))

> (search-by-title *books* "Titulo 1")
((:TITLE . "Titulo 1") (:AUTOR . "Autor 1"))

EDIT: Or find-if with search (partial match/ substring):

(defun search-by-title (books title)
  (find-if (lambda (book) (search title (cdr (assoc :title book))))
           books))

> (search-by-title *books* "2")
((:TITLE . "Titulo 2") (:AUTOR . "Autor 2"))
Martin Půda
  • 7,353
  • 2
  • 6
  • 13
3

You have to split your code into smaller, testable functions, otherwise you are going to have difficulties testing along as you code. Furthermore, it is not clear to me what (string-include string1 string2) means, adding a comment above or a documentation string to the function would help clarify, for you and others, what the function should be doing.

More importantly, when you ask a question, saying "none of the tests have worked" is a bit unclear: did the interpreter show an error, and that case, which one? or did the test just return NIL without no further information?

The main problem is likely that your book titles are expressed as lists of symbols or numbers, whereas you are using string functions. What I mean is that:

(:title 'Titulo 1)

... is read by your interpreter as (:title (quote Titulo) 1). You can write the following expression to obtain the same form:

(cons :title (cons (quote Titulo) (cons 1 nil)))

What I am saying is that in your case your a cons-cell binds the keyword :title to the list (Titulo 1), which has 2 elements. If you want to manipulate strings instead, you would need to write them between double-quotes, for example "Titulo 1".

See also SEARCH.

coredump
  • 37,664
  • 5
  • 43
  • 77
0

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.

Hurry Blob
  • 130
  • 5