Subject: Re: File I/O & String conversion - CLISP
From: rpw3@rpw3.org (Rob Warnock)
Date: Tue, 21 Oct 2008 04:09:10 -0500
Newsgroups: comp.lang.lisp
Message-ID: <_uednXGU2pWrAWDVnZ2dnUVZ_iydnZ2d@speakeasy.net>
Jason  <jemeade@gmail.com> wrote:
+---------------
| Carla <crashoverride...@gmail.com> wrote:
| > I am trying with this version of my function.
...[trimmed]...
| > But this is just returning true, even after I am explicitly returning
| > main-list before the end of let block.
| 
| I changed the (return main-list) to simply return-list, and prefaced
| your push statements with nreverse, and the code works as expected.
| 
| (let ((in (open "file1.txt" :if-does-not-exist nil))
|       (main-list nil))
|   (when in
|     (loop for line = (read-line in nil)
|        while line do (let ((line-string line)
| 			   (string-form nil))
| 		       (loop for i from 1 to (length line-string)
| 			  do
| 			  (push (subseq line-string (1- i) i)
| 				string-form))
| 		       (push (nreverse string-form) main-list)))
|     (close in))
|   (nreverse main-list))
+---------------

Carla, now that Jason has debugged the proximate problem, we can
start working on style and using common CL idioms, especially
within the LOOPs.

First, there's a standard CL macro that takes care of opening and
closing a file for you, so instead of:

    (let ((in (open "file1.txt" :if-does-not-exist nil)))
      (let ((main-list nil))
	(when in
	  ...{body which PUSHes onto MAIN-LIST}...)
	(close in))
      main-list)

you can write this:

    (with-open-file (in "file1.txt" :if-does-not-exist nil)
      (let ((main-list nil))
        ...{body}...
        main-list))

Next, you can eliminate the LINE-STRING variable -- just use LINE,
it's still in scope.

Your LOOPs just *cry* out for using the COLLECT feature of the
LOOP macro instead of setting up results variables (MAIN-LIST,
STRING-FORM) and PUSH'ing stuff onto them. Plus, COLLECT keeps
things in forward order so you don't have to REVERSE (or NREVERSE)
them. First let's do the outer LOOP:

    (with-open-file (in "file1.txt" :if-does-not-exist nil)
      (loop for line = (read-line in nil)
	    while line
	 collect (let ((string-form nil))
		   (loop for i from 1 to (length line) do
		     (push (subseq line (1- i) i) string-form))
		   string-form)))

and now the inner:

    (with-open-file (in "file1.txt" :if-does-not-exist nil)
      (loop for line = (read-line in nil)
	    while line
	 collect (loop for i from 1 to (length line)
		   collect (subseq line (1- i) i))))

Finally, you can use the FOR...ACROSS array scanning feature
of LOOP to walk each LINE character by character, with STRING
to turn each character back into a string:

    (with-open-file (in "file1.txt" :if-does-not-exist nil)
      (loop for line = (read-line in nil)
	    while line
	 collect (loop for c across line collect (string c))))

And there's the 3-line solution I mentioned before
[well, 4 lines, with the addition of the WITH-OPEN-FILE].


-Rob

p.s. People differ it how much they like to compress vertical
whitespace. You can crunch the above to 3 lines this way:

    (with-open-file (in "file1.txt" :if-does-not-exist nil)
      (loop for line = (read-line in nil) while line
	 collect (loop for c across line collect (string c))))

though many people prefer a looser layout that's more easily
parsable by eye [and looks like the other LOOPs you're likely
to find in CL code]:

    (with-open-file (in "file1.txt" :if-does-not-exist nil)
      (loop for line = (read-line in nil)
	    while line
	 collect (loop for c across line
		   collect (string c))))

-----
Rob Warnock			<rpw3@rpw3.org>
627 26th Avenue			<URL:http://rpw3.org/>
San Mateo, CA 94403		(650)572-2607