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.
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 ...
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.
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.
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
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
.
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).
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>{}, "!");
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With