I'm writing a program that will read information from a file, but when I try to read the information for some reason my count variable isn't being incremented.
(defun fill-lib()
(with-open-file (s-stream "/Users/David/Desktop/CS/CS_408/LISP/Books.txt"
:direction :input)
(loop
(cond((> count 1) (return "Library filled")))
(setf (aref *lib* count)
(make-instance 'book :title (read s-stream)
:author (read s-stream)
:genre (read s-stream)))
(setq count (+ count 1)))))
I have a feeling its because I'm not using the loop properly but I'm not entirely sure how I could fix that.
The way you have implemented the loop, only one book will ever be added to *lib*. This is because you are explicitly terminating the loop when count exceeds 1, i.e. after the first book entry has been read from the input file:
(cond((> count 1) (return "Library filled")))
Instead of checking a counter, I guess I would add a small function whose sole purpose is to read one book entry from a stream, and that function would indicate to the caller when there is no input left. (Or you could exit from the loop when a book-title with a nil value is detected. The best approach depends on the structure of the input data, and on the level of robustness you are after, of course.)
Here is a rough variation of your code, using the approach of adding a function which is dedicated to reading a single book entry from the input:
(defstruct book
(title nil)
(author nil)
(genre nil))
(defun read-book(s)
(make-book :title (read-line s nil :eof)
:author (read-line s nil :eof)
:genre (read-line s nil :eof)))
(defun fill-lib ()
(let ((lib ()))
(with-open-file (s "/tmp/Books.txt" :direction :input)
(loop for book = (read-book s)
until (eq (book-title book) :eof) do
(push book lib)))
lib))
(print (fill-lib))
Related
I started learning LISP last night and I am currently writing a text-based Hotel Room Bookings system in common LISP. These are the lines where I have initialized my lists and vars :
(defparameter *rooms* (list 0))
(defvar counter 0)
(defvar room-num 0)
The following is the piece of code where I am manipulating these to search through my list :
(loop
(setq room-num(read))
(if (and
(> counter 0)(equal t (find room-num '(*room*)))
)
(progn
(print "Room already exists:")
(return 1)
)
)
(push room-num (cdr (last *rooms*)))
(setq counter (+ counter 1))
)
The above code is appending room-num to the room list if it does not already exist in the list.
The problem I have is with finding room-num in room list.
Following is what I have tried (Sorry if the code pretty sloppy. As I said I started working with LISP just yesterday) :
(if (and (> counter 0)(equal (member room-num *room*)))
Also tried :
(if ((if (member room-num '(rooms)) t nil))
(print "Room already exists")
)
Any help would be appreciated.
also, in addition to #coredump 's answer, you could also use some existing common lisp facilities, like this (employing the loop macro):
(loop for counter from 0
for room = (read)
until (find room rooms)
collect room into rooms
finally (progn (format t "room ~a already exists in ~a~%" room rooms)
(return (values rooms counter))))
or this (employing do):
(let ((rooms))
(do ((counter 0 (1+ counter))
(room (read) (read)))
((find room rooms)
(format t "room ~a already exists in ~a~%" room rooms)
(values rooms counter))
(push room rooms)))
although it is not an answer, approaching to a problem using high level facilities can save plenty of debugging time.
This declaration is fine:
(defparameter *rooms* (list 0))
The following ones are not great:
(defvar counter 0)
(defvar room-num 0)
Special variables declared with defvar should be named with earmuffs, ie. a pair of asterisks, just like you did for the previous variable.
It is helpful when reading code to know that some variables are globals.
Also, you are writing a script that manipulates global state, instead of defining a function that only modifies a local state. For a small example this is ok, but a good exercise would be to encapsulate this script in a function.
(loop
(setq room-num(read))
(if (and
(> counter 0)(equal t (find room-num '(*room*)))
)
(progn
(print "Room already exists:")
(return 1)
)
)
(push room-num (cdr (last *rooms*)))
(setq counter (+ counter 1))
)
The formatting is unconventional, please follow an idiomatic style.
Here is your code reformatted:
(loop
(setq room-num (read))
(when (and (> counter 0)
(equal t (find room-num '(*room*))))
(print "Room already exists:")
(return 1))
(push room-num (cdr (last *rooms*)))
(setq counter (+ counter 1)))
I replaced (if A (progn B C)) by (when A B C), which is simpler to read.
Now, your problem is here:
(equal t (find room-num '(*room*)))
You are trying to find room-num in a list that literally contains the symbol *room*, and not the value of the variable named *room*.
This is because you quoted the list: '(*room*) is the same as (quote (*room*)), which when evaluated just return the form as read by the Lisp reader, i.e. a list of one symbol.
You just need to call (member room-num *room*) to test membership, and you do not have to compare the return value of (member ...) with t, using (equal t ...): if the test succeeds, it will be non-null, ie. true.
Also:
(push room-num (cdr (last *rooms*)))
You do not need to push the room as the last element, just put it in front with:
(push room *rooms*)
Order does not matter in your case anyway, and you avoid one traversal of the list for nothing with last.
This function resets the value for each of the specified parts of speech to NIL using putp.
The first argument is a hashtable, for this example, lets call it word-dict.
There are a variable number of parts of speech passed to resetPartsOfSpeech.
Example:
(resetPartsOfSpeech word-dict 'subject 'verb 'prep 'directObj)
(defun resetPartsOfSpeech(word-dict &rest parts)
(do ((partsVar parts (cdr partsVar)))
( (null partsVar) T)
;;; procces the car
(putp NIL word-dict (car partsVar))
))
; here is the results of the function
#S(HASH-TABLE :TEST FASTHASH-EQL (NIL . VERB) (LICKED . VERB) (HAS . VERB) (ATE . VERB) (RAN . VERB) (TAUGHT . VERB)
As you see, it only adds the NIL variable to the list, not clearing them all out.
Helper Functions I Have, The job of these two functions is to put and get data from the hashtable created.
; creating the hash table
(setf word-dict (MAKE-HASH-TABLE))
(defun putp (symbol ht value)
(if (ATOM symbol)
(setf (gethash symbol ht) value)
(ERROR "~s is not a valid symbol for putp" symbol)
))
(defun getp (symbol ht)
(gethash symbol ht) )
(defun isa(word partOfSpeech) ; this function returns T if the specified word is that specified partOfSpeech,
; otherwise, NIL is returned.
(eql (getp word word-dict) partOfSpeech))
(defun set_isa (partOfSpeech &rest words) ; this function defines each
word in the list of words to the specified partOfSpeech
; in the dictionary (hard code word-dict).
(do ((wordVar words (cdr wordVar)) )
( (NULL wordVar ) T)
;;; proccess the CAR
(putp (car wordVar) word-dict partOfSpeech)
(print (car wordVar))))
What I am having trouble understanding is that how I should go about itterating thru each value in the hash table. What I was considering was doing a nested do or dolist loop but can't quite figure out how to go about that with the values from the table, or if that is even possible.
The fundamental problem is with:
(putp NIL word-dict (car partsVar))
When putp is called, nil is bound to symbol, word-dict is bound to ht, and (car partsVar), i.e. the next symbol in the list of parts of speech, is bound to value. Within putp the expression:
(setf (gethash symbol ht) value)
becomes:
(setf (gethash 'nil word-dict) (car partsVar))
Here, (gethash 'nil word-dict) is the place that is set to the value (car partsVar). Since there is no 'nil key in the hash table yet, a new key is created and given the value (car partsVar), which is 'verb in the OP example.
In the original putp expression, (car partsVal) should have been in the symbol position as that is the key which should be updated:
(defun resetPartsOfSpeech (word-dict &rest parts)
(do ((partsVar parts (cdr partsVar)))
((null partsVar) t)
(putp (car partsVar) word-dict 'nil)))
Although this solves the problem, there is a better solution.
(defun reset-parts-of-speech (word-dict &rest parts)
(dolist (part parts)
(putp part word-dict 'nil)))
When you want to do a simple iteration over a list of elements, symbols for parts of speech in this case, just use a simple dolist. Additionally, it would be good to get into better habits with respect to Lisp style. Prefer kebab-case to camel-case; put all closing parentheses on one line (almost always); use proper indentation to make program structure clear. A good lisp-aware text editor can be most helpful for the last two.
Here is some testing in the REPL using a set-isa function based on the previous question by OP:
SCRATCH> (defvar *word-dict* (make-hash-table))
*WORD-DICT*
SCRATCH> (set-isa 'verb 'eat 'sleep 'walk)
NIL
SCRATCH> (set-isa 'noun 'cake 'ice-cream 'pizza)
NIL
SCRATCH> (gethash 'verb *word-dict*)
(WALK SLEEP EAT)
T
SCRATCH> (gethash 'noun *word-dict*)
(PIZZA ICE-CREAM CAKE)
T
SCRATCH> (set-isa 'adjective 'delicious 'sweet 'crispy)
NIL
SCRATCH> (gethash 'adjective *word-dict*)
(CRISPY SWEET DELICIOUS)
T
SCRATCH> (resetPartsOfSpeech *word-dict* 'verb)
T
SCRATCH> (gethash 'verb *word-dict*)
NIL
T
SCRATCH> (gethash 'noun *word-dict*)
(PIZZA ICE-CREAM CAKE)
T
SCRATCH> (reset-parts-of-speech *word-dict* 'adjective 'noun)
NIL
SCRATCH> (gethash 'noun *word-dict*)
NIL
T
SCRATCH> (gethash 'adjective *word-dict*)
NIL
T
Update
The above was predicated on OP statement: "This function resets the value for each of the specified parts of speech to NIL...," which seemed to suggest that OP wants the hash table to store parts of speech as keys and lists of words as the associated values. This also seems to align with a previous question posted by OP. But, after an exchange of comments, it seems that OP may prefer a hash table with individual words as keys and parts of speech as the associated values. It is unclear how words which may be associated with multiple parts of speech should be handled.
The hash table shown in OP example code #S(HASH-TABLE :TEST FASTHASH-EQL (NIL . VERB) (LICKED . VERB) ;..., along with OP comments, supports this second interpretation. If this is the case, then what does it mean to "reset each value" in the hash table to 'nil? Perhaps the sensible thing to do is to remove each entry entirely which has a value that matches a provided part-of-speech argument.
This can easily be accomplished by using dolist to loop over the list of parts of speech, and subsequently mapping over the hash table with maphash and a function that removes any entry holding a matching value:
(defun remove-parts-of-speech (word-dict &rest parts)
(dolist (part parts)
(maphash #'(lambda (k v) (if (eql v part) (remhash k word-dict)))
word-dict)))
Here is another REPL demonstration using OP's current set-isa function which populates a hash table with words for keys and parts of speech for values. After populating the hash table with nine words which are 'nouns, 'verbs, and 'adjectives, the remove-parts-of-speech function is used to remove all entries which are nouns or verbs from *word-dict*. After this, only the three adjective entries remain in the hash table.
CL-USER> (defvar *word-dict* (make-hash-table))
*WORD-DICT*
CL-USER> (set-isa 'verb 'run 'jump 'climb)
RUN
JUMP
CLIMB
T
CL-USER> (set-isa 'noun 'hat 'shoe 'scarf)
HAT
SHOE
SCARF
T
CL-USER> (set-isa 'adjective 'salty 'spicy 'sour)
SALTY
SPICY
SOUR
T
CL-USER> *word-dict*
#<HASH-TABLE :TEST EQL :COUNT 9 {1003CE10C3}>
CL-USER> (hash-table-count *word-dict*)
9
CL-USER> (remove-parts-of-speech *word-dict* 'noun 'verb)
NIL
CL-USER> (hash-table-count *word-dict*)
3
CL-USER> (gethash 'spicy *word-dict*)
ADJECTIVE
T
In chapter 4 of the book, Successful Lisp, by David B. Lamkins,
there is a simple application to keep track of bank checks.
https://dept-info.labri.fr/~strandh/Teaching/MTP/Common/David-Lamkins/chapter04.html
At the end, we write a macro that will save and restore functions.
The problem occurs when I execute the reader function and the value to read is the hash table.
The macro to save and restore functions is:
(defmacro def-i/o (writer-name reader-name (&rest vars))
(let ((file-name (gensym))
(var (gensym))
(stream (gensym)))
`(progn
(defun ,writer-name (,file-name)
(with-open-file (,stream ,file-name
:direction :output :if-exists :supersede)
(dolist (,var (list ,#vars))
(declare (special ,#vars))
(print ,var ,stream))))
(defun ,reader-name (,file-name)
(with-open-file (,stream ,file-name
:direction :input :if-does-not-exist :error)
(dolist (,var ',vars)
(set ,var (read ,stream)))))
t)))
Here is my hash table and what is going on:
(defvar *payees* (make-hash-table :test #'equal))
(check-check 100.00 "Acme" "Rocket booster T-1000")
CL-USER> *payees*
#<HASH-TABLE :TEST EQUAL :COUNT 0 {25520F91}>
CL-USER> (check-check 100.00 "Acme" "T-1000 rocket booster")
#S(CHECK
:NUMBER 100
:DATE "2020-4-1"
:AMOUNT 100.0
:PAID "Acme"
:MEMO "T-1000 rocket booster")
CL-USER> (def-i/o save-checks charge-checks (*payees*))
T
CL-USER> (save-checks "/home/checks.dat")
NIL
CL-USER> (makunbound '*payees*)
*PAYEES*
CL-USER> (load-checks "/home/checks.dat")
; Evaluation aborted on #<SB-INT:SIMPLE-READER-ERROR "illegal sharp macro character: ~S" {258A8541}>.
In Lispworks, the error message is:
Error: subcharacter #\< not defined for dispatch char #\#.
To simplify, I get the same error if I execute:
CL-USER> (defvar *payees* (make-hash-table :test #'equal))
*PAYEES*
CL-USER> (with-open-file (in "/home/checks.dat"
:direction :input)
(set *payees* (read in)))
; Evaluation aborted on #<SB-INT:SIMPLE-READER-ERROR "illegal sharp macro character: ~S" {23E83B91}>.
Can someone explain to me where the problem is coming from, and what I need to fix in my code for this to work.
Thank you in advance for the explanations you can give me and the help you can give me.
Values which cannot be read back are printed with #<, see Sharpsign Less-Than-Sign. Common Lisp does not define how hash-tables should be printed readably (there is no reader syntax for hash tables):
* (make-hash-table)
#<HASH-TABLE :TEST EQL :COUNT 0 {1006556E53}>
The example in the book is only suitable to be used with the bank example that was previously shown, and is not intended to be a general-purpose serialization mechanism.
There are many ways to print a hash-table and read them back, depending on your needs, but there is no default representation. See cl-store for a library that store arbitrary objects.
Custom dump function
Let's write a form that evaluate to an equivalent hash-table; let's define a generic function named dump, and a default method that simply returns the object as-is:
(defgeneric dump (object)
(:method (object) object))
Given a hash-table, we can serialize it as a plist (sequence of key/value elements in a list), while also calling dump on keys and values, in case our hash-tables contain hash-tables:
(defun hash-table-plist-dump (hash &aux plist)
(with-hash-table-iterator (next hash)
(loop
(multiple-value-bind (some key value) (next)
(unless some
(return plist))
(push (dump value) plist)
(push (dump key) plist)))))
Instead of reinventing the wheel, we could also have used hash-table-plist from the alexandria system.
(ql:quickload :alexandria)
The above is equivalent to:
(defun hash-table-plist-dump (hash)
(mapcar #'dump (alexandria:hash-table-plist hash)))
Then, we can specialize dump for hash-tables:
(defmethod dump ((hash hash-table))
(loop
for (initarg accessor)
in '((:test hash-table-test)
(:size hash-table-size)
(:rehash-size hash-table-rehash-size)
(:rehash-threshold hash-table-rehash-threshold))
collect initarg into args
collect `(quote ,(funcall accessor hash)) into args
finally (return
(alexandria:with-gensyms (h k v)
`(loop
:with ,h := (make-hash-table ,#args)
:for (,k ,v)
:on (list ,#(hash-table-plist-dump hash))
:by #'cddr
:do (setf (gethash ,k ,h) ,v)
:finally (return ,h))))))
The first part of the loop computes all the hash-table properties, like the kind of hash function to use or the rehash-size, and builds args, the argument list for a call to make-hash-table with the same values.
In the finally clause, we build a loop expression (see backquotes) that first allocates the hash-table, then populates it according to the current values of the hash, and finally return the new hash. Note that the generated code does not depend on alexandria, it could be read back from another Lisp system that does not have this dependency.
Example
CL-USER> (alexandria:plist-hash-table '("abc" 0 "def" 1 "ghi" 2 "jkl" 3)
:test #'equal)
#<HASH-TABLE :TEST EQUAL :COUNT 4 {100C91F8C3}>
Dump it:
CL-USER> (dump *)
(LOOP :WITH #:H603 := (MAKE-HASH-TABLE :TEST 'EQUAL :SIZE '14 :REHASH-SIZE '1.5
:REHASH-THRESHOLD '1.0)
:FOR (#:K604 #:V605) :ON (LIST "jkl" 3 "ghi" 2 "def" 1 "abc"
0) :BY #'CDDR
:DO (SETF (GETHASH #:K604 #:H603) #:V605)
:FINALLY (RETURN #:H603))
The generated data is also valid Lisp code, evaluate it:
CL-USER> (eval *)
#<HASH-TABLE :TEST EQUAL :COUNT 4 {100CD5CE93}>
The resulting hash is equalp to the original one:
CL-USER> (equalp * ***)
T
I'm reading a file char by char and constructing a list which is consist of list of letters of words. I did that but when it comes to testing it prints out NIL. Also outside of test function when i print out list, it prints nicely. What is the problem here? Is there any other meaning of LET keyword?
This is my read fucntion:
(defun read-and-parse (filename)
(with-open-file (s filename)
(let (words)
(let (letter)
(loop for c = (read-char s nil)
while c
do(when (char/= c #\Space)
(if (char/= c #\Newline) (push c letter)))
do(when (or (char= c #\Space) (char= c #\Newline) )
(push (reverse letter) words)
(setf letter '())))
(reverse words)
))))
This is test function:
(defun test_on_test_data ()
(let (doc (read-and-parse "document2.txt"))
(print doc)
))
This is input text:
hello
this is a test
You're not using let properly. The syntax is:
(let ((var1 val1)
(var2 val2)
...)
body)
If the initial value of the variable is NIL, you can abbreviate (varN nil) as just varN.
You wrote:
(let (doc
(read-and-parse "document2.txt"))
(print doc))
Based on the above, this is using the abbreviation, and it's equivalent to:
(let ((doc nil)
(read-and-parse "document2.txt"))
(print doc))
Now you can see that this binds doc to NIL, and binds the variable read-and-parse to "document2.txt". It never calls the function. The correct syntax is:
(let ((doc (read-and-parse "document2.txt")))
(print doc))
Barmar's answer is the right one. For interest, here is a version of read-and-parse which makes possibly-more-idiomatic use of loop, and also abstracts out the 'is the character white' decision since this is something which is really not usefully possible in portable CL as the standard character repertoire is absurdly poor (there's no tab for instance!). I'm sure there is some library available via Quicklisp which deals with this better than the below.
I think this is fairly readable: there's an outer loop which collects words, and an inner loop which collects characters into a word, skipping over whitespace until it finds the next word. Both use loop's collect feature to collect lists forwards. On the other hand, I feel kind of bad every time I use loop (I know there are alternatives).
By default this collects the words as lists of characters: if you tell it to it will collect them as strings.
(defun char-white-p (c)
;; Is a character white? The fallback for this is horrid, since
;; tab &c are not a standard characters. There must be a portability
;; library with a function which does this.
#+LispWorks (lw:whitespace-char-p c)
#+CCL (ccl:whitespacep c) ;?
#-(or LispWorks CCL)
(member char (load-time-value
(mapcan (lambda (n)
(let ((c (name-char n)))
(and c (list c))))
'("Space" "Newline" "Page" "Tab" "Return" "Linefeed"
;; and I am not sure about the following, but, well
"Backspace" "Rubout")))))
(defun read-and-parse (filename &key (as-strings nil))
"Parse a file into a list of words, splitting on whitespace.
By default the words are returned as lists of characters. If
AS-STRINGS is T then they are coerced to strings"
(with-open-file (s filename)
(loop for maybe-word = (loop with collecting = nil
for c = (read-char s nil)
;; carry on until we hit EOF, or we
;; hit whitespace while collecting a
;; word
until (or (not c) ;EOF
(and collecting (char-white-p c)))
;; if we're not collecting and we see
;; a non-white character, then we're
;; now collecting
when (and (not collecting) (not (char-white-p c)))
do (setf collecting t)
when collecting
collect c)
while (not (null maybe-word))
collect (if as-strings
(coerce maybe-word 'string)
maybe-word))))
I'm running into a name collision with iterate and count standard function in the example below:
(defun svs-to-images (file)
(with-open-file (stream file)
(iterate:iter
(iterate:for line #:= (read-line stream nil nil))
(iterate:while line)
(line-to-image
(iterate:iter
(iterate:for c #:in-string line)
(iterate:with word)
(iterate:with pos #:= 0)
(iterate:with result #:= ; ---------\/ here
(make-array (list (1+ (count #\, line)))
:element-type 'fixnum))
(if (char= c #\,)
(setf (aref result pos)
(parse-integer
(coerce (reverse word) 'string))
pos (1+ pos)
word nil)
(setf word (cons c word)))
(iterate:finally result)) 28))))
The error I'm getting is:
csv-parser.lisp:19:5:
error:
during macroexpansion of
(ITERATE:ITER
(ITERATE:FOR LINE #:= ...)
(ITERATE:WHILE LINE)
...).
Use *BREAK-ON-SIGNALS* to intercept:
Iterate, in (COUNT , LINE):
Missing value for LINE keyword
Compilation failed.
And, if I understood it correctly, it is trying to use count as if it was the count driver from iterate, instead of the original function. How would I make it so that the correct count is used?
In comp.lang.lisp Chris Riesbeck offered this as a workaround for a similar question a few years ago:
(remprop 'count 'iter::synonym)
From then you need to use COUNTING as the iterate clause. CL:COUNT then should refer to the Common Lisp function. You would need to recompile the code.
This is a bug/feature of how iterate processes its body.
You can use a version of iterate from rutils - it uses keywords instead of plain symbols, so there will be no symbol clashes.