Skip to content

Commit 87deb3b

Browse files
committed
Reworked graph traversals to not be iterables and added all the unit tests; introduced new test around nullable destinations in traversals
1 parent 01a22d0 commit 87deb3b

File tree

7 files changed

+704
-128
lines changed

7 files changed

+704
-128
lines changed

graphs/src/main/kotlin/com/lillicoder/algorithms/graphs/AdjacencyListGraph.kt

Lines changed: 225 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class AdjacencyListGraph<T>(
2626
if (vertex == it.source) it.destination else it.source
2727
}?.toSet() ?: setOf()
2828

29-
override fun root() = vertices.keys.first()
29+
override fun root() = vertices.keys.firstOrNull()
3030

3131
override fun size() = vertices.keys.size
3232

@@ -48,4 +48,228 @@ class AdjacencyListGraph<T>(
4848
)
4949

5050
override fun vertex(id: Long) = vertices.keys.find { it.id == id }
51+
52+
/**
53+
* Builder for adjacency list graphs.
54+
*/
55+
class Builder<T> {
56+
/**
57+
* Builder for an edge in an adjacency list graph.
58+
* @param source Source [Vertex].
59+
* @param destination Destination vertex.
60+
*/
61+
class EdgeBuilder<T>(
62+
private val source: Vertex<T>,
63+
private val destination: Vertex<T>,
64+
) {
65+
private var isDirected: Boolean = false
66+
private var weight: Long = 0L
67+
68+
/**
69+
* Instantiates a new [Edge] from this builder.
70+
* @return Edge.
71+
*/
72+
fun build() = Edge(source, destination, isDirected, weight)
73+
74+
/**
75+
* Marks this builder's edge as directed.
76+
*/
77+
fun directed() {
78+
isDirected = true
79+
}
80+
81+
/**
82+
* Sets the weight of this builder's edge.
83+
* @param value Weight.
84+
*/
85+
fun weight(value: Long) {
86+
weight = value
87+
}
88+
}
89+
90+
private val edges = mutableSetOf<Edge<T>>()
91+
private val vertices = mutableMapOf<Vertex<T>, MutableSet<Edge<T>>>()
92+
93+
/**
94+
* Instantiates a new [AdjacencyListGraph] from this builder.
95+
* @return Adjacency list graph.
96+
*/
97+
fun build() = AdjacencyListGraph(vertices, edges)
98+
99+
/**
100+
* Type-safe builder for creating an [Edge] of a graph from an existing
101+
* pair of [Vertex].
102+
*
103+
* Example usage:
104+
* ```
105+
* vertex("a")
106+
* vertex("b")
107+
*
108+
* edge(0, 1) { // First vertex has ID of 0, second vertex has ID of 1
109+
* directed()
110+
* weight(10L)
111+
* }
112+
* ```
113+
* @param sourceId Source vertex ID.
114+
* @param destinationId Destination vertex ID.
115+
* @param init Function with receiver.
116+
* @return Edge.
117+
*/
118+
fun edge(
119+
sourceId: Long,
120+
destinationId: Long,
121+
init: (EdgeBuilder<T>.() -> Unit)? = null,
122+
) = edge(
123+
vertices.keys.find { it.id == sourceId }!!,
124+
vertices.keys.find { it.id == destinationId }!!,
125+
init,
126+
)
127+
128+
/**
129+
* Type-safe builder for creating an [Edge] of a graph from an existing
130+
* pair of [Vertex].
131+
*
132+
* Note that the first vertex matching each value will be used,
133+
* avoid this method when building a graph whose nodes have duplicate values.
134+
*
135+
* Example usage:
136+
* ```
137+
* vertex("a")
138+
* vertex("b")
139+
*
140+
* edge("a", "b") {
141+
* directed()
142+
* weight(10L)
143+
* }
144+
* ```
145+
* @param source Source vertex value.
146+
* @param destination Destination vertex value.
147+
* @param init Function with receiver.
148+
* @return Edge.
149+
*/
150+
fun edge(
151+
source: T,
152+
destination: T,
153+
init: (EdgeBuilder<T>.() -> Unit)? = null,
154+
) = edge(
155+
vertices.keys.find { it.value == source }!!,
156+
vertices.keys.find { it.value == destination }!!,
157+
init,
158+
)
159+
160+
/**
161+
* Type-safe builder for creating an [Edge] of a graph from a pair of existing [Vertex].
162+
* Used internally by more fluent builders (e.g. by-ID or by-value edge builders).
163+
* @param source Source vertex.
164+
* @param destination Destination vertex.
165+
* @param init Function with receiver.
166+
* @return Edge.
167+
*/
168+
private fun edge(
169+
source: Vertex<T>,
170+
destination: Vertex<T>,
171+
init: (EdgeBuilder<T>.() -> Unit)? = null,
172+
): Edge<T> {
173+
val builder = EdgeBuilder(source, destination)
174+
init?.invoke(builder)
175+
176+
val edge = builder.build()
177+
vertices[source]?.add(edge)
178+
vertices[destination]?.add(edge)
179+
edges.add(edge)
180+
181+
return edge
182+
}
183+
184+
/**
185+
* Type-safe builder for creating a [Vertex] of a graph.
186+
*
187+
* Example usage:
188+
* ```
189+
* vertex("a")
190+
* ```
191+
* @param value Value.
192+
* @param init Function with receiver.
193+
* @return Vertex.
194+
*/
195+
fun vertex(
196+
value: T,
197+
init: (Vertex<T>.() -> Unit)? = null,
198+
) = vertex(
199+
vertices.size.toLong(),
200+
value,
201+
init,
202+
)
203+
204+
/**
205+
* Type-safe builder for creating a [Vertex] of a graph from an existing vertex. ID and value
206+
* of the given vertex will be copied.
207+
*
208+
* Example usage:
209+
* ```
210+
* val other = someOtherVertex()
211+
* vertex(other)
212+
* ```
213+
* @param vertex Vertex to copy.
214+
* @param init Function with receiver.
215+
* @return Vertex.
216+
*/
217+
fun vertex(
218+
vertex: Vertex<T>,
219+
init: (Vertex<T>.() -> Unit)? = null,
220+
) = vertex(
221+
vertex.id,
222+
vertex.value,
223+
init,
224+
)
225+
226+
/**
227+
* Type-safe builder for creating a [Vertex] of a graph.
228+
*
229+
* Example usage:
230+
* ```
231+
* vertex(0, "a")
232+
* ```
233+
* @param id ID.
234+
* @param value Value.
235+
* @param init Function with receiver.
236+
* @return Vertex.
237+
*/
238+
private fun vertex(
239+
id: Long,
240+
value: T,
241+
init: (Vertex<T>.() -> Unit)? = null,
242+
): Vertex<T> {
243+
val vertex = Vertex(id, value)
244+
init?.invoke(vertex)
245+
vertices[vertex] = mutableSetOf()
246+
return vertex
247+
}
248+
}
249+
}
250+
251+
/**
252+
* Type-safe builder for creating an [AdjacencyListGraph].
253+
*
254+
* Example usage:
255+
* ```
256+
* graph {
257+
* vertex("a")
258+
* vertex("b")
259+
* vertex("c")
260+
*
261+
* edge(1, 2)
262+
* edge(2, 3) {
263+
* directed()
264+
* weight(10L)
265+
* }
266+
* }
267+
* ```
268+
* @param init Function with receiver.
269+
* @return Adjacency list graph.
270+
*/
271+
fun <T> graph(init: AdjacencyListGraph.Builder<T>.() -> Unit): AdjacencyListGraph<T> {
272+
val builder = AdjacencyListGraph.Builder<T>()
273+
builder.init()
274+
return builder.build()
51275
}

graphs/src/main/kotlin/com/lillicoder/algorithms/graphs/traversal/BreadthFirstIterator.kt renamed to graphs/src/main/kotlin/com/lillicoder/algorithms/graphs/traversal/BreadthFirstTraversal.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import com.lillicoder.algorithms.graphs.Vertex
2323
* Breadth-first [Traversal] of a given [Graph].
2424
* @param graph Graph to traverse.
2525
*/
26-
class BreadthFirstIterator<T>(private val graph: Graph<T>) : Traversal<T> {
26+
class BreadthFirstTraversal<T>(private val graph: Graph<T>) : Traversal<T> {
2727
private val queue = ArrayDeque<Vertex<T>>()
2828
private val visited = linkedMapOf<Vertex<T>, Vertex<T>?>()
2929

@@ -46,7 +46,12 @@ class BreadthFirstIterator<T>(private val graph: Graph<T>) : Traversal<T> {
4646
}
4747
}
4848

49-
return visited.keys.toList()
49+
// Return empty list if an explicit destination was requested,
50+
// otherwise give back the vertices in visited order
51+
return when (destination == null) {
52+
true -> visited.keys.toList()
53+
else -> emptyList()
54+
}
5055
}
5156

5257
/**

graphs/src/main/kotlin/com/lillicoder/algorithms/graphs/traversal/DepthFirstIterator.kt renamed to graphs/src/main/kotlin/com/lillicoder/algorithms/graphs/traversal/DepthFirstTraversal.kt

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import com.lillicoder.algorithms.graphs.Vertex
2323
* Depth-first [Traversal] of a given [Graph].
2424
* @param graph Graph to traverse.
2525
*/
26-
private class DepthFirstIterator<T>(private val graph: Graph<T>) : Traversal<T> {
26+
class DepthFirstTraversal<T>(private val graph: Graph<T>) : Traversal<T> {
2727
private val stack = ArrayDeque<Vertex<T>>()
2828
private val visited = linkedMapOf<Vertex<T>, Boolean>()
2929
private val parent = mutableMapOf<Vertex<T>, Vertex<T>?>()
@@ -50,7 +50,12 @@ private class DepthFirstIterator<T>(private val graph: Graph<T>) : Traversal<T>
5050
}
5151
}
5252

53-
return visited.keys.toList()
53+
// Return empty list if an explicit destination was requested,
54+
// otherwise give back the vertices in visited order
55+
return when (destination == null) {
56+
true -> visited.keys.toList()
57+
else -> emptyList()
58+
}
5459
}
5560

5661
/**

0 commit comments

Comments
 (0)