From: Karl A. Krueger
Subject: My LOOP is ugly
Date: 
Message-ID: <c64tdh$ie8$1@baldur.whoi.edu>
There has -got- to be a nicer way to do this.  It's space-efficient;
time-efficient too except the assoc ... but it looks like I'm writing
Python code in Lisp.


(defun pairs-to-bunch (pairs &key ((:test test) #'eql))
  "((A 1) (A 2) (B 2) (C 2) (C 3)) ==> ((A 1 2) (B 2) (C 2 3))"
  (loop with bunch = nil
        for (one two) in pairs
        for other-ones = (assoc one bunch :test test)
        if (null other-ones)
            do (push (cons one (list two)) bunch)
        else
            do (push two (cdr other-ones))
        finally (return bunch)))


Here's the Python translation I did afterward:

def pairs_to_bunch(pairs):
	bunch = {}
	for (one, two) in pairs:
		if one not in bunch:
			bunch[one] = [two]
		else:
			bunch[one].append(two)
	return bunch
		

It's fine Python, but I feel like it's weird Lisp.  Am I being overly
critical?

-- 
Karl A. Krueger <········@example.edu>
Woods Hole Oceanographic Institution
Email address is spamtrapped.  s/example/whoi/
"Outlook not so good." -- Magic 8-Ball Software Reviews

From: Jeremy Yallop
Subject: Re: My LOOP is ugly
Date: 
Message-ID: <slrnc8c0q9.v19.jeremy@akan.cl.cam.ac.uk>
Karl A. Krueger wrote:
> (defun pairs-to-bunch (pairs &key ((:test test) #'eql))
>   "((A 1) (A 2) (B 2) (C 2) (C 3)) ==> ((A 1 2) (B 2) (C 2 3))"

"Pair" usually means cons, not list of two elements, FWIW.

>   (loop with bunch = nil
>         for (one two) in pairs
>         for other-ones = (assoc one bunch :test test)
>         if (null other-ones)
>             do (push (cons one (list two)) bunch)
>         else
>             do (push two (cdr other-ones))
>         finally (return bunch)))

Perhaps not terribly efficient on short lists, but:

  (defun pairs-to-bunchtable (pairs &key ((:test test) #'eql))
    (let ((table (make-hash-table :test test)))
      (dolist (pair pairs (hash-table->alist table))
        (push (second pair) (gethash (first pair) table)))))

Jeremy.
From: Jeremy Yallop
Subject: Re: My LOOP is ugly
Date: 
Message-ID: <slrnc8c0r6.v19.jeremy@akan.cl.cam.ac.uk>
Karl A. Krueger wrote:
> (defun pairs-to-bunch (pairs &key ((:test test) #'eql))
>   "((A 1) (A 2) (B 2) (C 2) (C 3)) ==> ((A 1 2) (B 2) (C 2 3))"

"Pair" usually means cons, not list of two elements, FWIW.

>   (loop with bunch = nil
>         for (one two) in pairs
>         for other-ones = (assoc one bunch :test test)
>         if (null other-ones)
>             do (push (cons one (list two)) bunch)
>         else
>             do (push two (cdr other-ones))
>         finally (return bunch)))

Perhaps not terribly efficient on short lists, but:

  (defun pairs-to-bunch (pairs &key ((:test test) #'eql))
    (let ((table (make-hash-table :test test)))
      (dolist (pair pairs (hash-table->alist table))
        (push (second pair) (gethash (first pair) table)))))

Jeremy.
From: Kenny Tilton
Subject: Re: My LOOP is ugly
Date: 
Message-ID: <m_nhc.50858$WA4.28784@twister.nyc.rr.com>
Karl A. Krueger wrote:
> There has -got- to be a nicer way to do this.  It's space-efficient;
> time-efficient too except the assoc ... but it looks like I'm writing
> Python code in Lisp.
> 
> 
> (defun pairs-to-bunch (pairs &key ((:test test) #'eql))
>   "((A 1) (A 2) (B 2) (C 2) (C 3)) ==> ((A 1 2) (B 2) (C 2 3))"
>   (loop with bunch = nil
>         for (one two) in pairs
>         for other-ones = (assoc one bunch :test test)
>         if (null other-ones)
>             do (push (cons one (list two)) bunch)
>         else
>             do (push two (cdr other-ones))
>         finally (return bunch)))
> 
> 
> Here's the Python translation I did afterward:
> 
> def pairs_to_bunch(pairs):
> 	bunch = {}
> 	for (one, two) in pairs:
> 		if one not in bunch:
> 			bunch[one] = [two]
> 		else:
> 			bunch[one].append(two)
> 	return bunch
> 		
> 
> It's fine Python, but I feel like it's weird Lisp.  Am I being overly
> critical?

(defun p2b (pairs &key ((:test test) #'eql))
   "((A 1) (A 2) (B 2) (C 2) (C 3)) ==> ((A 1 2) (B 2) (C 2 3))"
   (loop with bunch = nil
         for (one two) in pairs
         do (push two (cdr (or (assoc one bunch :test test)
                             (car (push (list one) bunch)))))
         finally (return bunch)))

?


kenny


-- 
Home? http://tilton-technology.com
Cells? http://www.common-lisp.net/project/cells/
Cello? http://www.common-lisp.net/project/cello/
Why Lisp? http://alu.cliki.net/RtL%20Highlight%20Film
Your Project Here! http://alu.cliki.net/Industry%20Application
From: Espen Vestre
Subject: Re: My LOOP is ugly
Date: 
Message-ID: <kwekqhwz69.fsf@merced.netfonds.no>
Kenny Tilton <·······@nyc.rr.com> writes:

> (defun p2b (pairs &key ((:test test) #'eql))
>    "((A 1) (A 2) (B 2) (C 2) (C 3)) ==> ((A 1 2) (B 2) (C 2 3))"
>    (loop with bunch = nil
>          for (one two) in pairs
>          do (push two (cdr (or (assoc one bunch :test test)
>                              (car (push (list one) bunch)))))
>          finally (return bunch)))

Nice version, but I'd ask the OP to consider using plists rather
than alists. Things like this usually look neater with
plists:

(defun p2pb (pairs)
  (loop with bunch = nil
        for (one two) in pairs
        do (push two (getf bunch one))
        finally (return bunch)))

or:

(defun p2pb (pairs)
  (let ((bunch nil))
    (mapc (lambda(pair)
            (push (second pair) (getf bunch (first pair))))
          pairs)
    bunch))
-- 
  (espen)
From: Kenny Tilton
Subject: Re: My LOOP is ugly
Date: 
Message-ID: <rZuhc.50955$WA4.27930@twister.nyc.rr.com>
Espen Vestre wrote:
> Kenny Tilton <·······@nyc.rr.com> writes:
> 
> 
>>(defun p2b (pairs &key ((:test test) #'eql))
>>   "((A 1) (A 2) (B 2) (C 2) (C 3)) ==> ((A 1 2) (B 2) (C 2 3))"
>>   (loop with bunch = nil
>>         for (one two) in pairs
>>         do (push two (cdr (or (assoc one bunch :test test)
>>                             (car (push (list one) bunch)))))
>>         finally (return bunch)))
> 
> 
> Nice version, but I'd ask the OP to consider using plists rather
> than alists. Things like this usually look neater with
> plists:
> 
> (defun p2pb (pairs)
>   (loop with bunch = nil
>         for (one two) in pairs
>         do (push two (getf bunch one))
>         finally (return bunch)))

Wow. I came to Lisp late in life, went straight for the CLOS and never 
paid much attention to punch-cardy plists. I knew about GET, but this is 
behavior of GETF is news to me. Gotta go dig up all the code where I 
re-invented it...

kenny

-- 
Home? http://tilton-technology.com
Cells? http://www.common-lisp.net/project/cells/
Cello? http://www.common-lisp.net/project/cello/
Why Lisp? http://alu.cliki.net/RtL%20Highlight%20Film
Your Project Here! http://alu.cliki.net/Industry%20Application
From: Espen Vestre
Subject: Re: My LOOP is ugly
Date: 
Message-ID: <kwbrlltnxe.fsf@merced.netfonds.no>
Kenny Tilton <·······@nyc.rr.com> writes:

> Wow. I came to Lisp late in life, went straight for the CLOS and never
> paid much attention to punch-cardy plists. 

I'm a closmaniac myself, but you always need plists!

If not for anything else, then for make-instance argument list 
construction :-)
-- 
  (espen)
From: Kenny Tilton
Subject: Re: My LOOP is ugly
Date: 
Message-ID: <K0xhc.50966$WA4.4342@twister.nyc.rr.com>
Espen Vestre wrote:

> Kenny Tilton <·······@nyc.rr.com> writes:
> 
> 
>>Wow. I came to Lisp late in life, went straight for the CLOS and never
>>paid much attention to punch-cardy plists. 
> 
> 
> I'm a closmaniac myself, but you always need plists!
> 
> If not for anything else, then for make-instance argument list 
> construction :-)

Well I had come close to getf when I monkey-see-monkey-did a remf on 
such an argument list, producing a long-standing undetected Cells bug 
since it never occurred to the monkey on duty that remf was destructive, 
despite the subtle tip-off in the name.

:)

kenny

-- 
Home? http://tilton-technology.com
Cells? http://www.common-lisp.net/project/cells/
Cello? http://www.common-lisp.net/project/cello/
Why Lisp? http://alu.cliki.net/RtL%20Highlight%20Film
Your Project Here! http://alu.cliki.net/Industry%20Application
From: Pascal Costanza
Subject: Re: My LOOP is ugly
Date: 
Message-ID: <c6emfe$eia$1@newsreader2.netcologne.de>
Kenny Tilton wrote:

> Wow. I came to Lisp late in life, went straight for the CLOS and never 
> paid much attention to punch-cardy plists. I knew about GET, but this is 
> behavior of GETF is news to me. Gotta go dig up all the code where I 
> re-invented it...

Recently I needed this, and found it very cool:

(incf (getf plist key 0))

This means: If the key is not bound in plist yet, assume 0 as its 
default value.

So:

? (let ((plist '(:test1 4)))
     (incf (getf plist :test1 0))
     (incf (getf plist :test2 0))
     plist)
(:TEST2 1 :TEST1 5)


Pascal

-- 
1st European Lisp and Scheme Workshop
June 13 - Oslo, Norway - co-located with ECOOP 2004
http://www.cs.uni-bonn.de/~costanza/lisp-ecoop/
From: Rob Warnock
Subject: Re: My LOOP is ugly
Date: 
Message-ID: <H6adnQNuK4TaPBbdRVn-sA@speakeasy.net>
Pascal Costanza  <········@web.de> wrote:
+---------------
| Recently I needed this, and found it very cool:
|   (incf (getf plist key 0))
| This means: If the key is not bound in plist yet, assume 0 as its 
| default value.
+---------------

And to bring this back around to hash tables (which the very
first responses in this thread suggested), for certain kinds
of text-processing tasks[1] I find myself doing this:

    (incf (gethash key ht 0))

or this:

    (push value (gethash key ht))	; NIL is fine as a default here


-Rob

[1] Histograms, checking for uniqueness, etc.

-----
Rob Warnock			<····@rpw3.org>
627 26th Avenue			<URL:http://rpw3.org/>
San Mateo, CA 94403		(650)572-2607
From: Hannah Schroeter
Subject: Re: My LOOP is ugly
Date: 
Message-ID: <c6gha6$l34$1@c3po.use.schlund.de>
Hello!

Pascal Costanza  <········@web.de> wrote:

>? (let ((plist '(:test1 4)))

You'd prefer
? (let ((plist (list :test1 4)))
    ...)
though, so you don't invoke undefined behaviour.

>     (incf (getf plist :test1 0))
>     (incf (getf plist :test2 0))
>     plist)
>(:TEST2 1 :TEST1 5)

Kind regards,

Hannah.
From: Karl A. Krueger
Subject: Re: My LOOP is ugly
Date: 
Message-ID: <c665pa$2ck$1@baldur.whoi.edu>
Espen Vestre <·····@*do-not-spam-me*.vestre.net> wrote:
> Kenny Tilton <·······@nyc.rr.com> writes:
>> (defun p2b (pairs &key ((:test test) #'eql))
>>    "((A 1) (A 2) (B 2) (C 2) (C 3)) ==> ((A 1 2) (B 2) (C 2 3))"
>>    (loop with bunch = nil
>>          for (one two) in pairs
>>          do (push two (cdr (or (assoc one bunch :test test)
>>                              (car (push (list one) bunch)))))
>>          finally (return bunch)))
> 
> Nice version, but I'd ask the OP to consider using plists rather
> than alists. Things like this usually look neater with
> plists:
> 
> (defun p2pb (pairs)
>  (loop with bunch = nil
>        for (one two) in pairs
>        do (push two (getf bunch one))
>        finally (return bunch)))

Wow.  Neat.

But GETF doesn't accept a :test argument like ASSOC does; it only uses
#'EQ ... so it does not work for strings, only symbols (or other things
that are EQ):

* (pairs-to-bunch '((llama rama) (llama pants)))
((LLAMA PANTS RAMA))

* (pairs-to-bunch '(("llama" "rama") ("llama" "pants")) :test #'string=)
(("llama" "pants" "rama"))

* (pairs-to-plist '((llama rama) (llama pants)))
(LLAMA (PANTS RAMA))

* (pairs-to-plist '(("llama" "rama") ("llama" "pants")))
("llama" ("pants") "llama" ("rama"))

And I'm working on strings, because that's what CLSQL hands me from my
database.  So I'm sticking with alists for this one, but I'll keep
plists in mind for when I can use 'em -- the push/getf combination is
quite spiffy.

-- 
Karl A. Krueger <········@example.edu>
Woods Hole Oceanographic Institution
Email address is spamtrapped.  s/example/whoi/
"Outlook not so good." -- Magic 8-Ball Software Reviews
From: Pascal Costanza
Subject: Re: My LOOP is ugly
Date: 
Message-ID: <c666ok$qga$1@f1node01.rhrz.uni-bonn.de>
Karl A. Krueger wrote:

> But GETF doesn't accept a :test argument like ASSOC does; it only uses
> #'EQ ... so it does not work for strings
[...]

> And I'm working on strings, because that's what CLSQL hands me from my
> database.

You could create a package for things handed from the database, and 
intern the strings you get in that package.


Pascal

-- 
ECOOP 2004 Workshops - Oslo, Norway
*1st European Lisp and Scheme Workshop, June 13*
http://www.cs.uni-bonn.de/~costanza/lisp-ecoop/
*2nd Post-Java Workshop, June 14*
http://prog.vub.ac.be/~wdmeuter/PostJava04/
From: Karl A. Krueger
Subject: Re: My LOOP is ugly
Date: 
Message-ID: <c66dt1$53k$1@baldur.whoi.edu>
Pascal Costanza <········@web.de> wrote:
> Karl A. Krueger wrote:
>> But GETF doesn't accept a :test argument like ASSOC does; it only uses
>> #'EQ ... so it does not work for strings
> [...]
>> And I'm working on strings, because that's what CLSQL hands me from my
>> database.
> 
> You could create a package for things handed from the database, and 
> intern the strings you get in that package.

Could.  #'STRING= is probably not that efficient, huh -- especially
since the strings in question are IP addresses with the same prefix.

Plenty to think about ... and this is just a simple report-generating
mod_lisp script.  :)

-- 
Karl A. Krueger <········@example.edu>
Woods Hole Oceanographic Institution
Email address is spamtrapped.  s/example/whoi/
"Outlook not so good." -- Magic 8-Ball Software Reviews
From: Wolfhard Buß
Subject: Re: My LOOP is ugly
Date: 
Message-ID: <m3wu49fkux.fsf@buss-14250.user.cis.dfn.de>
* Kenny Tilton:

> (defun p2b (pairs &key ((:test test) #'eql))
>    "((A 1) (A 2) (B 2) (C 2) (C 3)) ==> ((A 1 2) (B 2) (C 2 3))"
>    (loop with bunch = nil
>          for (one two) in pairs
>          do (push two (cdr (or (assoc one bunch :test test)
>                              (car (push (list one) bunch)))))
>          finally (return bunch)))

* Espen Vestre:

> Nice version, but I'd ask the OP to consider using plists rather
> than alists. Things like this usually look neater with
> plists:
>
> (defun p2pb (pairs)
>   (loop with bunch = nil
>         for (one two) in pairs
>         do (push two (getf bunch one))
>         finally (return bunch)))

Lispniks with get-assoc and (setf get-assoc) (modeled on getf) could
write

 (defun pairs-to-bunch (pairs &key (test #'eql))
   "((A 1) (A 2) (B 2) (C 2) (C 3)) ==> ((A 2 1) (B 2) (C 3 2))"
   (loop with bunch = nil
         for (one two) in pairs
         do (push two (get-assoc one bunch :test test))
         finally (return bunch)))

or something more elaborate, that respects the order of the pairs.


-- 
"Hurry if you still want to see something. Everything is vanishing."
                                       --  Paul C�zanne (1839-1906)
From: Michael Naunton
Subject: Re: My LOOP is ugly
Date: 
Message-ID: <slrnc8ecqr.leu.mmn@micron.bellatlantic.net>
On Wed, 21 Apr 2004, Karl A. Krueger <········@example.edu> wrote:
> 
> def pairs_to_bunch(pairs):
> 	bunch = {}
> 	for (one, two) in pairs:
> 		if one not in bunch:
> 			bunch[one] = [two]
> 		else:
> 			bunch[one].append(two)
> 	return bunch
> 		
> 

The direct translation looks very similar to me:

(defun pairs-to-bunch (pairs &key ((:test test) #'eql))
  (loop with bunch = ()
	for (first second) in pairs
	for group = (assoc first bunch :test test)
	do (if group
	       (push second (second group))
	       (push `(,first (,second)) bunch))
	finally (return bunch)))

-- MMN