英文:
LISP find item in alist
问题
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.
英文:
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
答案1
得分: 8
你的数据结构不是关联列表。Alist是一对对的列表,所以你应该像这样创建它:
(defparameter *books* '(((:title . "Titulo 1")
(:autor . "Autor 1"))
((:title . "Titulo 2")
(:autor . "Autor 2"))))
注意三件事情:
- 你不应该使用
setf
来初始化全局变量-应该对全局变量使用defparameter
或defvar
,对局部变量使用let
。
这可以帮助你:defvar、defparameter、setf和setq之间的区别是什么?
- 你应该只使用一个
quote
,因为引用数据结构内的表达式也被引用。另外,这个调用(quote Autor 1)
没有任何意义-quote
只接受一个参数。
这可以帮助你:在Lisp中何时使用'(或quote)?
- 你应该正确格式化你的代码-包括括号。
我不会过多评论string-include
,但有些调用看起来很奇怪。为什么要调用 (string string1)
(将字符串转换为字符串)或 (string= string1 s :end2 (length string1))
(string=
比较字符串,但(length string1)
的结果将是数字)?你不仅应该编写更小且可测试的函数,还应该在REPL中不断测试你的代码,以确保一切按照你的期望工作。
那个buscar
函数看起来稍微好一些(但仍然包含一些奇怪的调用,比如 (format nil book)
你没有在之前进行测试),你可以将它更改为如下所示:
(defun buscar (books title)
(remove-if-not (lambda (book) (string= (cdr (assoc :title book)) title))
books))
测试:
> (buscar *books* "Titulo 1")
(((:TITLE . "Titulo 1") (:AUTOR . "Autor 1")))
或者你可以使用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"))
编辑: 或者 find-if
与 search
(部分匹配/子字符串):
(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"))
英文:
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 usedefparameter
ordefvar
for global variables andlet
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"))
答案2
得分: 3
你需要将你的代码分成更小的、可测试的函数,否则在编写代码时测试会变得困难。此外,对于(string-include string1 string2)
,我不清楚它的含义,添加一个注释或文档字符串到函数会帮助澄清它应该做什么,不仅对你自己,也对其他人有帮助。
更重要的是,当你提出问题时,说“没有一个测试起作用”有点不清楚:是解释器显示了一个错误吗?如果是的话,是哪一个错误?还是测试只是返回了NIL而没有提供更多的信息?
主要问题可能是你的书名被表示为符号或数字的列表,而你正在使用字符串函数。我的意思是:
(:title 'Titulo 1)
...会被你的解释器解释为(:title (quote Titulo) 1)
。你可以写下以下表达式来获得相同的形式:
(cons :title (cons (quote Titulo) (cons 1 nil)))
我的意思是,在你的情况下,你的cons单元将关键字:title
绑定到列表(Titulo 1)
,它有2个元素。如果你想要操作字符串,你需要将它们放在双引号之间,例如"Titulo 1"
。
还可以参考SEARCH
。
英文:
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
.
答案3
得分: 0
这部分内容是有关设计时问题的代码,涉及数据结构和API的问题。如果您有任何具体的问题或需要进一步的信息,请告诉我。
英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论