From: ·············@gmail.com
Subject: A more elegant solution to this problem?
Date: 
Message-ID: <1157826233.563809.4000@p79g2000cwp.googlegroups.com>
Here's the basic situation...I have a list of structures (or
dictionary):

(defstruct entry
  "A string for the native word, a list of strings for the
translation(s), and a list of strings for the notes."
  native
  translation
  note)

I want a function or macro which returns the value of the dictionary
argument if all entries are removed such
that each argument is either equal to its corresponding value in the
entry or null.

I have:

(defmacro remove-entry (dict nat trans note)
  "Returns the value of the dictionary argument if all entries are
removed such
that each argument is either equal to its corresponding value in the
entry or null."
  `(remove-if #'(lambda (ent)
                  (and (or (equal (entry-native ent) ,nat)
                           (null ,nat))
                       (or (equal (entry-translation ent) ,trans)
                           (null ,trans))
                       (or (equal (entry-note ent) ,note)
                           (null ,note))))
              ,dict))

Is this the best way to handle it?

From: John Thingstad
Subject: Re: A more elegant solution to this problem?
Date: 
Message-ID: <op.tfmjwiy8pqzri1@pandora.upc.no>
On Sat, 09 Sep 2006 20:23:53 +0200, <·············@gmail.com> wrote:

> Here's the basic situation...I have a list of structures (or
> dictionary):
>
> (defstruct entry
>   "A string for the native word, a list of strings for the
> translation(s), and a list of strings for the notes."
>   native
>   translation
>   note)
>
> I want a function or macro which returns the value of the dictionary
> argument if all entries are removed such
> that each argument is either equal to its corresponding value in the
> entry or null.
>
> I have:
>
> (defmacro remove-entry (dict nat trans note)
>   "Returns the value of the dictionary argument if all entries are
> removed such
> that each argument is either equal to its corresponding value in the
> entry or null."
>   `(remove-if #'(lambda (ent)
>                   (and (or (equal (entry-native ent) ,nat)
>                            (null ,nat))
>                        (or (equal (entry-translation ent) ,trans)
>                            (null ,trans))
>                        (or (equal (entry-note ent) ,note)
>                            (null ,note))))
>               ,dict))
>
> Is this the best way to handle it?
>

Well I don't see why it's a macro. Seems it should be a function.
Apart from that wouldn't a hash-table be more efficient?
Why do you need to compare all three entries?
Even if you have multiple entries per key wouldn't a bucket
be better? Of cource I don't really know how you intend to use
it or how large your dictionary is so it is hard to say.
But (native, translation) sounds more like a word translation
to me so it sounds quite large.

-- 
Using Opera's revolutionary e-mail client: http://www.opera.com/mail/
From: ·············@gmail.com
Subject: Re: A more elegant solution to this problem?
Date: 
Message-ID: <1157835483.334752.54460@i42g2000cwa.googlegroups.com>
John Thingstad wrote:
> Well I don't see why it's a macro. Seems it should be a function.
> Apart from that wouldn't a hash-table be more efficient?
> Why do you need to compare all three entries?
> Even if you have multiple entries per key wouldn't a bucket
> be better? Of cource I don't really know how you intend to use
> it or how large your dictionary is so it is hard to say.
> But (native, translation) sounds more like a word translation
> to me so it sounds quite large.

It's a macro because remove-if takes a one-argument function, so I
can't include the necessary information about the entry I want to
remove in the function, it has to be generated with each use.

It is a word dictionary, yes.

The reason it needs to compare all three is that there is a difference
between these two:

#S(ENTRY :NATIVE "Foo" :TRANSLATION "Bar" :NOTE "Notes")
#S(ENTRY :NATIVE "Foo" :TRANSLATION "Not Bar" :NOTE "Notes")

If it removed anything with, say, "Foo" as the native word, both would
be gone, even if we only wanted to remove an incorrect entry.
From: Barry Margolin
Subject: Re: A more elegant solution to this problem?
Date: 
Message-ID: <barmar-F2420F.18503509092006@comcast.dca.giganews.com>
In article <·······················@i42g2000cwa.googlegroups.com>,
 ·············@gmail.com wrote:

> John Thingstad wrote:
> > Well I don't see why it's a macro. Seems it should be a function.
> > Apart from that wouldn't a hash-table be more efficient?
> > Why do you need to compare all three entries?
> > Even if you have multiple entries per key wouldn't a bucket
> > be better? Of cource I don't really know how you intend to use
> > it or how large your dictionary is so it is hard to say.
> > But (native, translation) sounds more like a word translation
> > to me so it sounds quite large.
> 
> It's a macro because remove-if takes a one-argument function, so I
> can't include the necessary information about the entry I want to
> remove in the function, it has to be generated with each use.

Here's the function version.  All you have to do is remove all the 
backquotes and commas and it works identically.  Lexical scoping allows 
the function to refer to the parameter variables of the REMOVE-ENTRY 
function -- they don't have to be parameters of the inner function.

(defun remove-entry (dict nat trans note)
  "Returns the value of the dictionary argument if all entries are
removed such
that each argument is either equal to its corresponding value in the
entry or null."
  (remove-if #'(lambda (ent)
                  (and (or (null nat)
                           (string-equal (entry-native ent) nat))
                       (or (null trans)
                           (string-equal (entry-translation ent) trans))
                       (or (null note)
                           (string-equal (entry-note ent) note))))
              dict))

The other changes I made were to put the NULL checks ahead of the equal 
checks.  This takes advantage of OR's short-circuiting, so that it 
doesn't bother with the more expensive test if the corresponding 
parameter is null.  I also changed it to use STRING-EQUAL rather than 
EQUAL -- now that we've short-circuited, you don't have to worry about 
trying to use STRING-EQUAL with NIL.

One annoyance of the above code is that it checks each parameter for 
being null each time through the REMOVE-IF inner loop, even though this 
can never change.  Here's a more complex version that omits this:

(defun remove-entry (dict nat trans note)
  "Returns the value of the dictionary argument if all entries are
removed such
that each argument is either equal to its corresponding value in the
entry or null."
  (let ((native-check (if (null nat) (constantly t)
                        #'(lambda (x) (string-equal (entry-native x) 
nat))))
        (trans-check (if (null trans) (constantly t)
                       #'(lambda (x) (string-equal (entry-translation x) 
trans))))
        (note-check (if (null note) (constantly t)
                      #'(lambda (x) (string-equal (entry-note x) 
note)))))
    (remove-if #'(lambda (ent)
                   (and (funcall nat-check ent)
                        (funcall trans-check ent)
                        (funcall note-check ent)))
               dict)))

-- 
Barry Margolin, ······@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***
*** PLEASE don't copy me on replies, I'll read them in the group ***
From: ·············@gmail.com
Subject: Re: A more elegant solution to this problem?
Date: 
Message-ID: <1157862408.077703.143940@i3g2000cwc.googlegroups.com>
Barry Margolin wrote:

> (defun remove-entry (dict nat trans note)
>   "Returns the value of the dictionary argument if all entries are
> removed such
> that each argument is either equal to its corresponding value in the
> entry or null."
>   (let ((native-check (if (null nat) (constantly t)
>                         #'(lambda (x) (string-equal (entry-native x)
> nat))))
>         (trans-check (if (null trans) (constantly t)
>                        #'(lambda (x) (string-equal (entry-translation x)
> trans))))
>         (note-check (if (null note) (constantly t)
>                       #'(lambda (x) (string-equal (entry-note x)
> note)))))
>     (remove-if #'(lambda (ent)
>                    (and (funcall nat-check ent)
>                         (funcall trans-check ent)
>                         (funcall note-check ent)))
>                dict)))

Hey, thanks...I hadn't realized lexical scoping would take care of it,
and just assumed that the anonymous function couldn't access the
parameters. Thanks for all the help!

(By the way...I live in Winchester, MA, actually very close to the
Arlington-Winchester border...)
From: John Thingstad
Subject: Re: A more elegant solution to this problem?
Date: 
Message-ID: <op.tfmuymydpqzri1@pandora.upc.no>
On Sat, 09 Sep 2006 22:58:03 +0200, <·············@gmail.com> wrote:

>
> It's a macro because remove-if takes a one-argument function, so I
> can't include the necessary information about the entry I want to
> remove in the function, it has to be generated with each use.
>

Well this is a way:

(defun remove-entry (dict nat trans note)
   "Returns the value of the dictionary argument if all entries are
removed such
that each argument is either equal to its corresponding value in the
entry or null."
   (macrolet ((match (nat trans note)
                `(lambda (ent)
                   (and (or (equal (entry-native ent) ,nat)
                            (null ,nat))
                        (or (equal (entry-translation ent) ,trans)
                            (null ,trans))
                        (or (equal (entry-note ent) ,note)
                            (null ,note))))))
     (remove-if (match nat trans note) dict)))

-- 
Using Opera's revolutionary e-mail client: http://www.opera.com/mail/
From: Thomas A. Russ
Subject: Re: A more elegant solution to this problem?
Date: 
Message-ID: <ymiodtm8la4.fsf@sevak.isi.edu>
·············@gmail.com writes:

> John Thingstad wrote:
> > Well I don't see why it's a macro. Seems it should be a function.
> > Apart from that wouldn't a hash-table be more efficient?
> > Why do you need to compare all three entries?
> > Even if you have multiple entries per key wouldn't a bucket
> > be better? Of cource I don't really know how you intend to use
> > it or how large your dictionary is so it is hard to say.
> > But (native, translation) sounds more like a word translation
> > to me so it sounds quite large.
> 
> It's a macro because remove-if takes a one-argument function, so I
> can't include the necessary information about the entry I want to
> remove in the function, it has to be generated with each use.

Sure you can.  Lisp supports closures.  Try the following function:

(defun remove-entry (dict nat trans note)
  "Returns the value of the dictionary argument if all entries are
removed such
that each argument is either equal to its corresponding value in the
entry or null."
  (remove-if #'(lambda (ent)
                  (and (or (equal (entry-native ent) nat)
                           (null nat))
                       (or (equal (entry-translation ent) trans)
                           (null trans))
                       (or (equal (entry-note ent) note)
                           (null note))))
              dict))


> It is a word dictionary, yes.
> 
> The reason it needs to compare all three is that there is a difference
> between these two:
> 
> #S(ENTRY :NATIVE "Foo" :TRANSLATION "Bar" :NOTE "Notes")
> #S(ENTRY :NATIVE "Foo" :TRANSLATION "Not Bar" :NOTE "Notes")
> 
> If it removed anything with, say, "Foo" as the native word, both would
> be gone, even if we only wanted to remove an incorrect entry.

Well, the examples seem not to match your structure description of
translation and notes being LISTs of strings.

But perhaps a simpler design would be to introduce your own matches
predicate for entry structures like the following, and allowing a
separate wildcard value for structure matching, tentatively called
:ANY.

(defun entry-value-matches (v1 v2)
   (or (eq v1 :any)
       (eq v2 :any)
       (equal v1 v2)))
             
(defun entry-matches (e1 e2)
   (and (entry-value-matches (entry-native e1) (entry-native e2))
        (entry-value-matches (entry-translation e1) (entry-translation e2))
        (entry-value-matches (entry-note e1) (entry-note e2))))

Then you can filter trivially by the following:

(defun remove-entry (dict nat trans note)
  (remove (make-entry :native nat :translation trans :note note)
          dict
          :test #'entry-matches))


-- 
Thomas A. Russ,  USC/Information Sciences Institute