From: Kalle Olavi Niemitalo
Subject: LOOP and COLLECT to an array?
Date: 
Message-ID: <87zo7v57gj.fsf@Astalo.y2000.kon.iki.fi>
I have a list of "stacks" (lists) of "pieces".  Each piece has a
"mnemonic character".  I want to collect the mnemonic character
of the topmost piece in each stack, and build a string from them.
After that, I want to make another string from the mnemonic
characters of the second-from-top pieces, etc. until I have
processed all the pieces.  I can freely modify the outer list, so
I pop the stacks instead of using NTH.

The inner loop looks like this:

  (loop for stack-node on stacks
	for piece = (pop (car stack-node))
	collect (if piece
                    (piece-mnemonic-char piece)
                  #\Space))

This however returns a list.  I looked for a LOOP value
accumulation clause that would return a string but didn't find
one, so I just put the LOOP inside (map 'string #'identity ...).

Is this good Lisp, or is there a better way to write the loop and
the conversion to string?  Should I first create a string with
the correct size and then store the characters into it?

From: Frank A. Adrian
Subject: Re: LOOP and COLLECT to an array?
Date: 
Message-ID: <TUep7.6$Mm5.10950@news.uswest.net>
There are seversl ways to handle this situation:

(a) As you did with the map.

(b) Use (make-vector (length stacks) :element-type 'character :fill-pointer 
0) to create a temporary variable and use vector-push to add the characters 
to the string.

(c) After the list is collected, apply #'(lambda (&rest elements) 
(make-array (length elements) :element-type 'character :initial-contents 
elements) to it to make a string.

(d) Create an empty string using (make-string 0) and use concatenate to 
append characters.

(e) Use the function reduce on the list of characters with the 
lambda-function #'(lambda (x y) (concatenate (coerce 'string x) (coerce 
'string y)).  Note - you may need a :from-end t here, too and one of the 
coercions may be unecessary if you use :initial-value "".

(f) As Frode has suggested, use with-output-to-string and write-char.

I'm sure there are other ways, as well - that's part of the beauty of Lisp. 
You'll  learn a lot more by coding up each of these ways for forming your 
strings and seeing which is faster.

Actually, at a meta-level, it's a shame that the LOOP macro designers 
didn't add a "vector-pushing" clause to the loop syntax - you can cons, 
append, or nconc for list sequences, but for vectors, bupkis.  It would 
have made stuff like what the original poster was asking about a bit 
easier, though I don't think what's being done is very common.  Even so, it 
would add to the orthogonality of the language (what orthogonality in 
Common Lisp !?!?!) and it seems a shame to provide a rich set of list 
construction options and leave array construction out in the cold...

faa

Kalle Olavi Niemitalo wrote:

> I have a list of "stacks" (lists) of "pieces".  Each piece has a
> "mnemonic character".  I want to collect the mnemonic character
> of the topmost piece in each stack, and build a string from them.
> After that, I want to make another string from the mnemonic
> characters of the second-from-top pieces, etc. until I have
> processed all the pieces.  I can freely modify the outer list, so
> I pop the stacks instead of using NTH.
> 
> The inner loop looks like this:
> 
>   (loop for stack-node on stacks
> for piece = (pop (car stack-node))
> collect (if piece
>                     (piece-mnemonic-char piece)
>                   #\Space))
> 
> This however returns a list.  I looked for a LOOP value
> accumulation clause that would return a string but didn't find
> one, so I just put the LOOP inside (map 'string #'identity ...).
> 
> Is this good Lisp, or is there a better way to write the loop and
> the conversion to string?  Should I first create a string with
> the correct size and then store the characters into it?
> 
From: Thomas F. Burdick
Subject: Re: LOOP and COLLECT to an array?
Date: 
Message-ID: <xcv7kuyuzjo.fsf@apocalypse.OCF.Berkeley.EDU>
Frank A. Adrian <·······@qwest.net> writes:

> There are seversl ways to handle this situation:

Also, one you didn't mention was 

  (loop for stack-node on stacks
	for piece = (pop (car stack-node))
	collect (if piece
                    (piece-mnemonic-char piece)
                  #\Space)
        into result
        finally (return (coerce result 'string)))

I like this way because it says exactly what you're trying to do:
change the list you've been collecting into a string.

> Actually, at a meta-level, it's a shame that the LOOP macro designers 
> didn't add a "vector-pushing" clause to the loop syntax - you can cons, 
> append, or nconc for list sequences, but for vectors, bupkis.  It would 
> have made stuff like what the original poster was asking about a bit 
> easier, though I don't think what's being done is very common.  Even so, it 
> would add to the orthogonality of the language (what orthogonality in 
> Common Lisp !?!?!) and it seems a shame to provide a rich set of list 
> construction options and leave array construction out in the cold...

Agreed.  You can do (loop ... with result = (make-array ...) ...
                              do (vector-push-extend ...) ...
                              finally (return result))
but that's not nearly as good as if it were built in.
From: Kalle Olavi Niemitalo
Subject: Re: LOOP and COLLECT to an array?
Date: 
Message-ID: <87sndm8kj5.fsf@Astalo.y2000.kon.iki.fi>
Frank A. Adrian <·······@qwest.net> writes:

> (c) After the list is collected, apply #'(lambda (&rest elements) 
> (make-array (length elements) :element-type 'character :initial-contents 
> elements) to it to make a string.

That would depend on CALL-ARGUMENTS-LIMIT, which can be 50.
My string will normally have at least 80 characters.
Also, is there any reason why this more verbose code would be
more efficient than the MAP?
From: Frank A. Adrian
Subject: Re: LOOP and COLLECT to an array?
Date: 
Message-ID: <N%Bp7.3108$Pz4.398865@news.uswest.net>
Kalle Olavi Niemitalo wrote:

> That would depend on CALL-ARGUMENTS-LIMIT, which can be 50.

You, of course, are correct.

> My string will normally have at least 80 characters.
> Also, is there any reason why this more verbose code would be
> more efficient than the MAP?

Never said it would, be.  In fact, without profiling, you can't know if any 
of these things would be faster or slower than your alternative for the 
function.  In any case, my point was that (in Lisp) there are usually many 
ways to achieve any particular result.  Exploration is often better than a 
pat answer (and easier to provide)!

faa
From: Frode Vatvedt Fjeld
Subject: Re: LOOP and COLLECT to an array?
Date: 
Message-ID: <2hheu2rj71.fsf@dslab7.cs.uit.no>
Kalle Olavi Niemitalo <···@iki.fi> writes:

> [..] This however returns a list.  I looked for a LOOP value
> accumulation clause that would return a string but didn't find one,
> so I just put the LOOP inside (map 'string #'identity ...).

How about wrapping the loop in a with-output-to-string, and do
write-char rather than collect in the loop?

-- 
Frode Vatvedt Fjeld
From: Kent M Pitman
Subject: Re: LOOP and COLLECT to an array?
Date: 
Message-ID: <sfwk7yympbl.fsf@world.std.com>
Frode Vatvedt Fjeld <······@acm.org> writes:

> Kalle Olavi Niemitalo <···@iki.fi> writes:
> 
> > [..] This however returns a list.  I looked for a LOOP value
> > accumulation clause that would return a string but didn't find one,
> > so I just put the LOOP inside (map 'string #'identity ...).
> 
> How about wrapping the loop in a with-output-to-string, and do
> write-char rather than collect in the loop?

Again, that would work.  But for so trivial an accumulation, this
might be overkill.  I'd like to think all Lisps did this efficiently,
but I've seen some that are high-overhead about the stream that with
with-output-to-string conses.  For something more complicated, I might
accept such overhead, but for something this trivial where I already
had another solution in hand, I wouldn't perturb what I had for a 
potentially less efficient solution.
From: Kent M Pitman
Subject: Re: LOOP and COLLECT to an array?
Date: 
Message-ID: <sfwlmjempfd.fsf@world.std.com>
Kalle Olavi Niemitalo <···@iki.fi> writes:

> 
> I have a list of "stacks" (lists) of "pieces".  Each piece has a
> "mnemonic character".  I want to collect the mnemonic character
> of the topmost piece in each stack, and build a string from them.
> After that, I want to make another string from the mnemonic
> characters of the second-from-top pieces, etc. until I have
> processed all the pieces.  I can freely modify the outer list, so
> I pop the stacks instead of using NTH.
> 
> The inner loop looks like this:
> 
>   (loop for stack-node on stacks
> 	for piece = (pop (car stack-node))
> 	collect (if piece
>                     (piece-mnemonic-char piece)
>                   #\Space))
> 
> This however returns a list.  I looked for a LOOP value
> accumulation clause that would return a string but didn't find
> one, so I just put the LOOP inside (map 'string #'identity ...).
> 
> Is this good Lisp, or is there a better way to write the loop and
> the conversion to string?  Should I first create a string with
> the correct size and then store the characters into it?

The Lisp is fine.  How you should do it depends on how much consing you
can afford.  It will cons less if you don't cons a list and then discard
it upon creating the string.  Then again, if you aren't in a super-critical
inner loop, it may just not matter. Note, of course, you could also do
 (map 'string #'(lambda (stack-node)
                  (let ((piece (pop (car stack-node))))
                    (if piece
                        (piece-mnemonic-char piece)
                      #\Space)))
      stacks)
(or something like that--I didn't test it) and avoid the intermediate list.
From: Kalle Olavi Niemitalo
Subject: Re: LOOP and COLLECT to an array?
Date: 
Message-ID: <87vgii8kvo.fsf@Astalo.y2000.kon.iki.fi>
Kent M Pitman <······@world.std.com> writes:

> Then again, if you aren't in a super-critical inner loop, it
> may just not matter.

The function is for debugging, so I optimize for readability.
Hmm, maybe I should then forget about stacks and just use NTH.

> Note, of course, you could also do
>  (map 'string #'(lambda (stack-node)
>                   (let ((piece (pop (car stack-node))))
>                     (if piece
>                         (piece-mnemonic-char piece)
>                       #\Space)))
>       stacks)
> (or something like that--I didn't test it) and avoid the intermediate list.

Remember I used (loop for stack-node ON stacks ...), so that (pop
(car stack-node)) affects the list itself.  With MAP, I'd have to
change STACKS from ((#<piece> #<piece>) (#<piece>)) to
(((#<piece> #<piece>)) ((#<piece>))).  I feel this would make the
code more difficult to read.

There is MAPLIST, but that always returns a list.
From: Kent M Pitman
Subject: Re: LOOP and COLLECT to an array?
Date: 
Message-ID: <sfw3d5lii3g.fsf@world.std.com>
Kalle Olavi Niemitalo <···@iki.fi> writes:

> 
> Kent M Pitman <······@world.std.com> writes:
> 
> > Then again, if you aren't in a super-critical inner loop, it
> > may just not matter.
> 
> The function is for debugging, so I optimize for readability.
> Hmm, maybe I should then forget about stacks and just use NTH.
> 
> > Note, of course, you could also do
> >  (map 'string #'(lambda (stack-node)
> >                   (let ((piece (pop (car stack-node))))
> >                     (if piece
> >                         (piece-mnemonic-char piece)
> >                       #\Space)))
> >       stacks)
> > (or something like that--I didn't test it) and avoid the intermediate list.
> 
> Remember I used (loop for stack-node ON stacks ...), so that (pop
> (car stack-node)) affects the list itself.  With MAP, I'd have to
> change STACKS from ((#<piece> #<piece>) (#<piece>)) to
> (((#<piece> #<piece>)) ((#<piece>))).  I feel this would make the
> code more difficult to read.
> 
> There is MAPLIST, but that always returns a list.

Oops. Sorry, missed that.