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

scalax.collection.generic.edgeEquality.scala Maven / Gradle / Ivy

The newest version!
package scalax.collection
package generic

sealed protected[collection] trait Eq { this: Edge[_] =>
  protected def baseEquals(other: Edge[_]): Boolean
  protected def baseHashCode: Int

  override def equals(other: Any): Boolean = other match {
    case that: Edge[_] =>
      (this eq that) ||
      (that canEqual this) &&
      this.isDirected == that.isDirected &&
      this.isInstanceOf[MultiEdge] == that.isInstanceOf[MultiEdge] &&
      equals(that)
    case _ => false
  }

  /** Preconditions:
    *  `this.directed == that.directed &&`
    *  `this.isInstanceOf[Keyed] == that.isInstanceOf[Keyed]`
    */
  protected def equals(other: Edge[_]): Boolean = baseEquals(other)

  override def hashCode: Int = baseHashCode

}

protected[collection] object Eq {
  /* Works for both sets and bags.
   */
  private def nrEqualingNodes(itA: OneOrMore[_], itB: OneOrMore[_], bLen: Int): Int = {
    var nr   = 0
    val used = new Array[Boolean](bLen)
    for (a <- itA) {
      val bs = itB.iterator
      var j  = 0
      while (j < bLen) {
        val b = bs.next()
        if (!used(j) && a == b) {
          nr += 1
          used(j) = true
          j = bLen
        }
        j += 1
      }
    }
    nr
  }

  def equalEnds(
      left: Edge[_],
      leftEnds: OneOrMore[_],
      right: Edge[_],
      rightEnds: OneOrMore[_]
  ): Boolean = {
    val thisOrdered = left.isInstanceOf[OrderedEndpoints]
    val thatOrdered = right.isInstanceOf[OrderedEndpoints]

    thisOrdered == thatOrdered && (
      if (thisOrdered) leftEnds == rightEnds
      else {
        val rightSize = rightEnds.size
        Eq.nrEqualingNodes(leftEnds, rightEnds, rightSize) == rightSize
      }
    )
  }
}

protected[collection] trait EqHyper extends Eq {
  this: AnyHyperEdge[_] =>

  override protected def baseEquals(other: Edge[_]): Boolean = {
    val (thisArity, thatArity) = (arity, other.arity)
    if (thisArity == thatArity)
      Eq.equalEnds(this, this.ends.toOneOrMore, other, other.ends.toOneOrMore)
    else false
  }

  override protected def baseHashCode: Int = ends.foldLeft(0)(_ ^ _.hashCode)
}

/** Equality for targets handled as a $BAG.
  *  Targets are equal if they contain the same nodes irrespective of their position.
  */
protected[collection] trait EqDiHyper extends Eq {
  this: AnyDiHyperEdge[_] =>

  override protected def baseEquals(other: Edge[_]): Boolean = {
    val (thisArity, thatArity) = (arity, other.arity)
    if (thisArity == thatArity)
      if (thisArity == 2)
        this.sources.head == other.sources.head &&
        this.targets.head == other.targets.head
      else
        other match {
          case diHyper: AnyDiHyperEdge[_] =>
            Eq.equalEnds(this, this.sources, diHyper, diHyper.sources) &&
            Eq.equalEnds(this, this.targets, diHyper, diHyper.targets)
          case _ => false
        }
    else false
  }

  override protected def baseHashCode: Int = {
    var m = 4

    def mul(i: Int): Int = { m += 3; m * i }
    ends.foldLeft(0)((s: Int, n: Any) => s ^ mul(n.hashCode))
  }
}

protected[collection] trait EqUnDi[+N] extends Eq {
  this: AnyUnDiEdge[N] =>

  @inline final protected def unDiBaseEquals(n1: Any, n2: Any): Boolean =
    this.node1 == n1 && this.node2 == n2 ||
      this.node1 == n2 && this.node2 == n1

  override protected def baseEquals(other: Edge[_]): Boolean = other match {
    case edge: AnyEdge[_] => unDiBaseEquals(edge.node1, edge.node2)
    case hyper: AnyHyperEdge[_] if hyper.isUndirected && hyper.arity == 2 =>
      unDiBaseEquals(hyper.node(0), hyper.node(1))
    case _ => false
  }

  override protected def baseHashCode: Int = node1.## ^ node2.##
}

protected[collection] trait EqDi[+N] extends Eq {
  this: AnyDiEdge[N] =>

  @inline final protected def diBaseEquals(n1: Any, n2: Any): Boolean =
    this.source == n1 &&
      this.target == n2

  final override protected def baseEquals(other: Edge[_]): Boolean = other match {
    case edge: AnyDiEdge[_]                           => diBaseEquals(edge.source, edge.target)
    case hyper: AnyDiHyperEdge[_] if hyper.arity == 2 => diBaseEquals(hyper.sources.head, hyper.targets.head)
    case _                                            => false
  }

  override protected def baseHashCode: Int = 23 * node1.## ^ node2.##
}

/** Defines how to handle the ends of hyperedges, or the source/target ends of directed hyperedges,
  *  with respect to equality.
  */
sealed abstract class CollectionKind(val duplicatesAllowed: Boolean, val orderSignificant: Boolean)

object CollectionKind {
  protected[collection] def from(duplicatesAllowed: Boolean, orderSignificant: Boolean): CollectionKind =
    if (duplicatesAllowed)
      if (orderSignificant) Sequence else Bag
    else
      throw new IllegalArgumentException("'duplicatesAllowed == false' is not supported for endpoints kind.")

  protected[collection] def from(s: String): CollectionKind =
    if (s == Bag.toString) Bag
    else if (s == Sequence.toString) Sequence
    else throw new IllegalArgumentException(s"Unexpected representation of '$s' for endpoints kind.")

  protected[collection] def from(edge: Edge[_]): CollectionKind =
    CollectionKind.from(duplicatesAllowed = true, orderSignificant = edge.isInstanceOf[OrderedEndpoints])

  def unapply(kind: CollectionKind): Option[(Boolean, Boolean)] =
    Some((kind.duplicatesAllowed, kind.orderSignificant))
}

/** Marks a hyperedge, $ORDIHYPER, to handle the endpoints
  *  as an unordered collection of nodes with duplicates allowed.
  */
case object Bag extends CollectionKind(true, false)

/** Marks a hyperedge, $ORDIHYPER, to handle the endpoints
  *  as an ordered collection of nodes with duplicates allowed.
  */
case object Sequence extends CollectionKind(true, true)

/** Marks (directed) hyperedge endpoints to have a significant order. */
protected[collection] trait OrderedEndpoints

/** Edge mixin for multigraph support.
  *
  * As a default, hashCode/equality of edges is determined by their ends.
  * We say that edge ends are part of the edge key.
  *
  * Whenever your custom edge needs to be a multi-edge, meaning that your graph is allowed
  * to connect some nodes by an instance of this edge even if those nodes are already connected,
  * the edge key needs be extended by adding at least one more class member, constant or whatsoever to the edge key.
  *
  * For example edges representing flight connections between airports need be multi-edges to allow for different
  * flights between the same airports.
  *
  * @author Peter Empen
  */
trait MultiEdge { this: Edge[_] =>

  /** Each element in this sequence references a class member of the custom edge that will be added to the edge key.
    * The edge ends, like `source` and `target`, are always part of the key so don't add them here.
    * Once you add class members to the edge key they automatically become part of the edge's hashCode/equality.
    */
  def extendKeyBy: OneOrMore[Any]

  override def equals(other: Any): Boolean =
    super.equals(other) && (other match {
      case that: MultiEdge => this.extendKeyBy == that.extendKeyBy
      case _               => false
    })

  override def hashCode: Int = super.hashCode + extendKeyBy.map(_.## * 41).iterator.sum
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy