All Downloads are FREE. Search and download functionalities are using the official Maven repository.

overflowdb.algorithm.LowestCommonAncestors.scala Maven / Gradle / Ivy

package overflowdb.algorithm

import scala.annotation.tailrec

object LowestCommonAncestors {

  /**
   * Find the lowest common ancestor(s) for a set of nodes in a directed acyclic graph (DAG).
   * @return Set.empty if given nodes have cyclic dependencies
   *
   * Algorithm:
   * 1) for each relevant node, find their recursive parents
   * 2) create the intersection of all of those sets
   * 3) the LCA are those nodes, that do not have any children in that set
   *
   * based on https://www.baeldung.com/cs/lowest-common-ancestor-acyclic-graph
   */
  def apply[A: GetParents](nodes: Set[A]): Set[A] = {
    if (nodes.size <= 1) {
      nodes
    } else {
      val (head, tail) = (nodes.head, nodes.tail)
      val parentsIntersection = tail.foldLeft(parentsRecursive(head)) {
        case (res, next) =>
          res.intersect(parentsRecursive(next))
      }

      parentsIntersection.filter { node =>
        val childCount = parentsIntersection.count(parentsRecursive(_).contains(node))
        childCount == 0
      }
    }
  }

  def parentsRecursive[A: GetParents](node: A): Set[A] =
    parentsRecursive0(Set(node), Set.empty, Set.empty)

  @tailrec
  private def parentsRecursive0[A: GetParents](nodes: Set[A], accumulator: Set[A], visited: Set[A]): Set[A] = {
    if (nodes.isEmpty || nodes.forall(visited.contains))
      accumulator
    else {
      val getParents = implicitly[GetParents[A]]
      val parents = nodes.flatMap(getParents.apply)
      val nextAccumulator = accumulator ++ parents

      parentsRecursive0(parents, nextAccumulator, visited ++ nodes)
    }

  }


}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy