From: Will McCutchen
Subject: A lazy or forgiving SUBSTRING a la Python
Date: 
Message-ID: <1117727766.908101.238300@g49g2000cwa.googlegroups.com>
First off, I'm very new to Lisp and to comp.lang.lisp, so whatever Lisp
code I paste into this message will probably be wrong on all sorts of
levels.  My intentions may even be completely misguided.  Any input at
all will be appreciated.

A little background:  To (try to) learn Lisp, I'm porting a little
Python app to Common Lisp.

I've written a function that lets me be lazy about getting substrings,
which is what I'm used to in Python.  What I mean is that it won't
signal an error if my substring indexes are out of bounds, so that I
can ask for a substring of "012345" from 3 to 15 and I'll get back
"345".

Here is the function.  I have a feeling that it is not very Lisp-y at
all, and I'm just wondering if there are any obviously better ways to
accomplish this.


(defun py-substring (str start &optional end)
  (let ((len (length str)))
    (let* ((goodstart
	    (cond
	       ((< start 0) 0)
	       ((> start len) len)
	       (t start)))
	  (goodend
	   (cond
	     ((null end) len)
	     ((< end goodstart) goodstart)
	     ((> end len) len)
	     (t end))))
      (subseq str goodstart goodend))))


I know that there may be some inherent flaws in my program or in my
brain if I'm too lazy to ensure that my string indexes are out of
bounds, but it sure makes this program easier to deal with.

Thanks for taking a look, and I'm sorry if this is a waste of time.


Will.

From: Steven E. Harris
Subject: Re: A lazy or forgiving SUBSTRING a la Python
Date: 
Message-ID: <jk4zmu83a03.fsf@W003275.na.alarismed.com>
"Will McCutchen" <·········@gmail.com> writes:

> I'm just wondering if there are any obviously better ways to
> accomplish this.

Here's one I wrote for similar purposes:

(defun validate-sequence-bounds (seq start end)
  (check-type start (integer 0) "a nonnegative integer")
  (let ((length (length seq)))
    (if end
        (progn
          (check-type end (integer 0) "a nonnegative integer")
          (cond
            ((> end length)
             (error "End position (~A) should not be greater than the sequence length (~A)."
                    end length))
            ((> start end)
             (error "Start position (~A) should not be greater than end position (~A)."
                    end start))
            (t end)))
        (if (> start length)
            (error "Start position (~A) should not be greater than the sequence length (~A)."
                   start length)
            length))))


Use it at the start of a function like this:

,----
| (defun some-sequence-function (seq &key (start 0) end)
|   (let ((end (validate-sequence-bounds seq start end)))
|     ;; ...
|     ))
`----


So your function would whittle down to:

,----
| (defun py-substring (str start &optional end)
|   (subseq str start
|           (validate-sequence-bounds str start end)))
`----

-- 
Steven E. Harris
From: ··············@hotmail.com
Subject: Re: A lazy or forgiving SUBSTRING a la Python
Date: 
Message-ID: <1117747650.125604.79760@z14g2000cwz.googlegroups.com>
Steven E. Harris wrote:
> "Will McCutchen" <·········@gmail.com> writes:
>
> > I'm just wondering if there are any obviously better ways to
> > accomplish this.
>
> Here's one I wrote for similar purposes:
>
> (defun validate-sequence-bounds (seq start end)
>   (check-type start (integer 0) "a nonnegative integer")
>   (let ((length (length seq)))

 ...

One further specialization would be to recognize the case where SEQ is
a list, potentially very long, and the desire is only to verify that
SEQ is long enough that END is a valid bound.

Asking for the (LENGTH SEQ) is guaranteed to traverse a list to the end
(or, get caught in a circular list); one could instead cons down a
finite number of times, verifying that one does not run into the end.
From: Steven E. Harris
Subject: Re: A lazy or forgiving SUBSTRING a la Python
Date: 
Message-ID: <jk4is0w2vo9.fsf@W003275.na.alarismed.com>
···············@hotmail.com" <············@gmail.com> writes:

> Asking for the (LENGTH SEQ) is guaranteed to traverse a list to the end
> (or, get caught in a circular list); one could instead cons down a
> finite number of times, verifying that one does not run into the end.

That's a great idea. I can see how to walk down the list a finite
number of times, but is there a built-in facility to help
incrementally detect circular lists (without walking the entire list)?
Or would I have to build up a set of "seen" conses along the way,
testing each next cons to check if I've already seen it?

-- 
Steven E. Harris
From: Pascal Bourguignon
Subject: Re: A lazy or forgiving SUBSTRING a la Python
Date: 
Message-ID: <87fyw070bm.fsf@thalassa.informatimago.com>
"Steven E. Harris" <···@panix.com> writes:

> ···············@hotmail.com" <············@gmail.com> writes:
>
>> Asking for the (LENGTH SEQ) is guaranteed to traverse a list to the end
>> (or, get caught in a circular list); one could instead cons down a
>> finite number of times, verifying that one does not run into the end.
>
> That's a great idea. I can see how to walk down the list a finite
> number of times, but is there a built-in facility to help
> incrementally detect circular lists (without walking the entire list)?
> Or would I have to build up a set of "seen" conses along the way,
> testing each next cons to check if I've already seen it?

LIST-LENGTH will return nil for circular lists and (length list) for
proper lists.


-- 
__Pascal Bourguignon__                     http://www.informatimago.com/

The world will now reboot.  don't bother saving your artefacts.
From: Barry Margolin
Subject: Re: A lazy or forgiving SUBSTRING a la Python
Date: 
Message-ID: <barmar-D9C8A1.21071302062005@comcast.dca.giganews.com>
In article <··············@thalassa.informatimago.com>,
 Pascal Bourguignon <···@informatimago.com> wrote:

> "Steven E. Harris" <···@panix.com> writes:
> 
> > ···············@hotmail.com" <············@gmail.com> writes:
> >
> >> Asking for the (LENGTH SEQ) is guaranteed to traverse a list to the end
> >> (or, get caught in a circular list); one could instead cons down a
> >> finite number of times, verifying that one does not run into the end.
> >
> > That's a great idea. I can see how to walk down the list a finite
> > number of times, but is there a built-in facility to help
> > incrementally detect circular lists (without walking the entire list)?
> > Or would I have to build up a set of "seen" conses along the way,
> > testing each next cons to check if I've already seen it?
> 
> LIST-LENGTH will return nil for circular lists and (length list) for
> proper lists.

Of course, this will typically work in the way Steven wishes to avoid, 
by walking the entire list.  However, it's not necessary to build up a 
set of seen conses.  The usual implementation is something like this:

(defun list-length (list)
  (if (null list)
      0
      (1+ (loop for slow on (cdr list)
                and fast = (cddr list) then (cddr fast)
                counting slow
                when (eq slow fast)
                do (return-from list-length nil)))))

Rather than remembering every cons it has seen, it walks the list one 
step at a time and two steps at a time.  If the fast one ever "laps" the 
slow one, the list must be circular.

-- 
Barry Margolin, ······@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***
From: Pascal Bourguignon
Subject: Re: A lazy or forgiving SUBSTRING a la Python
Date: 
Message-ID: <878y1s6z26.fsf@thalassa.informatimago.com>
"Steven E. Harris" <···@panix.com> writes:

> ···············@hotmail.com" <············@gmail.com> writes:
>
>> Asking for the (LENGTH SEQ) is guaranteed to traverse a list to the end
>> (or, get caught in a circular list); one could instead cons down a
>> finite number of times, verifying that one does not run into the end.
>
> That's a great idea. I can see how to walk down the list a finite
> number of times, but is there a built-in facility to help
> incrementally detect circular lists (without walking the entire list)?

Without walking the entire list?

> Or would I have to build up a set of "seen" conses along the way,
> testing each next cons to check if I've already seen it?

That'd be O(N) in space, and for time, depending on whether you use a
hash table [between O(N) and O(Nlog(N)) total] or if you cons a new
list (smaller constants, but --> O(N�) total).


It's simplier to just keep a second pointer advancing twice the speed.
Keeping an signature upward compatible with dolist:

(defmacro dolist-checking-circular ((var list &optional result &body circular)
                                    &body body)
  (let ((current (gensym)) (forward (gensym)))
    `(loop for ,current = ,list          then (cdr ,current)
           for ,forward = (cdr ,current) then (cddr ,forward)
           while ,current
           do (if (eq ,forward ,current)
                (return
                 (let ((,var (car ,current)))
                   ,@circular))
                (let ((,var (car ,current)))
                  ,@body))
           finally (return ,result))))

(unless (dolist-checking-circular (item  (list 1 2 3 4 5 6) :not-circular)
           (print item))
   :circular)

(dolist-checking-circular (item  (list 1 2 3 4 5 6)
                                 :not-circular
                                 (princ "Oops: circular!")(terpri)
                                 :circular)
  (print item))

(defun make-circular (list) (setf (cdr (last list)) list) list)

(unless (dolist-checking-circular (item (make-circular (list 1 2 3 4 5 6))
                                          :not-circular)
           (print item))
   :circular)

(dolist-checking-circular (item (make-circular (list 1 2 3 4 5 6))
                                :not-circular
                                (princ "Oops: circular!")(terpri)
                                :circular)
  (print item))

;; Local variables:
;; eval: (put 'dolist-checking-circular 'ident 1)
;; End:

-- 
__Pascal Bourguignon__                     http://www.informatimago.com/
From: ··············@hotmail.com
Subject: Re: A lazy or forgiving SUBSTRING a la Python
Date: 
Message-ID: <1117827449.612335.20340@g14g2000cwa.googlegroups.com>
Steven E. Harris wrote:
> ···············@hotmail.com" <············@gmail.com> writes:
>
> > Asking for the (LENGTH SEQ) is guaranteed to traverse a list to the end
> > (or, get caught in a circular list); one could instead cons down a
> > finite number of times, verifying that one does not run into the end.
>
> That's a great idea. I can see how to walk down the list a finite
> number of times, but is there a built-in facility to help
> incrementally detect circular lists (without walking the entire list)?
> Or would I have to build up a set of "seen" conses along the way,
> testing each next cons to check if I've already seen it?
>
> Steven E. Harris

Others have supplied the answer to your question; however, my main
point was that a circular/infinite list is *always* long enough, and a
very long list might be much longer than needed. Once you know the list
goes on at least END (or is it (1- END)...) conses, you stop checking.

No particular need here for the special code to detect circularity.
From: Steven E. Harris
Subject: Re: A lazy or forgiving SUBSTRING a la Python
Date: 
Message-ID: <jk464wvukkl.fsf@W003275.na.alarismed.com>
···············@hotmail.com" <············@gmail.com> writes:

> my main point was that a circular/infinite list is *always* long
> enough, and a very long list might be much longer than needed. Once
> you know the list goes on at least END (or is it (1- END)...)
> conses, you stop checking.
>
> No particular need here for the special code to detect circularity.

Yes, but for the case where no end was specified, we still wish to
check whether the "start" is sensible, and would like to figure out a
sensible "end" value to return from `validate-sequence-bounds'.

If I'm reading you correctly, you're saying not to worry about
counting repeated conses in a circular list, but that won't help me
synthesize an "end" count for the circular list. Perhaps I should just
punt then and return nil like `list-length'.

By the way, I just noticed that the HyperSpec says that `length' can
signal a type-error if the sequence is an improper list. Does "should
be prepared to signal" imply that an implementation /must/ signal?

-- 
Steven E. Harris
From: William Bland
Subject: Re: A lazy or forgiving SUBSTRING a la Python
Date: 
Message-ID: <pan.2005.06.02.16.20.44.443823@abstractnonsense.com>
On Thu, 02 Jun 2005 08:56:06 -0700, Will McCutchen wrote:
> 
> I've written a function that lets me be lazy about getting substrings

"Sloppy" might be a better word ;-)

Try this:

(defun py-substring (str start &optional (end (length str)))
  (let* ((len (length str))
	 (x (min (max 0 start) len))  ; 0 <= x <= len
	 (y (max x (min end len))))   ; x <= y <= len
    (subseq str x y)))

Best wishes,
		Bill.
From: Will McCutchen
Subject: Re: A lazy or forgiving SUBSTRING a la Python
Date: 
Message-ID: <1117730885.485724.206700@g47g2000cwa.googlegroups.com>
> "Sloppy" might be a better word ;-)

I think sloppy is the exact word I was looking for!

> Try this:
>
> (defun py-substring (str start &optional (end (length str)))
>   (let* ((len (length str))
> 	 (x (min (max 0 start) len))  ; 0 <= x <= len
> 	 (y (max x (min end len))))   ; x <= y <= len
>     (subseq str x y)))

Hey, that's much nicer.  I'm sure I read it somewhere, but I had
forgotten that function arguments with default values can refer to
previous arguments.

Thanks for the help,


Will.
From: Paolo Amoroso
Subject: Re: A lazy or forgiving SUBSTRING a la Python
Date: 
Message-ID: <87br6olo4e.fsf@plato.moon.paoloamoroso.it>
"Will McCutchen" <·········@gmail.com> writes:

> First off, I'm very new to Lisp and to comp.lang.lisp, so whatever Lisp

You may want to have a look at Peter Seibel's book:

  Practical Common Lisp
  http://www.gigamonkeys.com/book


Paolo
-- 
Why Lisp? http://lisp.tech.coop/RtL%20Highlight%20Film
Recommended Common Lisp libraries/tools (see also http://clrfi.alu.org):
- ASDF/ASDF-INSTALL: system building/installation
- CL-PPCRE: regular expressions
- UFFI: Foreign Function Interface
From: Will McCutchen
Subject: Re: A lazy or forgiving SUBSTRING a la Python
Date: 
Message-ID: <1117731196.094738.34460@o13g2000cwo.googlegroups.com>
> > First off, I'm very new to Lisp and to comp.lang.lisp, so whatever Lisp
>
> You may want to have a look at Peter Seibel's book:

Thanks, Paolo.  That book is what actually convinced me that maybe my
small brain could handle learning Lisp (I had decided that I could not
learn it after attempting Paul Graham's On Lisp and various other
"tutorials").  I haven't read the entire thing through (I'd like to buy
a copy and show some support), but I refer to the online copy pretty
frequently.


Will.
From: Eric Lavigne
Subject: Re: A lazy or forgiving SUBSTRING a la Python
Date: 
Message-ID: <1117733313.760943.12180@f14g2000cwb.googlegroups.com>
>I had decided that I could not
>learn it after attempting Paul Graham's On Lisp and various other
>"tutorials"

"On Lisp" was never meant to be a tutorial. Check the complete title:
On Lisp: Advanced Techniques for Common Lisp
Even though it covers some relatively basic stuff in the first few
chapters, I'd be surprised if someone used that as their first book
without getting scared away :)

Paul Graham also wrote a beginner's book, Ansi Common Lisp, though
Peter Seibel's book is still a bit easier to read.
From: Marco Baringer
Subject: Re: A lazy or forgiving SUBSTRING a la Python
Date: 
Message-ID: <m2zmu8r9s2.fsf@soma.local>
"Will McCutchen" <·········@gmail.com> writes:

> (defun py-substring (str start &optional end)
>   (let ((len (length str)))
>     (let* ((goodstart
> 	    (cond
> 	       ((< start 0) 0)
> 	       ((> start len) len)
> 	       (t start)))
> 	  (goodend
> 	   (cond
> 	     ((null end) len)
> 	     ((< end goodstart) goodstart)
> 	     ((> end len) len)
> 	     (t end))))
>       (subseq str goodstart goodend))))

[untested]

(defun py-substring (string start &optinal (end (length string)))
  (flet ((range-bound (lower-bound x upper-bound)
           (max lower-bound (min x upper-bound))))
    (let* ((length (length string))
           (start  (range-bound 0     start length))
           (end    (range-bound start end   length)))
      (subseq string start end))))

-- 
-Marco
Ring the bells that still can ring.
Forget the perfect offering.
There is a crack in everything.
That's how the light gets in.
	-Leonard Cohen
From: Frank Buss
Subject: Re: A lazy or forgiving SUBSTRING a la Python
Date: 
Message-ID: <199n58wba5p4l$.1dv3crchtj4ky$.dlg@40tude.net>
Will McCutchen wrote:

> (defun py-substring (str start &optional end)
>   (let ((len (length str)))
>     (let* ((goodstart
> 	    (cond
> 	       ((< start 0) 0)
> 	       ((> start len) len)
> 	       (t start)))
> 	  (goodend
> 	   (cond
> 	     ((null end) len)
> 	     ((< end goodstart) goodstart)
> 	     ((> end len) len)
> 	     (t end))))
>       (subseq str goodstart goodend))))

you could include the "len" in the "let*" block. And the indention of
"goodend" should be the same as "goodstart". Lispworks formats it like
this:

(defun py-substring (str start &optional end)
  (let* ((len (length str))
         (goodstart
          (cond
           ((< start 0) 0)
           ((> start len) len)
           (t start)))
         (goodend
          (cond
           ((null end) len)
           ((< end goodstart) goodstart)
           ((> end len) len)
           (t end))))
    (subseq str goodstart goodend)))

> I know that there may be some inherent flaws in my program or in my
> brain if I'm too lazy to ensure that my string indexes are out of
> bounds, but it sure makes this program easier to deal with.

It looks ok, but I would write it like this:

(defun substring (string start &optional end)
  (let ((len (length string)))
    (when (< start 0) (setf start 0))
    (when (> start len) (setf start len))
    (when end
      (when (< end start) (setf end start))
      (when (> end len) (setf end len)))
    (subseq string start end)))

And don't forget to implement the defsetf:

(defun check-limits (len start end)
  (when (< start 0) (setf start 0))
  (when (> start len) (setf start len))
  (when end
    (when (< end start) (setf end start))
    (when (> end len) (setf end len)))
  (values start end))
  
(defun substring (sequence start &optional end)
  (let ((len (length sequence)))
    (multiple-value-bind (start end) (check-limits len start end)
      (subseq sequence start end))))

(defsetf substring (sequence start &optional end) (new-sequence) 
  (let ((goodstart (gensym))
        (goodend (gensym))
        (len (gensym)))
    `(let ((,len (length ,sequence)))
       (multiple-value-bind (,goodstart ,goodend)
           (check-limits ,len ,start ,end)
         (replace ,sequence ,new-sequence 
                  :start1 ,start :end1 ,end)
         ,new-sequence))))

(let ((test "Hello World")) (setf (substring test 6 1000) "Lisp!") test)
-> "Hello Lisp!"

But I'm sure this can be implemented better, because I'm not a Lisp
professional.

-- 
Frank Bu�, ··@frank-buss.de
http://www.frank-buss.de, http://www.it4-systems.de
From: Will McCutchen
Subject: Re: A lazy or forgiving SUBSTRING a la Python
Date: 
Message-ID: <1117732769.118765.307180@z14g2000cwz.googlegroups.com>
Out of the three new functions given, I prefer the first one.  It's
shorter and what it does is much more readily apparent to my
new-to-Lisp brain:

William Bland wrote:
> (defun py-substring (str start &optional (end (length str)))
>   (let* ((len (length str))
> 	 (x (min (max 0 start) len))  ; 0 <= x <= len
> 	 (y (max x (min end len))))   ; x <= y <= len
>     (subseq str x y)))


Is there any real advantage to the other approaches?  As far as I can
tell, they both abstract out the logic of determining the bounds into a
small function, but that doesn't seem like a big win in this situation,
since it makes the overall function/task longer and (again, to me) less
clear:


Marco Baringer wrote:
> [untested]
>
> (defun py-substring (string start &optinal (end (length string)))
>   (flet ((range-bound (lower-bound x upper-bound)
>            (max lower-bound (min x upper-bound))))
>     (let* ((length (length string))
>            (start  (range-bound 0     start length))
>            (end    (range-bound start end   length)))
>       (subseq string start end))))


Frank Buss wrote:
> (defun check-limits (len start end)
>   (when (< start 0) (setf start 0))
>   (when (> start len) (setf start len))
>   (when end
>     (when (< end start) (setf end start))
>     (when (> end len) (setf end len)))
>   (values start end))
>
> (defun substring (sequence start &optional end)
>   (let ((len (length sequence)))
>     (multiple-value-bind (start end) (check-limits len start end)
>       (subseq sequence start end))))
>
> (defsetf substring (sequence start &optional end) (new-sequence)
>   (let ((goodstart (gensym))
>         (goodend (gensym))
>         (len (gensym)))
>     `(let ((,len (length ,sequence)))
>        (multiple-value-bind (,goodstart ,goodend)
>            (check-limits ,len ,start ,end)
>          (replace ,sequence ,new-sequence
>                   :start1 ,start :end1 ,end)
>          ,new-sequence))))
>
> (let ((test "Hello World")) (setf (substring test 6 1000) "Lisp!") test)
> -> "Hello Lisp!"


I do appreciate the replies, I'm just trying to figure out if there is
a real advantage to the longer versions.


Will.
From: Pascal Bourguignon
Subject: Re: A lazy or forgiving SUBSTRING a la Python
Date: 
Message-ID: <87hdgg8xh2.fsf@thalassa.informatimago.com>
"Will McCutchen" <·········@gmail.com> writes:

> Out of the three new functions given, I prefer the first one.  It's
> shorter and what it does is much more readily apparent to my
> new-to-Lisp brain:
>
> William Bland wrote:
>> (defun py-substring (str start &optional (end (length str)))
>>   (let* ((len (length str))
>> 	 (x (min (max 0 start) len))  ; 0 <= x <= len
>> 	 (y (max x (min end len))))   ; x <= y <= len
>>     (subseq str x y)))
>
>
> Is there any real advantage to the other approaches?  As far as I can
> tell, they both abstract out the logic of determining the bounds into a
> small function, but that doesn't seem like a big win in this situation,
> since it makes the overall function/task longer and (again, to me) less
> clear:

Think about it!


How often do you have to check-limits?  
Will this check-limits do what you need next time?

Perhaps range-bound would be better, since it works on one case, but
it's only a local function.  Would have it been better to make it
slightly longer and define it as a defun?

Frank added a defsetf which allow you to do this:

>> (let ((test "Hello World")) (setf (substring test 6 1000) "Lisp!") test)
>> -> "Hello Lisp!"

Did you need it?  
Was it useless, or will it simplify your code?  
Perhaps it should be even longer!

[266]> (test 1 "Hi!")
"HHi!o World"
[267]> (test 6 "YO")
"HHi!o YOrld"
[268]> (test 6 "What a marvelous world")

*** - SYSTEM::STORE: index 11 for "HHi!o YOrld" is out of range
The following restarts are available:
ABORT          :R1      ABORT
Break 1 [269]> 



> I do appreciate the replies, I'm just trying to figure out if there is
> a real advantage to the longer versions.


-- 
__Pascal Bourguignon__                     http://www.informatimago.com/
I need a new toy.
Tail of black dog keeps good time.
Pounce! Good dog! Good dog!
From: Will McCutchen
Subject: Re: A lazy or forgiving SUBSTRING a la Python
Date: 
Message-ID: <1117742480.436485.171550@g44g2000cwa.googlegroups.com>
Pascal Bourguignon wrote:
> Think about it!
>
> How often do you have to check-limits?
> Will this check-limits do what you need next time?

I guess I should have been a little bit clearer with my questions.  I
understand that having a general purpose CHECK-LIMITS or RANGE-BOUND
function lying around might come in handy at some point in the future.
At this point, and in this application, I don't see it getting used
outside of my PY-SUBSTRING function.

> Frank added a defsetf which allow you to do this:
>
> >> (let ((test "Hello World")) (setf (substring test 6 1000) "Lisp!") test)
> >> -> "Hello Lisp!"
>
> Did you need it?
> Was it useless, or will it simplify your code?
> Perhaps it should be even longer!

I noticed that, and I was glad to see how it's done, because I'm sure
that it will come in handy in the future.  Again, it feels like
overkill (at the moment) for the specific problem I'm trying to solve.


Will.
From: Pascal Bourguignon
Subject: Re: A lazy or forgiving SUBSTRING a la Python
Date: 
Message-ID: <87acm88qbm.fsf@thalassa.informatimago.com>
"Will McCutchen" <·········@gmail.com> writes:

> Pascal Bourguignon wrote:
>> Think about it!
>>
>> How often do you have to check-limits?
>> Will this check-limits do what you need next time?
>
> I guess I should have been a little bit clearer with my questions.  I
> understand that having a general purpose CHECK-LIMITS or RANGE-BOUND
> function lying around might come in handy at some point in the future.
> At this point, and in this application, I don't see it getting used
> outside of my PY-SUBSTRING function.
>
>> Frank added a defsetf which allow you to do this:
>>
>> >> (let ((test "Hello World")) (setf (substring test 6 1000) "Lisp!") test)
>> >> -> "Hello Lisp!"
>>
>> Did you need it?
>> Was it useless, or will it simplify your code?
>> Perhaps it should be even longer!
>
> I noticed that, and I was glad to see how it's done, because I'm sure
> that it will come in handy in the future.  Again, it feels like
> overkill (at the moment) for the specific problem I'm trying to solve.


I think that reuse should not be considered too much at this
microlevel.  The shortest function is the easiest read and understood,
so I'd consider it the best.

But don't be afraid to expand it a little if it allow you to implement
a useful defsetf for your application.

Often, the most important time is the programmer's time.

-- 
__Pascal Bourguignon__                     http://www.informatimago.com/
The rule for today:
Touch my tail, I shred your hand.
New rule tomorrow.
From: Frank Buss
Subject: Re: A lazy or forgiving SUBSTRING a la Python
Date: 
Message-ID: <1wdwfldp1ruz$.ih5ojtn8r7n3$.dlg@40tude.net>
Pascal Bourguignon wrote:

> Perhaps it should be even longer!
> 
> [266]> (test 1 "Hi!")
> "HHi!o World"
> [267]> (test 6 "YO")
> "HHi!o YOrld"
> [268]> (test 6 "What a marvelous world")
> 
> *** - SYSTEM::STORE: index 11 for "HHi!o YOrld" is out of range
> The following restarts are available:
> ABORT          :R1      ABORT
> Break 1 [269]> 

you are right, a bit longer, because I have to use ",goodstart" and
",goodend" instead of ",start" and ",end".

But the setf-function for subseq already checks for longer sequences, so it
could be written like this:

(defsetf substring (sequence start &optional end) (new-sequence) 
  (let ((goodstart (gensym))
        (goodend (gensym))
        (len (gensym)))
    `(let ((,len (length ,sequence)))
       (multiple-value-bind (,goodstart ,goodend)
           (check-limits ,len ,start ,end)
         (setf (subseq ,sequence ,goodstart ,goodend) ,new-sequence)))))

And you could reuse the substring function, I've asked this some time ago
for defsetf, but I forgot how to do it.

-- 
Frank Bu�, ··@frank-buss.de
http://www.frank-buss.de, http://www.it4-systems.de
From: Frank Buss
Subject: Re: A lazy or forgiving SUBSTRING a la Python
Date: 
Message-ID: <76iv81tlpduh.1am1yqge1s586.dlg@40tude.net>
Will McCutchen wrote:

> William Bland wrote:
>> (defun py-substring (str start &optional (end (length str)))
>>   (let* ((len (length str))
>> 	 (x (min (max 0 start) len))  ; 0 <= x <= len
>> 	 (y (max x (min end len))))   ; x <= y <= len
>>     (subseq str x y)))
> 
> 
> Is there any real advantage to the other approaches?

I don't like the min/max solution, because you have to think a bit before
understanding what the code means (and I think (max x (min end len)) could
be written as (min (max end x) len)), if you don't read the comments. 

Perhaps a macro would be better, because then it looks nearly the same as
it would be in pseudo-code or comments:

(defmacro limit (lower lower-compare value upper-compare upper)
  (let ((x (gensym)))
    `(let ((,x ,value))
       (cond ((eql ',lower-compare '<)
              (unless (< ,lower ,x) (setf ,x (1+ ,lower))))
             ((eql ',lower-compare '<=)
              (unless (<= ,lower ,x) (setf ,x ,lower)))
             (t (error "invalid lower-compare operator")))
       (cond ((eql ',upper-compare '<)
              (unless (< ,x ,upper) (setf ,x (1- ,upper))))
             ((eql ',upper-compare '<=)
              (unless (<= ,x ,upper) (setf ,x ,upper)))
             (t (error "invalid upper-compare operator")))
       ,x)))

(defun py-substring (string start &optional end)
  (let* ((len (length string))
         (x (limit 0 <= start <= len))
         (y (when end (limit x <= end <= len))))
    (subseq string x y)))

-- 
Frank Bu�, ··@frank-buss.de
http://www.frank-buss.de, http://www.it4-systems.de
From: Wade Humeniuk
Subject: Re: A lazy or forgiving SUBSTRING a la Python
Date: 
Message-ID: <hnKne.22179$HI.10730@edtnps84>
Here is another,

(defun py-substring (str start &optional end)
   (coerce
    (loop for i from (max 0 start)
          below (if end (min end (length str))
                  (length str))
          collect (char str i))
    'string))

Wade
From: William Bland
Subject: Re: A lazy or forgiving SUBSTRING a la Python
Date: 
Message-ID: <pan.2005.06.02.21.00.24.495125@abstractnonsense.com>
On Thu, 02 Jun 2005 20:55:41 +0000, Wade Humeniuk wrote:

> Here is another,
> 
> (defun py-substring (str start &optional end)
>    (coerce
>     (loop for i from (max 0 start)
>           below (if end (min end (length str))
>                   (length str))
>           collect (char str i))
>     'string))
> 
> Wade

Out of interest, is there a reason why some people don't seem to like to
use the "&optional (end (length str))" idiom?  I quite like it myself.

Cheers,
	Bill.
From: Frank Buss
Subject: Re: A lazy or forgiving SUBSTRING a la Python
Date: 
Message-ID: <zyn4xv7pseix$.irwiteey1s21$.dlg@40tude.net>
William Bland wrote:

> Out of interest, is there a reason why some people don't seem to like to
> use the "&optional (end (length str))" idiom?  I quite like it myself.

at least for my solution, it is not necessary, because subseq allows nil
for end, which is faster. But it looks interesting for other tasks. I
didn't know that it is possible.

-- 
Frank Bu�, ··@frank-buss.de
http://www.frank-buss.de, http://www.it4-systems.de
From: Pascal Costanza
Subject: Re: A lazy or forgiving SUBSTRING a la Python
Date: 
Message-ID: <3g9gtuFb8tanU1@individual.net>
William Bland wrote:

> On Thu, 02 Jun 2005 20:55:41 +0000, Wade Humeniuk wrote:
> 
>>Here is another,
>>
>>(defun py-substring (str start &optional end)
>>   (coerce
>>    (loop for i from (max 0 start)
>>          below (if end (min end (length str))
>>                  (length str))
>>          collect (char str i))
>>    'string))
>>
>>Wade
> 
> Out of interest, is there a reason why some people don't seem to like to
> use the "&optional (end (length str))" idiom?  I quite like it myself.

You may want to pass nil explicitly, for example in generated code. 
&optional (end (length str)) would require you to pass the length of the 
string in those cases, which may be somewhat more cumbersome.

I would change Wade's proposal slightly to this:

(defun py-substring (str start &optional end)
   (with-output-to-string (result)
     (loop ...
           do (write-char (char str i) result))))

Although I don't know which one performs better (coerce vs. 
with-output-to-string), to me it seems to be aesthetically more pleasing 
not to create an intermediate list.

Furthermore one can have additional safety by turning this into a method:

(defmethod py-substring ((str string) (start integer) &optional end)
   ...)

This would add a "free" type check at least for the required arguments.


Pascal

-- 
2nd European Lisp and Scheme Workshop
July 26 - Glasgow, Scotland - co-located with ECOOP 2005
http://lisp-ecoop05.bknr.net/
From: Wade Humeniuk
Subject: Re: A lazy or forgiving SUBSTRING a la Python
Date: 
Message-ID: <lMPne.31093$wr.4305@clgrps12>
Pascal Costanza wrote:

> 
> I would change Wade's proposal slightly to this:
> 
> (defun py-substring (str start &optional end)
>   (with-output-to-string (result)
>     (loop ...
>           do (write-char (char str i) result))))
> 
> Although I don't know which one performs better (coerce vs. 
> with-output-to-string), to me it seems to be aesthetically more pleasing 
> not to create an intermediate list.
> 

Or a vector-push version.  Anyways, here is my subseq version...

(defun py-substring (str start &optional end)
   (let* ((start (max 0 start))
          (end (if end
                   (min end (length str))
                 (length str)))
          (sublen (- end start)))
     (if (> sublen 0) (subseq str start end) "")))

Wade
From: William Bland
Subject: Re: A lazy or forgiving SUBSTRING a la Python
Date: 
Message-ID: <pan.2005.06.03.14.31.26.423299@abstractnonsense.com>
On Fri, 03 Jun 2005 00:52:45 +0200, Pascal Costanza wrote:

> William Bland wrote:
> 
>> Out of interest, is there a reason why some people don't seem to like to
>> use the "&optional (end (length str))" idiom?  I quite like it myself.
> 
> You may want to pass nil explicitly, for example in generated code. 
> &optional (end (length str)) would require you to pass the length of the 
> string in those cases, which may be somewhat more cumbersome.

Fair point - thanks.
Cheers,
	Bill.
From: Marco Antoniotti
Subject: Re: A lazy or forgiving SUBSTRING a la Python
Date: 
Message-ID: <bI_ne.54$mi7.85781@typhoon.nyu.edu>
William Bland wrote:
> On Thu, 02 Jun 2005 20:55:41 +0000, Wade Humeniuk wrote:
> 
> 
>>Here is another,
>>
>>(defun py-substring (str start &optional end)
>>   (coerce
>>    (loop for i from (max 0 start)
>>          below (if end (min end (length str))
>>                  (length str))
>>          collect (char str i))
>>    'string))
>>
>>Wade
> 
> 
> Out of interest, is there a reason why some people don't seem to like to
> use the "&optional (end (length str))" idiom?  I quite like it myself.

Because that is not the way most ANSI functions, e.g. SUBSEQ, are 
defined.  In ANSI, provisions are made for END to be NIL, meaning "the 
length of the sequence".  This allows you to write specifically

	(subseq a-sequence some-start nil)

and obtain the suffix.

I think I remember some a-ha moments in which I decided that the ANSI 
behavior is a good thing indeed.

Cheers
--
Marco