-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathGraph.kt
145 lines (116 loc) · 4.23 KB
/
Graph.kt
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package algorithmsinanutshell
typealias Vertex = Graph.Vertex
typealias Edge = Graph.Edge
typealias State = Graph.State
enum class Relation(val symbol: String) {
UNWEIGHTED_DIRECTED("----->"),
UNWEIGHTED_UNDIRECTED("<----->"),
WEIGHTED_DIRECTED("--(%s)-->"),
WEIGHTED_UNDIRECTED("<--(%s)-->");
fun isDirected() = this == WEIGHTED_DIRECTED || this == UNWEIGHTED_DIRECTED
fun isWeighted() = this == WEIGHTED_DIRECTED || this == WEIGHTED_UNDIRECTED
fun createSymbol(weight: Int? = null): String {
return if (this.isWeighted()) {
String.format(this.symbol, weight.toString())
} else {
this.symbol
}
}
}
class Graph(val relation: Relation = Relation.UNWEIGHTED_DIRECTED) {
var nodes = mutableListOf<Vertex>()
private set
private val map = mutableMapOf<Int, Vertex>()
val vertexCount: Int
get() = nodes.size
fun add(value: Int): Vertex {
require(!map.containsKey(value)) {
"Vertex $value already present"
}
return Vertex(value).also {
nodes.add(it)
map[value] = it
}
}
fun setAllAsUnvisited() {
getAllNodes().forEach {
it.state = State.Undiscovered
}
}
fun getUnvisited() = map.values.filter { it.state == State.Undiscovered }
fun getAllNodes() = map.values
override fun toString(): String {
return "Graph(nodes=$nodes)"
}
inner class Vertex(val value: Int) {
var edges = mutableListOf<Edge>()
private set
var state = State.Undiscovered
override fun toString(): String {
return "Vertex(value=$value, edges=$edges, state=$state)"
}
override fun equals(other: Any?): Boolean {
// In Graph#add method, there is a check that ensures vertex value is unique
if (other is Vertex) {
return this.value == other.value
}
return super.equals(other)
}
override fun hashCode(): Int {
return value
}
fun connectWith(vertex: Vertex, weight: Int? = null, readd: Boolean = true) {
if (relation.isWeighted()) require(weight != null)
else require(weight == null)
edges.add(Edge(this, vertex, weight))
if (!relation.isDirected() && readd) {
vertex.connectWith(this, weight, false)
}
}
}
inner class Edge(val startVertex: Vertex, val endVertex: Vertex, val weight: Int?) {
override fun equals(other: Any?): Boolean {
// In case of un-directed relationship, a--b and b--a are same
if (!relation.isDirected() && other is Edge) {
return ((this.startVertex == other.startVertex && this.endVertex == other.endVertex)
.or(this.endVertex == other.startVertex && this.startVertex == other.endVertex))
&& this.weight == other.weight
}
return super.equals(other)
}
override fun toString(): String {
return "Edge(${startVertex.value} ${relation.createSymbol(weight)} ${endVertex.value})"
}
override fun hashCode(): Int {
// start vertex and end vertex are interchangeable in case of un-directed relationship
if (!relation.isDirected()) {
var result = startVertex.value
result += endVertex.value
result = 31 * result + (weight ?: 0)
return result
}
return super.hashCode()
}
}
enum class State {
Discovered, Undiscovered, Processing;
}
companion object {
fun createWeightedDirectedGraph() = Graph(Relation.WEIGHTED_DIRECTED)
fun createUnweightedUndirectedGraph() = Graph(Relation.UNWEIGHTED_UNDIRECTED)
}
}
fun Iterable<Vertex>.getVertexNames() = this.map { it.value }.joinToString(",")
fun main() {
val graph = Graph(Relation.WEIGHTED_DIRECTED)
val v1 = graph.add(1)
val v2 = graph.add(2)
val v3 = graph.add(3)
val v4 = graph.add(4)
val v5 = graph.add(5)
v1.connectWith(v2, 1)
v2.connectWith(v3, 4)
v1.connectWith(v4, 5)
v4.connectWith(v5, 6)
println(graph)
}