Import the following for the examples below:
import scalax.collection.edges._
import scalax.collection.hyperedges._
import scalax.collection.generic.AnyEdge
import scalax.collection.{OneOrMore, Several}
import scalax.collection.OuterImplicits._
import scalax.collection.mutable.Graph
Note that all the demonstrated features also work for immutable graphs unless commented to the contrary.
val g = Graph(2 ~ 3, 3 ~ 1)
g.toString // "Graph(NodeSet(1, 2, 3), EdgeSet(2 ~ 3, 3 ~ 1))"
g.nodes mkString ", " // "1, 2, 3"
g.edges mkString ", " // "2 ~ 3, 3 ~ 1"
g.outerIterator
.map {
case g.OuterNode(n) => n.toString
case g.OuterEdge(e) => e.toString
}
.mkString(", ") // 1, 2, 3, 2 ~ 3, 3 ~ 1
As to
nodes of the type NodeSetT returns the node set of the graph.edges of the type EdgeSetT returns the edge set of the graph.iterator or outerIterator to iterate over nodes and edges in one go.
NodeSetT and EdgeSetT are inner classes that extend scala.collection.Set
by also adding a few methods like draw for picking a random element.
val g = Graph(1 ~ 2) g find 1 // Option[g.NodeT] = Some(1) g find 3 // Option[g.NodeT] = None g get 1 // g.NodeT = 1 g get 3 // NoSuchElementException g find 1 ~ 2 // Option[g.EdgeT] = Some(1 ~ 2) g.nodes find (_ == 1) // Option[g.NodeT] = 1 g addAndGet 3 // g.NodeT = 3
As to
1 in constant time.
1 in constant time.
Call get with caution. It is fine especially in test code if you know that the node exists.
3 so NoSuchElementException
is thrown.
1 ~ 2.
Looking up edges works in the same manner as for nodes.
_ ==
1 in linear time.
3 to the node set and returns the new inner node.
You need to look up nodes and edges to invoke traversals starting at a specific element.
With respect to traversals we also call the starting inner node the "root node".
Look-ups are implemented in terms of hashCode and equals.
val unDiEdge = 3 ~ 4 // UnDiEdge[Int]
unDiEdge._1 * unDiEdge._2 // Int = 12
unDiEdge.ends.iterator.product // Int = 12
unDiEdge match {
case n ~ m => n * m // Int = 12
}
val diEdge = 1 ~> 2 // DiEdge[Int]
unDiEdge.arity == diEdge.arity // true
diEdge.source - diEdge.target // Int = -1
diEdge match {
case s ~> t => s - t // Int = -1
}
val hyperEdge = 1 ~~ 2 ~~ 11 ~~ 12 // HyperEdge[Int]
hyperEdge.ends.get(hyperEdge.arity - 1) // Int = 12
hyperEdge.ends.iterator.sum // Int = 26
hyperEdge match {
case ~~(Several.Seq(n1, n2, _*)) => n1 - n2 // Int = -1
}
val diHyperEdge = OneOrMore(1, 2) ~~> OneOrMore(5, 6) // DiHyperEdge[Int]
diHyperEdge.sources.iterator.sum // Int = 3
diHyperEdge.targets.iterator.sum // Int = 11
diHyperEdge match {
case OneOrMore.Seq(_, s2, _*) ~~> OneOrMore(t1, _) => s2 * t1 // Int = 10
}
As to
_1 and _2 provide access to the first and second node of an edge.
ends allows for iterating over all connected nodes.
source and target
that are synonyms for _1 and _2.
unDiEdge and diEdge have an arity of 2.
get allows for direct access by index.
Here we access the last node of hyperEdge.
Besides how to add infix edge extractors, please refer to any of the numerous examples in the test code base like
val g = Graph[Int, AnyEdge](0, 1 ~ 3, 3 ~> 2) def n(outer: Int): g.NodeT = g get outer n(0).diSuccessors // Set[g.NodeT] = Set() n(2).diSuccessors // Set[g.NodeT] = Set() n(3).diSuccessors // Set[g.NodeT] = Set(1, 2) n(3).diPredecessors // Set[g.NodeT] = Set(1) n(2).incoming // Set[g.EdgeT] = Set(3 ~> 2)
As to
n(0) is independent so the set of direct successor nodes is empty.
n(2) is reachable but has no direct successor so the set of its out-neighbors is empty, too.
n(3), 1 and 2 are reachable.
n(3) is node 1.
n(2) there is only one incoming edge namely 3 ~> 2.
All in all, given a specific node, the following methods are available to inspect incident edges and neighbors:
| Result Type | Method name | Synonym |
Set[NodeT] |
diSuccessors |
outNeighbors |
diPredecessors |
inNeighbors |
|
neighbors |
||
Set[EdgeT] |
outgoing |
|
outgoingTo |
||
incoming |
||
incomingFrom |
||
Option[EdgeT] |
findOutgoingTo |
|
findIncomingFrom |
For "inspecting" elements that need not be connected directly, see Traverse Your Graph.
val g = Graph[Int, AnyEdge](2 ~> 3, 3 ~ 1, 5)
g.nodes.filter(_.outer > 2) // Set[g.NodeT] = Set(3, 5)
g.nodes.filter(_.degree > 1) // Set[g.NodeT] = Set(3)
g.edges.filter(_.adjacents.isEmpty) // Set[g.EdgeT] = Set()
g.outerIterator
.filter {
case g.OuterNode(n) => n > 1
case g.OuterEdge(AnyEdge(n1, n2)) => n1 > 1 && n2 > 1
}
.map {
case g.OuterNode(n) => n
case g.OuterEdge(e) => e
}
.toList // List[Any] = List(2, 3, 5, 2 ~> 3)
g.iterator
.filter {
case g.InnerNode(innerN, _) => innerN.degree > 1
case g.InnerEdge(_, outerE) => outerE.isDirected
}
.mkString // String = 3, 2 ~> 3
As to
(n: NodeT) => n.outer > 2.
_.degree > 1 where degree is a method of inner
nodes.
outerIterator has the type Iterator[OuterElem].
iterator has the type Iterator[g.InnerElem].
InnerNode extracts both the inner and the corresponding outer node.
We need the inner node to be able to call degree.
InnerEdge extracts both the inner and the corresponding outer edge.
Here we just need the outer edge.
val g = Graph(1~2, 2~3, 2~4, 3~5, 4~5) val h = Graph(3 ~ 4, 3 ~ 5, 4 ~ 6, 5 ~ 6) g union h // Graph(1 ~ 2, 2 ~ 3, 2 ~ 4, 3 ~ 5, 4 ~ 5, 3 ~ 4, 4 ~ 6, 5 ~ 6) g diff h // Graph(1 ~ 2) g intersect h // Graph(4, 3 ~ 5) g &= h // Graph(4, 3 ~ 5), mutated instance
union, same as operator ++, diff, same as --, and
intersec, same as &, work in compliance with graph theory definitions.
Use any of the above operators followed by = to mutate your mutable graph.
Assume g being the mixed graph in chapter
Traverse Your Graph. Then
import scalax.collection.edge.Implicits._ val g = Graph[Int, AnyEdge](1 ~ 2, 2 ~ 3, 1 ~> 3, 1 ~ 5, 3 ~ 5, 3 ~ 4, 4 ~> 4, 4 ~> 5) g.order // Int = 5 g.size // Int = 8 g.elementCount // Int = 13 g.totalDegree // Int = 16 g.degreeSet // TreeSet(4, 3, 2) g.degreeNodeSeq(g.InDegree) // List((4, 3), (3, 5), (2, 1), (2, 2), (2, 4)) g.degreeNodesMap // Map(2->Set(2), 3->Set(5, 1), 4->Set(3, 4)) g.degreeNodesMap(degreeFilter = _ > 3) // Map(4 -> Set(3, 4))
As to
g.g.g.g.g with inner node references.g with nodes having the degree of key.3.Degree methods come with implicit parameters to choose in- or out-degrees and optionally filter them.
val g = Graph(1, 2 ~> 3) g.isConnected // false (g addOne 2 ~> 1).isConnected // true (g get 2).findConnected(_.outer == 3) // Some(3) g.isCyclic // false (g addOne 3 ~> 2).isCyclic // true g.isComplete // false (g ++ List(1 ~> 2, 1 ~> 3, 2 ~> 1, 3 ~> 1, 3 ~> 2)).isComplete // true g.isDirected // true g.isHyper // false g.isMulti // false