clojurelowest-common-ancestor

Lowest common ancestor in parent/child isa? hierarchy in Clojure


Let's say we have this parent/child hierarchy:

(derive ::integer ::decimal)
(derive ::positive-integer ::integer)
(derive ::long ::integer)

What is a Clojure idiomatic to implement a way to find the lowest common ancestor in such a hierarchy? I.e.:

(lca ::positive-integer ::long) ; => ::integer

My initial thoughts include using a recursive function traversing combinations of parents of each argument, but I suspect there probably is a better approach.

My motivation is to use this as a dispatch function for a multimethod that takes 2 arguments and dispatches to the best suited implementation based on the types of the arguments.


Solution

  • The function ancestors returns a set, so you'll want to (require [clojure.set :as s]).

    Now write:

    (defn lca [h1 h2]
      (let [a1 (into #{} (conj (ancestors h1) h1))
            a2 (into #{} (conj (ancestors h2) h2))
            ac (s/intersection a1 a2)]
        (apply (partial max-key (comp count ancestors)) ac)))
    

    Let's try it out!

    stack-prj.hierarchy> (derive ::integer ::decimal)
    nil
    stack-prj.hierarchy> (derive ::positive-integer ::integer)
    nil
    stack-prj.hierarchy> (derive ::long ::integer)
    nil
    stack-prj.hierarchy> (lca ::positive-integer ::long)
    :stack-prj.hierarchy/integer
    

    Here's how it works: I take the set of ancestors of each type using ancestors. I add the type itself to the resulting set (since I think (lca ::integer ::long) should return integer instead of decimal) using conj, for both types. Using set intersection, I store all common ancestors into the variable ac.

    Of the common ancestors, I want to know which one of those has the most ancestors. (comp count ancestors) is a function which takes one type and returns the number of ancestors it has. I partially apply max-key to this function, and then I apply (using apply) the resulting function to the collection ac. The result is the common ancestor with the greatest number of ancestors, or the least common ancestor.

    (Note that lca will give an error if you pass it two types without any common ancestors! You should decide how to handle this case yourself.)