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