Hi Rob,

Thanks for tracking this down. If you will execute a CA [1], I would love to 
get a patch (with tests) that fixes this. I have created a ticket at [2] to 
track it.

I would prefer something along the lines of the simpler fix shown below, unless 
anybody pops up on this thread with a real-world use case for 
performance-critical underive.

Stu

[1] http://clojure.org/contributing
[2] 
https://www.assembla.com/spaces/clojure/tickets/382-fix-underive-for-multiple-inheritance

> As an alternative, if we're willing to rebuild the entire hierarchy,
> not just the subgraph that's affected by the underivation,
> we can just do this:
> 
> (defn easy-underive [h child parent]
>  (let [oldParents (:parents h)
>       childsParents (disj (clojure.set/union #{} (child oldParents))
> parent)
>       newParents (if (not-empty childsParents)
>                    (assoc oldParents child childsParents)
>                    (dissoc oldParents child))
>       derivation-seq (flatten (map #(cons (key %) (interpose (key %) (val
> %)))
>                           (seq newParents)))]
>    (reduce #(apply derive (cons %1 %2)) (make-hierarchy)
>           (partition 2 derivation-seq))))
> 
> It's less efficient, because it tosses out all of the information in
> the ancestor and descendant sets.  But it is a good deal simpler -- 10
> lines of code instead of 60 or so.  For small hierarchies this would
> be fine.  If anyone were to make large
> hierarchies which had to be modified efficiently, though, I think
> something like in the first message would be required.
> 
> On Jun 15, 4:29 pm, Rob Lachlan <robertlach...@gmail.com> wrote:
>> Oh God.  What broken formatting.  Sorry about that.
>> 
>> On Jun 15, 4:24 pm, Rob Lachlan <robertlach...@gmail.com> wrote:
>> 
>> 
>> 
>>> I think that the underive function for removing hierarchy
>>> relationships between keywords is broken.  I'll illustrate with an
>>> example and describe what I think the problems are.  I've got some
>>> code for a function which (I hope!) performs underive correctly, and
>>> I'd love it if people had a look.
>> 
>>> Consider a hierarchy with child-parent relationships:
>> 
>>> :c -> :p1,  :c -> :p2, :p1 -> :a1, :p1 -> :a2, :p2 -> :a2
>> 
>>> Which I'll illustrate with the world's worst ascii art:
>> 
>>> a1            a2
>>> |           /   |
>>> |       /       |
>>> |   /           |
>>> p1            p2
>>> |           /
>>> |       /
>>> |  /
>>> c
>> 
>>> ;  creating this hierarchy:
>>> user> (def h (reduce #(apply derive (cons %1 %2)) (make-hierarchy)
>>>                      [[:p1 :a1] [:p1 :a2] [:p2 :a2] [:c :p2] [:c :p1]]))
>>> #'user/h
>>> user> h
>>> {:parents {:c #{:p1 :p2}, :p2 #{:a2}, :p1 #{:a2 :a1}}, :ancestors {:c
>>> #{:p1 :p2 :a2 :a1}, :p2 #{:a2}, :p1 #{:a2 :a1}}, :descendants {:p1
>>> #{:c}, :p2 #{:c}, :a2 #{:p1 :p2 :c}, :a1 #{:p1 :c}}}
>> 
>>> Now the underive:
>> 
>>> user> (underive h :c :p1)
>>> {:parent {:c #{:p2}, :p2 #{:a2}, :p1 #{:a2 :a1}}, :ancestors {:c
>>> #{:p2}, :p2 #{:a2}, :p1 #{:a2 :a1}}, :descendants {:p1 #{}, :p2
>>> #{:c}, :a2 #{:p1 :p2}, :a1 #{:p1}}}
>> 
>>> Problems:
>> 
>>> Most seriously, it is incorrect: :c should still show up as a
>>> descendant of
>>> :a2, since :c is a child of :p2, and :p2 a child of :a2.  Note that
>>> the
>>> parent map is correct, so it is the ancestor and descendant maps which
>>> are
>>> inconsistent.
>> 
>>> Also, notice that the key for the parent map has changed from :parents
>>> to
>>> :parent, which causes calls to the parents function to return nil.
>> 
>>> Also, keys which map to empty sets are left in each of the maps
>>> (parents,
>>> descendants and ancestors), with the result that equality tests could
>>> fail
>>> in some cases in which they would be true.
>> 
>>> Potential fix, and request for code review:
>> 
>>> I've written an underive-new function which appears to fix these
>>> problems,
>>> at least in the tests that I've done.
>> 
>>> http://pastebin.com/eRiz5ihw
>> 
>>> My approach is to identify the sets of tags in the hierarchy whose
>>> ancestors
>>> or descendants may be affected, remove their ancestor or descendant
>>> mappings,
>>> then rebuild them.  In a call (underive h child parent), the child and
>>> all
>>> the child's descendants will need to have their ancestor mappings
>>> fixed.
>>> Similarly, the parent and all of the parent's ancestors will need to
>>> have
>>> their descendant mappings rebuilt.
>> 
>>> (As an aside, the problem here is that our hierarchy can be a DAG
>>> rather
>>> a tree, because multiple inheritance is allowed.  And therefore, for
>>> any
>>> ancestor/descendant relationship it's comparatively expensive to
>>> determine
>>> whether a particular parent-child edge is essential or not.)
>> 
>>> Next, the two sets of interest (child + child's descendants, and
>>> parent +
>>> parent's ancestors) are sorted topologically.  For the child +
>>> child's
>>> descendants, we can now rebuild the ancestor mappings by taking a tag
>>> from
>>> the top of the set, setting its ancestors to the union of tag's
>>> parents and
>>> parents' ancestors, and repeating the process on the next tag in
>>> topologically
>>> sorted order until all tags are consumed.  The parent + parent's
>>> ancestors
>>> can be done in a similar way, except that a children function is
>>> required, and
>>> the tags must be topologically sorted in the reverse direction.
>> 
>>> Some remarks:
>> 
>>> An alternative approach would be to just recompute the whole ancestor
>>> and
>>> descendant maps for the whole hierarchy.  This would be simpler, but
>>> for
>>> large hierarchies would be unnecessarily expensive.
>> 
>>> The topological sort function which I wrote avoids recursion.  If we
>>> wanted
>>> to allow multiple recursion, it could be simplified a bit, but I
>>> wanted to
>>> avoid stack consumption.
>> 
>>> Anyway, I'd appreciate any suggestions and criticisms, since this is
>>> the
>>> first clojure code of mine that anyone has looked at.
> 
> -- 
> You received this message because you are subscribed to the Google
> Groups "Clojure" group.
> To post to this group, send email to clojure@googlegroups.com
> Note that posts from new members are moderated - please be patient with your 
> first post.
> To unsubscribe from this group, send email to
> clojure+unsubscr...@googlegroups.com
> For more options, visit this group at
> http://groups.google.com/group/clojure?hl=en

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Reply via email to