Reputation: 31
Basically im trying to read lines from a file recursively (because i can't use any loop cycle), but i don't know where the file ends.
This is my function:
;; (get-problemas 0)
(defun get-problemas (indice &optional file (problemas '()))
(cond
((null file) (with-open-file (open "C:/Users/nunor/Desktop/problemas.dat" :direction :input :if-does-not-exist nil)
(get-problemas (1+ indice) open (cons (read open) nil))
)
)
(t (cond
((= indice 6) problemas)
(t (get-problemas (1+ indice) file (append problemas (cons (read file) nil))))
)
)
)
)
I'm using a counter 'indice' to stop de recursion because i dont kow how to stop when i reached the end of the file.
And i'm putting the lists that the file contains in to a list called 'problemas'.
The file looks like this:
(a (((0 0 0) (0 0 1) (0 1 1) (0 0 1)) ((0 0 0) (0 1 0) (0 0 1) (0 1 1))) 3)
(b (((0 0 0) (0 0 1) (0 1 1) (0 0 1)) ((0 0 0) (0 1 0) (0 0 1) (0 1 1))) 3)
(c (((0 0 0) (0 0 1) (0 1 1) (0 0 1)) ((0 0 0) (0 1 0) (0 0 1) (0 1 1))) 3)
(d (((0 0 0) (0 0 1) (0 1 1) (0 0 1)) ((0 0 0) (0 1 0) (0 0 1) (0 1 1))) 3)
(e (((0 0 0) (0 0 1) (0 1 1) (0 0 1)) ((0 0 0) (0 1 0) (0 0 1) (0 1 1))) 3)
(f (((0 0 0) (0 0 1) (0 1 1) (0 0 1)) ((0 0 0) (0 1 0) (0 0 1) (0 1 1))) 3)
I hope you can help me.
Upvotes: 1
Views: 268
Reputation: 31
I managed to solve my problem. To know if i reached the end of the file i used "(read file nil 'eof)", if it reached the end of the file 'line' is going to be 'eof, and in cond i verify if 'line' is equal to 'eof so the recursion can stop.
This is how my function looks like now:
(defun get-problemas (&optional file (problemas '()))
(cond
((null file) (with-open-file (open "C:/Users/nunor/Desktop/problemas.dat" :direction :input :if-does-not-exist nil)
(get-problemas open (cons (read open) nil))
)
)
(t (let
(
(line (read file nil 'eof))
)
(cond
((eq line 'eof) problemas)
(t (get-problemas file (append problemas (cons line nil))))
)
)
)
)
)
Thank you for your help.
Upvotes: 1
Reputation: 9282
Rather than all the hair about the file argument it's natural to split this into two functions. One deals with opening the file:
(defun get-problemas (&optional (file "C:/Users/nunor/Desktop/problemas.dat"))
(with-open-file (in file :direction :input)
(with-standard-io-syntax
(let ((*read-eval* nil))
(get-problemas/accumulate in '())))))
Note this uses with-standard-io-syntax
and binds *read-eval*
to nil which are both elementary safety precautions which far too few Lisp programmers use.
The second, recursive, function builds the list of problems. It uses a trick which also seems to be unknown to too many Lisp programmers: to detect the end of file you return the stream itself since this is an object which can't (without great heroics) be in data read from the file:
(defun get-problemas/accumulate (in accumulation)
(let ((got (read in nil in)))
(if (eql got in)
(reverse accumulation)
(get-problemas/accumulate in (cons got accumulation)))))
Upvotes: 3
Reputation: 58617
This works for me:
(defun get-problemas (&optional file (problemas nil))
(if file
(let ((prob (read file nil)))
(if prob
(get-problemas file (cons prob problemas))
(nreverse problemas)))
(with-open-file (stream (open "problemas.dat" :direction :input))
(get-problemas stream))))
Notes:
We pass arguments to read
so that it doesn't throw an error, but returns nil
. We detect this nil
to terminate the recursion.
Your tail recursion with explicit accumulator is good; I improved it by avoiding append
and accumulating the output in reverse. When we terminate the recursion, we nreverse
the reversed list of "problemas".
I got rid of the :if-does-not-exist nil
. If the file doesn't exist, we want to bail, and not recurse.
Upvotes: 3
Reputation: 7576
Look at some solutions that use loop
and rewrite them into recursion. Take for example this one:
(defun get-file (filename)
(with-open-file (stream filename)
(loop for line = (read-line stream nil)
while line
collect line)))
Note the usage of (read-line stream nil)
, which returns nil
at the end of the file. You can just repeatedly call it and save the result of each call until you will get nil
:
(defun read-until-null (f)
(let ((result (read-line f nil)))
(unless (null result)
(cons result (read-until-null f)))))
(defun file-to-lines (path)
(with-open-file (f path :direction :input :if-does-not-exist nil)
(read-until-null f)))
Upvotes: 3