See below. These functions have been created a thousand times and are
trivial, but I wrote these instances myself and am a pretty proud
beginner. What do you think?
A week ago recursion was still pretty hazy, but a after reading "Common
Lisp, An Interactive Approach" the progress has been steady. What a
great book.
;;;;;;;;;;;;;;;;
(defpackage :string-utils
(:use :common-lisp))
(in-package :string-utils)
(defun line-split (s d)
"Return a list of substrings in string S when delimited by delimiter D."
(check-type s string)
(check-type d character)
(if (= (length s) 0) '()
(cons (getf (line-split1 s d) :string)
(line-split (getf (line-split1 s d) :remainder) d))))
(defun line-split1 (s d)
"Helper function for LINE-SPLIT. Return a list with elements of
supplied string S up to the first instance of delimiter D as :STRING and
the remaining string after the delimiter as :REMAINDER."
(if (position d s) (list :string (subseq s 0 (position d s))
:remainder (subseq s (1+ (position d s))))
(list :string s :remainder "")))
(defun read-file (filename delimiter)
"Return a list of lists containing strings in pathname FILENAME
delimited by character DELIMITER."
(let ((output '()))
(with-open-file (in filename)
(do ((line (read-line in nil) (read-line in nil)))
((null line))
(push (line-split line delimiter) output))
(nreverse output))))
(defmacro join-strings (&rest strings)
"Return the result of concatenating the supplied strings."
`(concatenate 'string ,@strings))
(defun rjust (s w)
"Right justify string S by W spaces."
(rjust1 s (- w (length s))))
(defun rjust1 (s w)
"Helper function for RJUST."
(if (<= w 0) s
(join-strings (rjust1 s (1- w)) " ")))
(defun ljust (s w)
"Left justify string S by W spaces."
(ljust1 s (- w (length s))))
(defun zfill (s w)
"Left justify string S by W 0 characters."
(ljust1 s (- w (length s)) "0"))
(defun ljust1 (s w &optional (c " "))
"Helper function for LJUST."
(if (<= w 0) s
(join-strings c (ljust1 s (1- w) c))))
On 2005-09-30, crystal1 <········@spamless.net> wrote:
> (defmacro join-strings (&rest strings)
> "Return the result of concatenating the supplied strings."
> `(concatenate 'string ,@strings))
I'm just going to comment on this one stylistic point -- only use a
macro when you need to control the evaluation of arguments, never in a
case like this where an ordinary function would do. The reason is you
can't use a macro in REDUCE, APPLY, or any other context where you need
to treat it as a first-class value.
crystal1 wrote:
> See below. These functions have been created a thousand times and are
> trivial, but I wrote these instances myself and am a pretty proud
> beginner. What do you think?
Fun, right? What languages have you used before?
>
> A week ago recursion was still pretty hazy, but a after reading "Common
> Lisp, An Interactive Approach" the progress has been steady. What a
> great book.
>
> ;;;;;;;;;;;;;;;;
>
> (defpackage :string-utils
> (:use :common-lisp))
>
> (in-package :string-utils)
>
> (defun line-split (s d)
> "Return a list of substrings in string S when delimited by delimiter D."
> (check-type s string)
> (check-type d character)
> (if (= (length s) 0) '()
> (cons (getf (line-split1 s d) :string)
> (line-split (getf (line-split1 s d) :remainder) d))))
>
> (defun line-split1 (s d)
> "Helper function for LINE-SPLIT. Return a list with elements of
> supplied string S up to the first instance of delimiter D as :STRING and
> the remaining string after the delimiter as :REMAINDER."
> (if (position d s) (list :string (subseq s 0 (position d s))
> :remainder (subseq s (1+ (position d s))))
> (list :string s :remainder "")))
That is a clever way of returning more than one value, but you might be
interested in a simpler approach:
(if (position d s)
(values (subseq s 0 (position d s))
(subseq s (1+ (position d s))))
s)
Then in the caller:
(multiple-value-bind (segment remainder)
(line-split1...)
<do something clever with segment and remainder>)
Note that m-v-b is OK with line-split1 not returning a second value on
the last segment, and that I feel nil is a better way in Lisp to denote
"there is no remainder". Much easier to test for in client code.
--
Kenny
Why Lisp? http://wiki.alu.org/RtL_Highlight_Film
"I've wrestled with reality for 35 years, Doctor, and I'm happy to state
I finally won out over it."
Elwood P. Dowd, "Harvey", 1950
crystal1 <········@spamless.net> writes:
>
> See below. These functions have been created a thousand times and are
> trivial, but I wrote these instances myself and am a pretty proud
> beginner. What do you think?
Petty cool.
If you don't mind I have a few comments, though.
I'll try to skip what I've already seen mentioned by others.
> (defmacro join-strings (&rest strings)
> "Return the result of concatenating the supplied strings."
> `(concatenate 'string ,@strings))
>
> (defun rjust (s w)
> "Right justify string S by W spaces."
> (rjust1 s (- w (length s))))
>
> (defun rjust1 (s w)
> "Helper function for RJUST."
> (if (<= w 0) s
> (join-strings (rjust1 s (1- w)) " ")))
A much simpler method of doing this uses the built-in MAKE-STRING
function:
(defun rjust (s w)
(let ((difference (- w (length s))))
(if (<= difference 0)
s
(concatenate 'string s (make-string difference
:initial-element #\Space)))))
> (defun ljust (s w)
> "Left justify string S by W spaces."
> (ljust1 s (- w (length s))))
>
> (defun zfill (s w)
> "Left justify string S by W 0 characters."
> (ljust1 s (- w (length s)) "0"))
>
> (defun ljust1 (s w &optional (c " "))
> "Helper function for LJUST."
> (if (<= w 0) s
> (join-strings c (ljust1 s (1- w) c))))
This would be a good place for you to experiment with keyword arguments
as well. You may want to create a single justify function that you call
something like this:
(justify string width :right :fill-character #\0)
with a signature like
(defun (string width justification &key fill-character)
...)
implementation up to you. CASE or ECASE will come in handy. I mention
ECASE, since you seem to like to do error checking in your code.
Since you're just starting out, I will forbear posting and FORMAT
strings that accomplish the justification for you.
--
Thomas A. Russ, USC/Information Sciences Institute
crystal1 <········@spamless.net> writes:
> See below. These functions have been created a thousand times and are
> trivial, but I wrote these instances myself and am a pretty proud
> beginner. What do you think?
>
> A week ago recursion was still pretty hazy, but a after reading
> "Common Lisp, An Interactive Approach" the progress has been
> steady. What a great book.
>
> ;;;;;;;;;;;;;;;;
>
> (defpackage :string-utils
> (:use :common-lisp))
>
> (in-package :string-utils)
>
> (defun line-split1 (s d)
> "Helper function for LINE-SPLIT. Return a list with elements of
> supplied string S up to the first instance of delimiter D as :STRING
> and the remaining string after the delimiter as :REMAINDER."
> (if (position d s) (list :string (subseq s 0 (position d s))
> :remainder (subseq s (1+ (position d s))))
> (list :string s :remainder "")))
(defun line-split1 (s d)
(if (position d s)
(values (subseq s 0 (position d s))
(subseq s (1+ (position d s))))
(values s "")))
> (defun line-split (s d)
> "Return a list of substrings in string S when delimited by delimiter D."
> (check-type s string)
> (check-type d character)
These check-types are done by called functions, so you're doing them twice.
> (if (= (length s) 0) '()
> (cons (getf (line-split1 s d) :string)
> (line-split (getf (line-split1 s d) :remainder) d))))
Indent it this way:
(if condition
then
else)
otherwise we will believe your else is a then!
Note that you're calling twice line-split1 with the same argument!
(defun line-split (s d)
"Return a list of substrings in string S when delimited by delimiter D."
(if (= (length s) 0)
'()
(multiple-value-bind (string remainder) (line-split1 s d)
(cons string (line-split remainder d)))))
Note that this is still a non-terminal recursive function, so you'll
need a lot of stack space.
You could try to write a recursive function that is tail-recursive, to
give a chance to a TCO implementation. Note that not all CL
implementations do TCO, so you still may want to write an iterative solution.
> (defun read-file (filename delimiter)
> "Return a list of lists containing strings in pathname FILENAME
> delimited by character DELIMITER."
> (let ((output '()))
> (with-open-file (in filename)
> (do ((line (read-line in nil) (read-line in nil)))
> ((null line))
> (push (line-split line delimiter) output))
> (nreverse output))))
Good. Eventually, I switched to loop:
(defun read-file (filename delimiter)
"Return a list of lists containing strings in pathname FILENAME
delimited by character DELIMITER."
(with-open-file (in filename)
(loop
:for line = (read-line in nil)
:while line
:collect (line-split line delimiter))))
> (defmacro join-strings (&rest strings)
> "Return the result of concatenating the supplied strings."
> `(concatenate 'string ,@strings))
You don't need a macro here. With a macro, you cannot write:
(mapcar 'join-string '("A " "The " "Some ")
'("cat " "dog " "guy ")
'("eats " "bites " "runs ")
'("." "." "."))
Write it as a function. If you want, you can declare it INLINE.
> (defun rjust (s w)
> "Right justify string S by W spaces."
> (rjust1 s (- w (length s))))
>
> (defun rjust1 (s w)
> "Helper function for RJUST."
> (if (<= w 0) s
> (join-strings (rjust1 s (1- w)) " ")))
You can use local functions:
(defun rjust (s w)
"Right justify string S by W spaces."
(labels ((rjust1 (s w)
(if (<= w 0)
s
(join-strings (rjust1 s (1- w)) " "))))
(rjust1 s (- w (length s)))))
> (defun ljust (s w)
> "Left justify string S by W spaces."
> (ljust1 s (- w (length s))))
>
> (defun zfill (s w)
> "Left justify string S by W 0 characters."
> (ljust1 s (- w (length s)) "0"))
>
> (defun ljust1 (s w &optional (c " "))
> "Helper function for LJUST."
> (if (<= w 0) s
> (join-strings c (ljust1 s (1- w) c))))
Don't you feel that there's some redundancy here?
What about an API such as:
(defun string-pad (string width &key (padchar " ") (justification :left))
...)
(string-pad "123" 10 :padchar #\0 :justification :right)
(string-pad "Hello" 10 :padchar #\!)
(string-pad "BIG TITILE" 20 :justification :center)
Overall, congratulations for your lisping! Go on on this journey!
--
__Pascal Bourguignon__ http://www.informatimago.com/
Nobody can fix the economy. Nobody can be trusted with their finger
on the button. Nobody's perfect. VOTE FOR NOBODY.
crystal1 wrote
> These functions have been created a thousand times and are
> trivial, but I wrote these instances myself and am a
> pretty proud beginner. What do you think?
I think you are doing great. With such spirit and playfulness
your explorations will soon discover all of Common Lisp
I particularly liked the idea of using getf to extract a named
return value from amongst several. Other posters have shown
you the right way to return multiple values with values and
multiple-value-thing but in a spirit of play have a look at
this kitten-code. The idea is that
(:string "foo" :remainder "bar")
looks like a keyword argument list, so let us define a
function with keyword arguments and apply it to the list.
Then the keyword processing is doing the getf's for us.
(defun line-split (s d)
(if (= (length s) 0)
'()
(apply #'construct d (helper s d))))
(defun construct (d &key string remainder)
(cons string
(line-split remainder d)))
(defun helper (s d)
(let ((split (position d s)))
(if split
(list :string (subseq s 0 split)
:remainder (subseq s (+ split 1)))
(list :string s))))
(line-split "will florence ever get to kiss winston? " #\space)
=> ("will" "florence" "ever" "get" "to" "kiss" "winston?")
This code only works because keyword arguments default to
nil, and nil is a list of length zero, so
(= (length nil) 0) => t
The proper thing to do is to give remainder an explict
default of the string with no characters
(defun construct (d &key string (remainder "")) ... )
Then your excellent check-type's will be satisfied.
Alan Crowe
Edinburgh
Scotland