nunoreis
nunoreis

Reputation: 31

End of file (character) in Common Lisp

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

Answers (4)

nunoreis
nunoreis

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

ignis volens
ignis volens

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

Kaz
Kaz

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:

  1. We pass arguments to read so that it doesn't throw an error, but returns nil. We detect this nil to terminate the recursion.

  2. 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".

  3. 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

Martin Půda
Martin Půda

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

Related Questions