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

japgolly.microlibs.utils.TransitiveClosure.scala Maven / Gradle / Ivy

The newest version!
package japgolly.microlibs.utils

import cats.Eval
import japgolly.microlibs.utils.TransitiveClosure.Filter
import japgolly.univeq.UnivEq
import scala.collection.immutable.BitSet
import scala.reflect.ClassTag

object TransitiveClosure {
  def auto[A: UnivEq: ClassTag](as            : IterableOnce[A])
                               (directChildren: A => Iterable[A],
                                filter        : A => Filter = Filter.followAll) = {

    val map = as.iterator.zipWithIndex.toMap
    val array = new Array[A](map.size)
    for ((k,v) <- map)
      array(v) = k
    new TransitiveClosure(map.apply, array.apply, array.length, directChildren, filter)
  }

  sealed abstract class Filter
  object Filter {

    /** Include the subject node and follow its vertices. */
    case object Follow extends Filter

    /** Include the subject node but do not follow its vertices. */
    case object Terminal extends Filter

    /** Exclude the subject node and its vertices. */
    case object Exclude extends Filter

    val followAll: Any => Filter =
      _ => Follow

    def terminalSet[A](terminals: Set[A]): A => Filter =
      a => if (terminals.contains(a)) Terminal else Follow
  }
}

/**
 * Only works with acyclic digraphs.
 * Closure is also reflexive.
 *
 * Laws
 * ====
 * i ∈ [0,size)
 * a2i.i2a = id
 *
 * @param a2i Global index of a node.
 * @param i2a Node at global index.
 * @param directChildren Each direct child of a given node. Non-transitive; don't return self.
 */
final class TransitiveClosure[A: UnivEq](a2i           : A => Int,
                                         i2a           : Int => A,
                                         size          : Int,
                                         directChildren: A => Iterable[A],
                                         filter        : A => Filter) {

  private var traversing: BitSet =
    BitSet.empty

  private val closure: Array[Eval[BitSet]] =
    new Array(size)

  // Init
  for (i <- 0 until size) {
    closure(i) = Eval.later[BitSet] {
      val a = i2a(i)
      val z = BitSet.empty + i
      traversing += i
      try
        directChildren(a).foldLeft(z) { (q, c) =>
          filter(c) match {
            case Filter.Follow =>
              val ci = a2i(c)
              if (traversing(ci))
                q // cycle found
              else
                q ++ tc(ci)
            case Filter.Terminal => q + a2i(c)
            case Filter.Exclude  => q
          }
        }
      finally
        traversing -= i
    }
  }

  @inline private def tc(i: Int): BitSet =
    closure(i).value

//  private def a2io(a: A): Option[Int] =
//    try Some(a2i(a)) catch {
//      case _: Throwable => None
//    }

  private def a2is(a: A)(f: Int => Set[A]): Set[A] =
//    a2io(a).fold(empty)(f)
    f(a2i(a))

  @inline private def empty = UnivEq.emptySet[A]

  /** Reflexive */
  def apply(a: A): Set[A] =
    a2is(a)(i =>
      tc(i).foldLeft(empty)(_ + i2a(_)))

  /** Non-reflexive */
  def nonRefl(a: A): Set[A] =
    a2is(a)(i =>
      tc(i).foldLeft(empty)((q, j) =>
        if (i == j) q else q + i2a(j)))
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy