-
Notifications
You must be signed in to change notification settings - Fork 316
/
InductiveGraph.scala
120 lines (101 loc) · 3.8 KB
/
InductiveGraph.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
package graph
/**
* Inductive graph implementation based on Martin Erwig's paper:
* http://web.engr.oregonstate.edu/~erwig/papers/InductiveGraphs_JFP01.pdf
*
* Usage example
*
* val sample =
* Context(Adj(List(("left", Node(2)), ("up", Node(3)))),
* Node(1), "a", Adj(List(("right", Node(2))))) &:
* Context(Adj(List()), Node(2), "b", Adj(List(("down", Node(3))))) &:
* Context(Adj(List()), Node(3), "c", Adj(List())) &:
* Empty
*
* val updatedSample = sample.add("d")
*
* val connectedSample = sample.connect(Node(3), Node(4), "new edge")
*/
case class Node(id: Int)
case class Adj[+B](links: List[(B, Node)])
case class Context[+A, +B](in: Adj[B], node: Node, content: A, out: Adj[B])
sealed abstract class Graph[+A, +B] {
def isEmpty: Boolean
def &:[C >: A, D >: B](c: Context[C, D]): Graph[C, D] = new &:(c, this)
def :::[C >: A, D >: B](g: Graph[C, D]): Graph[C, D] = g match {
case Empty => this
case head &: rest => head &: rest ::: this
}
def size: Int = this match {
case _ &: rest => 1 + rest.size
case Empty => 0
}
def matchNode(n: Node): Option[Context[A, B]] = this match {
case (ctx @ Context(_, node, _, _)) &: _ if n == node => Some(ctx)
case _ &: rest => rest.matchNode(n)
case Empty => None
}
def map[C >: A, D >: B](f: Context[C, D] => Context[C, D]): Graph[C, D] = {
def loop(gg: Graph[C, D], acc: Graph[C, D]): Graph[C, D] = gg match {
case ctx &: rest => loop(rest, f(ctx) &: acc)
case Empty => acc
}
loop(this, Empty)
}
def ufold[T](init: T, f: (Context[A, B], T) => T): T = {
def loop(gg: Graph[A, B], acc: T): T = gg match {
case ctx &: rest => loop(rest, f(ctx, acc))
case Empty => acc
}
loop(this, init)
}
def nodes[C >: A, D >: B]: List[Node] =
ufold(List(), (c: Context[C, D], acc: List[Node]) => c.node :: acc)
def successors(node: Node): List[Node] = {
def succin(c: Context[A, B], acc: List[Node]): List[Node] =
c.in.links.foldLeft(List[Node]())((a, i) =>
if (i._2 == node) c.node :: a
else a
) ++ acc
val outNodes = this.matchNode(node) match {
case Some(ctx) => ctx.out.links.map(_._2)
case None => List[Node]()
}
this.ufold(List[Node](), succin) ++ outNodes
}
def add[C >: A, D >: B](c: C): Graph[C,D] =
Context(in = Adj[D](List()), out = Adj[D](List()),
node = Node(size+1), content = c ) &: this
/*
* Only works if at least one of the nodes exists. Will create edge to non-existing
* node in case one of the two nodes does not exist.
*/
def connect[C >: A, D >: B](from: Node, to: Node, edgeLabel: D): Graph[C, D] = {
def connector(graph: Graph[C, D], acc: Graph[C, D]): Graph[C, D] = graph match {
case Context(in, n, con, out) &: rest if n == from =>
// create outgoing link at from
val newContext = Context(in, n, con, Adj((edgeLabel, to) :: out.links))
newContext &: acc ::: rest
case Context(in, n, con, out) &: rest if n == to =>
// create incoming link at to
val newContext = Context(Adj((edgeLabel, from) :: in.links), n, con, out)
newContext &: acc ::: rest
case head &: rest => connector(rest, head &: acc)
case Empty => acc
}
connector(this, Empty)
}
}
case class &:[A, B](c: Context[A, B], g: Graph[A, B]) extends Graph[A, B] {
override def isEmpty: Boolean = false
override def toString: String = "Context(" + c.in + ", " + c.node +
", " + c.content + ", " + c.out + ") & \n" + g.toString
}
case object Empty extends Graph[Nothing, Nothing] {
override def isEmpty: Boolean = true
}
object Graph {
def empty[A, B]: Graph[A, B] = Empty
def apply[A, B](n: A, t: Graph[A, B] = Empty): Graph[A, B] =
new &:(Context(Adj[B](Nil), Node(1), n, Adj[B](Nil)), t)
}