Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Determine least common ancestor at compile-time

There are tons of questions about the least common ancestor algorithm, but this one is different because I'm trying to determine the LCA at compile-time, and my tree is neither binary nor a search tree, even though my simplified version may look like one.

Suppose you have a bunch of structures which contain a member typedef parent, which is another similar structure:

struct G
{
    typedef G parent;    // 'root' node has itself as parent
};

struct F
{
    typedef G parent;
};

struct E
{
    typedef G parent;
};

struct D
{
    typedef F parent;
};

struct C
{
     typedef F parent;
};

struct B
{
    typedef E parent;
};

struct A
{
     typedef E parent;
};

which together make a tree such as

A    B    C    D
 \  /      \  /
  E         F
   \       /
    \     /
     \   /
       G

NOTE: There is no inheritance relationship between the structures.

What I would like to do is create a type-trait least_common_ancestor such that:

least_common_ancestor<A, B>::type;    // E
least_common_ancestor<C, D>::type;    // F
least_common_ancestor<A, E>::type;    // E
least_common_ancestor<A, F>::type;    // G

What's the best way to do this?

I'm not concerned with algorithmic complexity particularly since the tree depth is small, rather I'm looking for the simplest meta-program that that will get to the correct answer.

EDIT: I need to be able to build the solution with msvc2013, among other compilers, so answers without constexpr are preferred.

like image 343
Nicolas Holthaus Avatar asked Apr 11 '16 14:04

Nicolas Holthaus


People also ask

What is lowest common ancestor explain the algorithm?

In graph theory and computer science, the lowest common ancestor (LCA) (also called least common ancestor) of two nodes v and w in a tree or directed acyclic graph (DAG) T is the lowest (i.e. deepest) node that has both v and w as descendants, where we define each node to be a descendant of itself (so if v has a direct ...

What approach will you follow to find the lowest common ancestor LCA of any two nodes in BST?

Iterative Approach If both the nodes node1 and node2 are in the right subtree, then iteratively move to the right subtree. If both the nodes node1 and node2 are in the left subtree, then iteratively move to the left subtree. Otherwise, the current node is the lowest common ancestor.


3 Answers

This may probably be improved, but you could first compute the depth of your type and then use this information to go up one branch or the other:

template <typename U, typename = typename U::parent>
struct depth {
    static const int value = depth<typename U::parent>::value + 1;
};

template <typename U>
struct depth<U, U> {
    static const int value = 0;
};

The above will basically compute the depth of your type in your tree.

Then you could use std::enable_if:

template <typename U, typename V, typename Enabler = void>
struct least_common_ancestor;

template <typename U>
struct least_common_ancestor<U, U> {
    using type = U;
};

template <typename U, typename V>
struct least_common_ancestor<U, V,
                             typename std::enable_if<(depth<U>::value < depth<V>::value)>::type> {
    using type = typename least_common_ancestor<U, typename V::parent>::type;
};

template <typename U, typename V>
struct least_common_ancestor<U, V,
                             typename std::enable_if<(depth<V>::value < depth<U>::value)>::type> {
    using type = typename least_common_ancestor<V, typename U::parent>::type;
};

template <typename U, typename V>
struct least_common_ancestor<U, V,
                             typename std::enable_if<!std::is_same<U, V>::value && (depth<V>::value == depth<U>::value)>::type> {
    using type = typename least_common_ancestor<typename V::parent, typename U::parent>::type;
};

Output:

int main(int, char *[]) {

    std::cout << std::is_same<least_common_ancestor<A, B>::type, E>::value << std::endl;
    std::cout << std::is_same<least_common_ancestor<C, D>::type, F>::value << std::endl;
    std::cout << std::is_same<least_common_ancestor<A, E>::type, E>::value << std::endl;
    std::cout << std::is_same<least_common_ancestor<A, F>::type, G>::value << std::endl;
    std::cout << std::is_same<least_common_ancestor<A, A>::type, A>::value << std::endl;

    return 0;
}

Gives:

1 1 1 1 1

This may probably be improved but could be used as a starting point.

like image 171
Holt Avatar answered Oct 05 '22 08:10

Holt


template <typename...> struct typelist {};

template <typename T, typename... Ts>
struct path : path<typename T::parent, T, Ts...> {};

template <typename T, typename... Ts>
struct path<T, T, Ts...> { using type = typelist<T, Ts...>; };

template <typename T, typename U>
struct least;

template <typename T, typename... Vs, typename... Us>
struct least<typelist<T, Vs...>, typelist<T, Us...>> { using type = T; };

template <typename T, typename W, typename... Vs, typename... Us>
struct least<typelist<T, W, Vs...>, typelist<T, W, Us...>>
    : least<typelist<W, Vs...>, typelist<W, Us...>> {};

template <typename V, typename U>
using least_common_ancestor = least<typename path<V>::type, typename path<U>::type>;

DEMO


  1. Bottom-up: Form paths (path::type) from both nodes to the root by means of prepending a typelist at each level (path<?, T, Ts...>), until parent equals the currently processed node (<T, T, ?...>). Moving upwards is performed by replacing T by T::parent.

  2. Top-down: Descend the two typelists simultaneously (least), until there's a mismatch at corresponding positions (Vs..., Us...); if so, the last common node is the common ancestor (T); otherwise (<T, W, ?...>, <T, W, ?...>), remove the matching node (T) and step one level down (now W is the last known common node).

like image 22
Piotr Skotnicki Avatar answered Oct 05 '22 10:10

Piotr Skotnicki


This is probably not the most algorithmically efficient approach, but it's functional.

First, we're going to create lists out of the ancestors of each type. So for A this would be <A,E,G> and for G this would be <G>:

template <class X>
using parent_t = typename X::parent;

template <class... > struct typelist {}; 
template <class T> struct tag_t { using type = T; };

template <class, class> struct concat;
template <class X, class Y> using concat_t = typename concat<X, Y>::type;

template <class... Xs, class... Ys> 
struct concat<typelist<Xs...>, typelist<Ys...>>
: tag_t<typelist<Xs..., Ys...>>
{ };

template <class X, class = parent_t<X>>
struct ancestors
    : tag_t<concat_t<typelist<X>, typename ancestors<parent_t<X>>::type>>
{ };

template <class X>
struct ancestors<X, X>
    : tag_t<typelist<X>>
{ };

template <class X>
using ancestors_t = typename ancestors<X>::type;

Then the least common ancestor of two nodes is just going to be the first node in one node's ancestors that is contained in the other node's ancestors:

template <class X, class TL> struct contains;
template <class X, class TL> using contains_t = typename contains<X, TL>::type;

template <class X, class... Xs>
struct contains<X, typelist<X, Xs...>> : std::true_type { };

template <class X, class Y, class... Xs>
struct contains<X, typelist<Y, Xs...>> : contains_t<X, typelist<Xs...>> { };

template <class X>
struct contains<X, typelist<>> : std::false_type { };

template <class X, class Y>
struct lca_impl;

template <class X, class Y>
struct lca : lca_impl<ancestors_t<X>, ancestors_t<Y>> { };

template <class X, class... Xs, class TL>
struct lca_impl<typelist<X, Xs...>, TL>
    : tag_t<
        typename std::conditional_t<contains_t<X, TL>::value,
            tag_t<X>,
            lca_impl<typelist<Xs...>, TL>
            >::type
        >
{ };


template <class X, class Y>
using lca_t = typename lca<X, Y>::type;

which has the behavior you'd expect:

static_assert(std::is_same<lca_t<A, E>, E>{}, "!");
static_assert(std::is_same<lca_t<A, D>, G>{}, "!");
like image 20
Barry Avatar answered Oct 05 '22 10:10

Barry