From: Erik R.
Subject: loop collecting vs. mapcar
Date: 
Message-ID: <1183824219.951636.276800@o61g2000hsh.googlegroups.com>
Greetings,

I'm new here.  To practice my list manipulation, I'm writing a simple
card game.  I'm representing cards as cons cells with rank and suit.
So,

---------
(defvar *suits* '(S H D C)
  "The suits in a standard deck of playing cards")
(defvar *ranks* '(A K Q J T 9 8 7 6 5 4 3 2)
  "The ranks in a standard deck of playing cards")

> hand
((6 . H) (K . S) (6 . D) (T . C) (9 . C) (7 . D) (K . C) (6 . C) (4 .
D) (4 . H) (Q . H) (T . S) (5 . H))
---------
To count the number of cards in each suit, I've got:
---------
(defun num-cards-in-suit (hand suit)
  (count-if #'(lambda (card) (eq (cdr card) suit)) hand))

> (num-cards-in-suit hand 'h)
4
---------

This all seems pretty sound to me.  But by all means, feel free to
point out a better way.  Now, on to my question:

Now I want to write a function called "distribution" to get the counts
for each suit.  My first attempt was:
---------
(defun distribution (hand)
  (loop for suit in *suits*
        collect (num-cards-in-suit hand suit)))

> (distribution hand)
(2 4 3 4)
---------
This is beautiful.  It couldn't be clearer to read or write.  Even
someone who's never seen Lisp before could probably figure out what it
was doing.

But then I realized that I could write it another way:
---------
(defun distribution (hand)
  (mapcar #'(lambda (suit) (num-cards-in-suit hand suit)) *suits*))
---------
This is much less clear to read, but I assume that it must be more
efficient than the huge lump of code that the loop macro expands into.

It seems to me that mapcar could be used for most simple "loop
collecting" patterns.  My question, to you seasoned lispers, is: Which
is better?  Faster, better style, etc.

Any other style pointers you have about my way of going about things
are more than welcome as well.

Cheers,
Erik

From: jayessay
Subject: Re: loop collecting vs. mapcar
Date: 
Message-ID: <m3myy8tbk9.fsf@sirius.goldenthreadtech.com>
"Erik R." <·············@gmail.com> writes:

> ---------
> (defun distribution (hand)
>   (loop for suit in *suits*
>         collect (num-cards-in-suit hand suit)))
> 
> > (distribution hand)
> (2 4 3 4)
> ---------
> This is beautiful.  It couldn't be clearer to read or write.  Even
> someone who's never seen Lisp before could probably figure out what it
> was doing.
> 
> But then I realized that I could write it another way:
> ---------
> (defun distribution (hand)
>   (mapcar #'(lambda (suit) (num-cards-in-suit hand suit)) *suits*))
> ---------
> This is much less clear to read, but I assume that it must be more
> efficient than the huge lump of code that the loop macro expands into.

What "huge lump of code" does the loop expand into?  Here's what I get
in ACL, fully expanded:

(block nil
  (let ((suit nil) (#:g123243 *suits*))
    (declare (ignorable suit) (type list #:g123243))
    (let* ((#:g123244 (list nil)) (#:g123245 #:g123244))
      (tagbody
        excl::next-loop
          (if (endp #:g123243) (progn nil (go excl::end-loop)) nil)
          (setq suit (car #:g123243))
          (setq #:g123243 (cdr #:g123243))
          (rplacd #:g123245
                  (setq #:g123245
                        (list (num-cards-in-suit hand suit))))
          (go excl::next-loop)
        excl::end-loop
          (return-from nil (cdr #:g123244))))))

That should compile down to 10-15 machine instructions (of course that
will include a couple calls to other functions).  OTOH, mapcar is
likely highly optimized by the implementation for what it does.  Here,
I doubt there is really much difference one way or the other on this
point.

Stylistically, I'd go with the mapcar, for several reasons -
functional, the use case here is exactly what it is intended for, the
loop is just reimplementiong mapcar, and map* is clearer and cleaner
for this sort of case.  Of course, YMMV...


/Jon

-- 
'j' - a n t h o n y at romeo/charley/november com
From: Rob St. Amant
Subject: Re: loop collecting vs. mapcar
Date: 
Message-ID: <f6ogoj$26b$1@blackhelicopter.databasix.com>
"Erik R." <·············@gmail.com> writes:

> Greetings,
>
> I'm new here.  To practice my list manipulation, I'm writing a simple
> card game.  I'm representing cards as cons cells with rank and suit.
> So,
>
> ---------
> (defvar *suits* '(S H D C)
>   "The suits in a standard deck of playing cards")
> (defvar *ranks* '(A K Q J T 9 8 7 6 5 4 3 2)
>   "The ranks in a standard deck of playing cards")
>
>> hand
> ((6 . H) (K . S) (6 . D) (T . C) (9 . C) (7 . D) (K . C) (6 . C) (4 .
> D) (4 . H) (Q . H) (T . S) (5 . H))
> ---------
> To count the number of cards in each suit, I've got:
> ---------
> (defun num-cards-in-suit (hand suit)
>   (count-if #'(lambda (card) (eq (cdr card) suit)) hand))
>
>> (num-cards-in-suit hand 'h)
> 4
> ---------
>
> This all seems pretty sound to me.  But by all means, feel free to
> point out a better way.  Now, on to my question:
>
> Now I want to write a function called "distribution" to get the counts
> for each suit.  My first attempt was:
> ---------
> (defun distribution (hand)
>   (loop for suit in *suits*
>         collect (num-cards-in-suit hand suit)))
>
>> (distribution hand)
> (2 4 3 4)

Just a couple of stylistic comments:

Because distributions rely implicitly on the ordering of *suits*, I
might use a defconstant and note this in the documentation.

It might be worthwhile to define card-suit and card-rank instead of
using car and cdr.  Not that there's much added clarity, but suppose
you'd like to represent a game in which some cards are up and some are
down, and you decide to use something other than a single cons to
represent cards?  You might end up having to change your code in lots
of different places.
From: Erik R.
Subject: Re: loop collecting vs. mapcar
Date: 
Message-ID: <1183831922.224412.217280@c77g2000hse.googlegroups.com>
On Jul 7, 6:58 pm, ·······@ncsu.edu (Rob St. Amant) wrote:
> "Erik R." <·············@gmail.com> writes:
> > Greetings,
>
> > I'm new here.  To practice my list manipulation, I'm writing a simple
> > card game.  I'm representing cards as cons cells with rank and suit.
> > So,
>
> > ---------
> > (defvar *suits* '(S H D C)
> >   "The suits in a standard deck of playing cards")
> > (defvar *ranks* '(A K Q J T 9 8 7 6 5 4 3 2)
> >   "The ranks in a standard deck of playing cards")
>
> >> hand
> > ((6 . H) (K . S) (6 . D) (T . C) (9 . C) (7 . D) (K . C) (6 . C) (4 .
> > D) (4 . H) (Q . H) (T . S) (5 . H))
> > ---------
> > To count the number of cards in each suit, I've got:
> > ---------
> > (defun num-cards-in-suit (hand suit)
> >   (count-if #'(lambda (card) (eq (cdr card) suit)) hand))
>
> >> (num-cards-in-suit hand 'h)
> > 4
> > ---------
>
> > This all seems pretty sound to me.  But by all means, feel free to
> > point out a better way.  Now, on to my question:
>
> > Now I want to write a function called "distribution" to get the counts
> > for each suit.  My first attempt was:
> > ---------
> > (defun distribution (hand)
> >   (loop for suit in *suits*
> >         collect (num-cards-in-suit hand suit)))
>
> >> (distribution hand)
> > (2 4 3 4)
>
> Just a couple of stylistic comments:
>
> Because distributions rely implicitly on the ordering of *suits*, I
> might use a defconstant and note this in the documentation.
>
> It might be worthwhile to define card-suit and card-rank instead of
> using car and cdr.  Not that there's much added clarity, but suppose
> you'd like to represent a game in which some cards are up and some are
> down, and you decide to use something other than a single cons to
> represent cards?  You might end up having to change your code in lots
> of different places.

Thanks for that.  I have made suit and rank functions that encapsulate
car and cdr.  I left them out here for brevity.

Originally I was doing a plist like '(:rank K :suit S) but that got
ugly fast.  Do you agree that a cons cell is probably better than
defining a whole card class?  It seems simpler, and I'd think it must
be better in terms memory and speed.

Matthias:  Being new to the language, I find loop a rather strange
creature to fully understand.  Due to its English-like nature, it's
pretty easy to read, but it's often hard to know what syntax is
available.

Thanks for your help guys.  This group is very responsive!

Erik
From: Matthias Buelow
Subject: Re: loop collecting vs. mapcar
Date: 
Message-ID: <5f9u22F3biviaU1@mid.dfncis.de>
Erik R. wrote:

[loop]
> This is beautiful.  It couldn't be clearer to read or write.  Even
> someone who's never seen Lisp before could probably figure out what it
> was doing.
...
[mapcar]
> This is much less clear to read, but I assume that it must be more
> efficient than the huge lump of code that the loop macro expands into.

I disagree; I always have trouble with loop, with it being nonobvious in
which way results are flowing and what the evaluation order is
(especially with more complex constructs). That's why I personally hate
loop with a vengeance and will never use it, but ymmv. I also can't see
how your loop example is more "beautiful" than the mapcar version, for
me it's the other way round.
Just so you get a different opinion (apparently there are many loop
lovers on this newsgroup).
From: Chris Russell
Subject: Re: loop collecting vs. mapcar
Date: 
Message-ID: <1183849327.468185.36910@22g2000hsm.googlegroups.com>
Erik R. wrote:

> This is much less clear to read, but I assume that it must be more
> efficient than the huge lump of code that the loop macro expands into.
>
> It seems to me that mapcar could be used for most simple "loop
> collecting" patterns.  My question, to you seasoned lispers, is: Which
> is better?  Faster, better style, etc.
>
> Any other style pointers you have about my way of going about things
> are more than welcome as well.

Ok, I guess the main style pointer is don't worry about efficiency
unless you need it, just try and express how you're solving the
problem as clearly as you can.

And if you are going to worry about efficiency, worry about what the
algorithm is doing in broad strokes first rather than the smallest
details. Your algorithm iterates across a linked list *suits* numbers
of times which is far more of a issue regarding the timing than mapcar
vs. loop.
From: Alan Crowe
Subject: Re: loop collecting vs. mapcar
Date: 
Message-ID: <86k5tbyh6b.fsf@cawtech.freeserve.co.uk>
"Erik R." <·············@gmail.com> writes:

> (defun num-cards-in-suit (hand suit)
>   (count-if #'(lambda (card) (eq (cdr card) suit)) hand))

> (defun distribution (hand)
>   (mapcar #'(lambda (suit) (num-cards-in-suit hand suit)) *suits*))

;;; I like to use a little macro, for writing functions that return functions

(defmacro defcurry (name primary-arg-list secondary-arg-list &body code)
  `(defun ,name ,primary-arg-list
    (lambda ,secondary-arg-list ,@code)))

;;; Then I can keep lambda and function and sharp quote out of my source
;;; by writing like this

(defcurry belongs-to (suit)(card)
  (eq (cdr card) suit))

(defcurry num-cards-in-suit (hand)(suit)
  (count-if (belongs-to suit) hand))

(defun distribution (hand)
  (mapcar (num-cards-in-suit hand) *suits*))

Alan Crowe
Edinburgh
Scotland
From: Tim Bradshaw
Subject: Re: loop collecting vs. mapcar
Date: 
Message-ID: <1183902342.601697.85470@w3g2000hsg.googlegroups.com>
On Jul 7, 5:03 pm, "Erik R." <·············@gmail.com> wrote:


> This is much less clear to read, but I assume that it must be more
> efficient than the huge lump of code that the loop macro expands into.

Don't worry about that.  The compiler will do a good job of removing
anything spurious (there probably isn't much, actually), and even if
it doesn't, worry about efficiency when it's apparent that this code
is on some critical path, not before.  Write the clear code.

--tim
From: Kaz Kylheku
Subject: Re: loop collecting vs. mapcar
Date: 
Message-ID: <1183968415.199530.254850@i38g2000prf.googlegroups.com>
On Jul 7, 9:03 am, "Erik R." <·············@gmail.com> wrote:
> ((6 . H) (K . S) (6 . D) (T . C) (9 . C) (7 . D) (K . C) (6 . C) (4 .
> D) (4 . H) (Q . H) (T . S) (5 . H))
> ---------
> To count the number of cards in each suit, I've got:
> ---------
> (defun num-cards-in-suit (hand suit)
>   (count-if #'(lambda (card) (eq (cdr card) suit)) hand))

(count suit hand :key #'cdr)

> Any other style pointers you have about my way of going about things
> are more than welcome as well.

Thou shalts know the parameters to thy sequences library.
From: Alain Picard
Subject: Re: loop collecting vs. mapcar
Date: 
Message-ID: <87bqemfsa8.fsf@memetrics.com>
"Erik R." <·············@gmail.com> writes:

>
> But then I realized that I could write it another way:
> ---------
> (defun distribution (hand)
>   (mapcar #'(lambda (suit) (num-cards-in-suit hand suit)) *suits*))
> ---------
> This is much less clear to read, but I assume that it must be more
> efficient than the huge lump of code that the loop macro expands into.

Don't assume.  Profile.  The chances are very high
that you'll find no efficiency difference between
the two versions.  But even if there _was_ some difference,
how large would it be before you decided to accept code
which, in your eyes, is harder to read (hence to maintain, modify etc)?
1%? 5%?  Where is the magic line?

> It seems to me that mapcar could be used for most simple "loop
> collecting" patterns.  My question, to you seasoned lispers, is: Which
> is better?  Faster, better style, etc.

Others have pointed you to idioms for currying and producing
readbable functions "on the fly", and indeed, I think that is
the most common idiom---however there is nothing wrong with
your loop version, and there's nothing even "non-idiomatic"
about it.

> Any other style pointers you have about my way of going about things
> are more than welcome as well.

I like you code - it's obvious and readable.  I suggest instead of
showing us beautiful code and saying "should I make this ugly?" you
should come back with some ugly code and say "how can I make
this beautiful?"  ;-)

Good luck!

                        --ap