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

izumi.fundamentals.collections.WildcardPrefixTree.scala Maven / Gradle / Ivy

package izumi.fundamentals.collections

import izumi.fundamentals.collections
import izumi.fundamentals.collections.WildcardPrefixTree.{BestMatch, PathElement}

import scala.annotation.tailrec

final case class WildcardPrefixTree[K, V](values: Seq[V], children: Map[PathElement[K], WildcardPrefixTree[K, V]]) {
  @tailrec
  def findExactMatch(prefix: Iterable[K]): Option[WildcardPrefixTree[K, V]] = {
    if (prefix.isEmpty) {
      Some(this)
    } else {
      val exact = children.get(PathElement.Value(prefix.head))
      val wildcard = children.get(PathElement.Wildcard)
      exact.orElse(wildcard) match {
        case Some(value) =>
          value.findExactMatch(prefix.tail)
        case None =>
          None
      }
    }
  }

  @tailrec
  def findBestMatch(prefix: Iterable[K]): BestMatch[K, V] = {
    if (prefix.isEmpty) {
      BestMatch(this, prefix)
    } else {
      val exact = children.get(PathElement.Value(prefix.head))
      val wildcard = children.get(PathElement.Wildcard)
      exact.orElse(wildcard) match {
        case Some(value) =>
          value.findBestMatch(prefix.tail)
        case None =>
          BestMatch(this, prefix)
      }
    }
  }

  def findAllMatchingSubtrees(prefix: Iterable[K]): Seq[WildcardPrefixTree[K, V]] = {
    if (prefix.isEmpty) {
      Seq(this)
    } else {
      val exact = children.get(PathElement.Value(prefix.head))
      val wildcard = children.get(PathElement.Wildcard)
      (exact.toSeq ++ wildcard.toSeq).flatMap(_.findAllMatchingSubtrees(prefix.tail))
    }
  }

  def allValuesFromSubtrees: Seq[V] = {
    values ++ children.values.flatMap(_.allValuesFromSubtrees)
  }

  def maxValues: Int = {
    (Seq(values.size) ++ children.values.map(_.maxValues)).max
  }
}

object WildcardPrefixTree {
  case class BestMatch[K, V](found: WildcardPrefixTree[K, V], unmatched: Iterable[K])

  sealed trait PathElement[+V]
  object PathElement {
    final case class Value[V](value: V) extends PathElement[V]
    case object Wildcard extends PathElement[Nothing]
  }

  def build[P, V](pairs: Seq[(Seq[Option[P]], V)]): WildcardPrefixTree[P, V] = {
    build(pairs, identity)
  }

  def build[P, V](pairs: Seq[(Seq[Option[P]], V)], map: (Seq[V] => Seq[V])): WildcardPrefixTree[P, V] = {
    val (currentValues, subValues) = pairs.partition(_._1.isEmpty)

    val next = subValues
      .collect {
        case (k :: tail, v) =>
          (k, (tail, v))
      }
      .groupBy(_._1)
      .toSeq
      .map {
        case (k, group) =>
          val wk: PathElement[P] = k match {
            case Some(value) =>
              PathElement.Value(value)
            case None =>
              PathElement.Wildcard
          }
          wk -> build(group.map(_._2), map)
      }
      .toMap

    val values = map(currentValues.map(_._2))

    collections.WildcardPrefixTree(values, next)
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy