You can start to utilize Graph
out of the box but often you want
to achieve a certain degree of customization. Basically, the following kinds
of customization are eligible:
In the default implementation, adjacency lists are represented by
ArraySet
s, a specific collection type included in Graph-core.
ArraySet
s bank on JVM native arrays to provide higher performance.
Mostly you can go with the default ArraySet
settings which were chosen by careful measurements. In production
however, it may be desirable to tune them. Based on the node density
and density distribution of your graphs you can determine up to which
number of neighbors the underlying ArraySet
s
should use arrays.
Once this limit is reached, ArraySet
will transparently
switch from the array to hash set representation.
Let g
be your Graph
instance. Then
g.config
returns the immutable configuration for g
.
To override these settings, define your own implicit value prior to
Graph
creation:
import scalax.collection.config.CoreConfig import scalax.collection.mutable.ArraySet.Hints implicit val myConfig = CoreConfig(orderHint = 5000, Hints(64, 0, 64, 75))
The first argument will be used for preallocations depending on the
expected order of the graph. The second argument ensures that both
the initial array capacity and the hash set limit are set to 64
.
These values would be preferable for Graph
s with an average
node degree of roughly 30 to 55 for the majority of nodes.
For more details please refer to the Scaladoc of ArraySet.Hints
.
You may want to add new methods to the library implementation of
Graph
or its inner traits InnerNodeLike
,
NodeSet
, InnerEdgeLike
or EdgeSet
.
Adding new methods to Graph may be achieved by the standard enrich
my library pattern using implicit
functions.
To enrich Graph
with your own methods simply use the
enrich-me pattern like:
implicit class ExtGraph[N, E[X] <: EdgeLikeIn[X]](g: Graph[N,E]) { def foo = "bar" } Graph(1~2).foo
To enrich the inner type NodeT
is more subtle:
implicit class ExtGraphNode[N, E[X] <: EdgeLikeIn[X]](node_ : Graph[N,E]#NodeT) { type NodeT = graph.NodeT val graph = node_.containingGraph val node = node_.asInstanceOf[NodeT] def foo = this.toString + "bar" } Graph(1~2).nodes.headOption map (_.foo)
For more sophisticated enrichment alternatives see TExtByImplicit.scala.
You may decide to bypass the predefined edge classes and design your
own edge type. Recapping the Flight
label example,
it is also possible to define a custom edge type Flight
.
Armed with such a custom edge type you can create Graph
instances of the type Graph[Airport, Flight]
.
For the sake of simplicity we design the Flight
edge to have
the single attribute flightNo
. Given the node class
case class Airport(val code: String) { override def toString = code // without Airport-prefix } val (ham, ny) = (Airport("HAM"), Airport("JFK")) // two nodes
assume we want to be able to write
val flight = ham ~> ny ## "007" // flightNo 007 - doesn't work yet val g = Graph(flight) // Graph[Airport, Flight] - doesn't work yet
Here is how to achieve the above requirements:
case class Flight[+N](fromAirport: N, toAirport: N, flightNo: String) extends DiEdge[N](NodeProduct(fromAirport, toAirport)) with ExtendedKey[N] with EdgeCopy[Flight] with OuterEdge[N,Flight] { private def this(nodes: Product, flightNo: String) { this(nodes.productElement(0).asInstanceOf[N], nodes.productElement(1).asInstanceOf[N], flightNo) } def keyAttributes = Seq(flightNo) override def copy[NN](newNodes: Product) = new Flight[NN](newNodes, flightNo) override protected def attributesToString = s" ($flightNo)" } object Flight { implicit final class ImplicitEdge[A <: Airport](val e: DiEdge[A]) extends AnyVal { def ## (flightNo: String) = new Flight[A](e.source, e.target, flightNo) } }
As to
DiEdge
should be the base of any directed custom
edge. Airport
is the node type.ExtendedKey
must be mixed in. An attribute is a key
if it must be considered by equals
. flightNo
is such a key attribute because there may exist several flights from
and to the same airport so we distinguished them by flightNo
.
EdgeCopy
.
OuterEdge
.
Seq
.
copy
will be called by Graph
transparently to create an inner edge. Thus copy
plays
the role of an inner edge factory. It must return an instance of the
edge class.
Flight
edge factory shortcut
##
that propagates a directed edge to Flight
.
Note that the supplied tests contain a more complete implementation of
the flight example - see Flight.scala
, TFlight.scala
and FlightRouteMap.jpg
in the repository.
In general, when deciding on how to define your custom edge, the following steps apply:
ExtendedKey
and enter all key
attributes into the list returned by def keyAttributes
which you must override.EdgeCopy
must always be mixed in to override its
abstract method copy
.LoopFreeEdge
.
weight
is your single custom attribute go with either
WUnDiEdge
or WDiEdge
. If you have more custom attributes
you may select one of the predefined WL*
edges but, for better type safety,
you are recommended to implement your own custom edge.
validate
that is called at edge instantiation time. For instance, the
predefined validate
requires that no edge end equals to null
.
toString
. Notice that EdgeLike
comes with several protected methods for prefixes, braces etc.such as
attributesToString
which just need to be overridden. Thus they remove
the burden of programming toString
from the bottom up.
implicit def
to
make edge creation more readable.
~
or ~>
.
In case of mixed graphs, prefer an ADT of custom edge classes.
See also:
object scalax.collection.GraphEdge._
object scalax.collection.GraphPredef._
Please refer to the Constrained User Guide.
You might want to modify methods of the library implementation of Graph
or its inner traits InnerNodeLike
, NodeSet
,
InnerEdgeLike
or EdgeSet
to alter their run-time behavior.
However, be careful not to alter method semantics but add your own methods whenever
applicable.
Please refer to ExtNode.scala and TExtNode.scala respectively.