diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..a55e7a1
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/algorithm/BreadthFirstSearch.java b/src/main/java/algorithm/BreadthFirstSearch.java
new file mode 100644
index 0000000..51f2e09
--- /dev/null
+++ b/src/main/java/algorithm/BreadthFirstSearch.java
@@ -0,0 +1,38 @@
+package algorithm;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+
+import static java.util.function.Predicate.not;
+import static java.util.stream.Stream.iterate;
+
+public class BreadthFirstSearch implements SearchAlgorithm {
+ @Override
+ public List findPath(Graph graph, T source, T target) {
+ final var queue = new LinkedList();
+ final var visited = new HashSet();
+ final var previous = new HashMap();
+ queue.add(source);
+
+ while (!queue.isEmpty()) {
+ final var node = queue.removeFirst();
+ if (target.equals(node)) {
+ final var path = new LinkedList();
+ iterate(node, Objects::nonNull, previous::get).forEach(path::addFirst);
+ return path;
+ }
+ visited.add(node);
+ graph.nodes().get(node).keySet().stream()
+ .filter(not(visited::contains))
+ .forEach(it -> {
+ previous.computeIfAbsent(it, x -> node);
+ queue.addLast(it);
+ });
+ }
+ return new LinkedList<>();
+ }
+
+}
diff --git a/src/main/java/algorithm/Dijkstras.java b/src/main/java/algorithm/Dijkstras.java
index b7cd187..d91727a 100644
--- a/src/main/java/algorithm/Dijkstras.java
+++ b/src/main/java/algorithm/Dijkstras.java
@@ -1,9 +1,13 @@
package algorithm;
-import java.util.*;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
import java.util.stream.Stream;
-public class Dijkstras implements Algorithm {
+public class Dijkstras implements SearchAlgorithm {
@Override
public List findPath(Graph graph, T source, T target) {
@@ -12,14 +16,15 @@ public List findPath(Graph graph, T source, T target) {
final var distances = new HashMap();
final var previous = new HashMap();
queue.add(source);
+ distances.put(source, .0);
while (!queue.isEmpty()) {
- final var prev = queue.pollFirst();
+ final var prev = queue.removeFirst();
final var edges = graph.nodes().get(prev);
edges.forEach((node, time) -> {
- final var distance = distances.getOrDefault(prev, .0) + time.doubleValue();
+ final var distance = distances.get(prev) + time.doubleValue();
if (!visited.contains(node)) {
- queue.add(node);
+ queue.addLast(node);
visited.add(node);
}
if (distance < distances.getOrDefault(node, Double.MAX_VALUE)) {
diff --git a/src/main/java/algorithm/Graph.java b/src/main/java/algorithm/Graph.java
index cecdbe0..05e6e61 100644
--- a/src/main/java/algorithm/Graph.java
+++ b/src/main/java/algorithm/Graph.java
@@ -5,7 +5,7 @@
public record Graph(Map> nodes) {
- public double getDistance(List path) {
+ public double getDistance(final List path) {
double distance = 0;
for (int i = 1; i < path.size(); ++i) {
final var previous = nodes.get(path.get(i - 1));
diff --git a/src/main/java/algorithm/Algorithm.java b/src/main/java/algorithm/SearchAlgorithm.java
similarity index 63%
rename from src/main/java/algorithm/Algorithm.java
rename to src/main/java/algorithm/SearchAlgorithm.java
index 8f138e7..a8af1e4 100644
--- a/src/main/java/algorithm/Algorithm.java
+++ b/src/main/java/algorithm/SearchAlgorithm.java
@@ -2,6 +2,7 @@
import java.util.List;
-public interface Algorithm {
+@FunctionalInterface
+public interface SearchAlgorithm {
List findPath(Graph graph, T source, T target);
}
diff --git a/src/test/groovy/algorithm/BreadthFirstSearchSpec.groovy b/src/test/groovy/algorithm/BreadthFirstSearchSpec.groovy
new file mode 100644
index 0000000..0220364
--- /dev/null
+++ b/src/test/groovy/algorithm/BreadthFirstSearchSpec.groovy
@@ -0,0 +1,71 @@
+package algorithm
+
+import spock.lang.Specification
+import spock.lang.Subject
+import spock.lang.Unroll
+
+class BreadthFirstSearchSpec extends Specification {
+ @Subject
+ def algorithm = new BreadthFirstSearch()
+
+ @Unroll("from #source to #target the time is #time and the path is #shortest")
+ def 'should find a route for simple graph'() {
+ given:
+ def graph = new Graph([
+ A: [B: 7, C: 2],
+ B: [A: 3, C: 5],
+ C: [A: 1, B: 3]
+ ])
+
+ when:
+ def path = algorithm.findPath(graph, source, target)
+
+ then:
+ path == shortest
+
+ and:
+ graph.getDistance(path) == time as double
+
+ where:
+ source | target || time | shortest
+ 'A' | 'A' || 0 | ['A']
+ 'A' | 'B' || 7 | ['A', 'B']
+ 'B' | 'C' || 5 | ['B', 'C']
+ 'C' | 'B' || 3 | ['C', 'B']
+ }
+
+ @Unroll("from #source to #target the time is #time and the path is #shortest")
+ def 'should find a route for complex graph'() {
+ given:
+ def graph = new Graph([
+ A: [B: 1],
+ B: [A: 1, D: 1],
+ C: [A: 1],
+ D: [C: 1, E: 1],
+ E: [F: 1],
+ F: [D: 1, E: 1]])
+
+ when:
+ def path = algorithm.findPath(graph, source, target)
+
+ then:
+ path == shortest
+
+ and:
+ graph.getDistance(path) == time as double
+
+ where:
+ source | target || shortest
+ 'A' | 'A' || ['A']
+ 'B' | 'B' || ['B']
+ 'A' | 'B' || ['A', 'B']
+ 'B' | 'A' || ['B', 'A']
+ 'A' | 'C' || ['A', 'B', 'D', 'C']
+ 'C' | 'A' || ['C', 'A']
+ 'E' | 'B' || ['E', 'F', 'D', 'C', 'A', 'B']
+
+ and:
+ time = shortest.size() - 1
+ }
+
+}
diff --git a/src/test/groovy/algorithm/DijkstrasSpec.groovy b/src/test/groovy/algorithm/DijkstrasSpec.groovy
index b37be24..5ff7835 100644
--- a/src/test/groovy/algorithm/DijkstrasSpec.groovy
+++ b/src/test/groovy/algorithm/DijkstrasSpec.groovy
@@ -5,29 +5,72 @@ import spock.lang.Subject
import spock.lang.Unroll
class DijkstrasSpec extends Specification {
-
- static final SAMPLE_ONE = [
- A: [B: 7, C: 2],
- B: [A: 3, C: 5],
- C: [A: 1, B: 3]
- ] as Graph
-
@Subject
def algorithm = new Dijkstras()
- @Unroll("from #source to #target the time is #time and the path is #expected")
- def 'should find a route for sample one'() {
+ @Unroll("from #source to #target the time is #time and the path is #fastest")
+ def 'should find a route for a simple graph'() {
given:
- def graph = SAMPLE_ONE
+ def graph = new Graph([
+ A: [B: 7, C: 2],
+ B: [A: 3, C: 5],
+ C: [A: 1, B: 3]
+ ])
when:
def path = algorithm.findPath(graph, source, target)
then:
- path == expected
+ path == fastest
+
+ and:
+ graph.getDistance(path) == time as double
where:
- source | target || time | expected
- 'A' | 'B' || 7 | ['A', 'B']
+ source | target || time | fastest
+ 'A' | 'A' || 0 | ['A']
+ 'B' | 'B' || 0 | ['B']
+ 'C' | 'C' || 0 | ['C']
+ 'A' | 'B' || 5 | ['A', 'C', 'B']
}
+
+ @Unroll("from #source to #target the time is #time and the path is #fastest")
+ def 'should find a route for a complex graph'() {
+ given:
+ def graph = new Graph([
+ A: [B: 5, H: 2],
+ B: [A: 5, C: 7],
+ C: [B: 7, D: 3, G: 4],
+ D: [C: 20, E: 4],
+ E: [F: 5],
+ F: [G: 6],
+ G: [C: 4],
+ H: [G: 3]
+ ])
+
+ when:
+ def path = algorithm.findPath(graph, source, target)
+
+ then:
+ path == fastest
+
+ and:
+ graph.getDistance(path) == time as double
+
+ where:
+ source | target || time | fastest
+ 'A' | 'A' || 0 | ['A']
+ 'B' | 'B' || 0 | ['B']
+ 'A' | 'B' || 5 | ['A', 'B']
+ 'B' | 'A' || 5 | ['B', 'A']
+ 'A' | 'C' || 9 | ['A', 'H', 'G', 'C']
+ 'C' | 'A' || 12 | ['C', 'B', 'A']
+ 'A' | 'G' || 5 | ['A', 'H', 'G']
+ 'C' | 'D' || 3 | ['C', 'D']
+ 'D' | 'C' || 19 | ['D', 'E', 'F', 'G', 'C']
+ 'B' | 'D' || 10 | ['B', 'C', 'D']
+ 'D' | 'B' || 26 | ['D', 'E', 'F', 'G', 'C', 'B']
+ 'D' | 'H' || 33 | ['D', 'E', 'F', 'G', 'C', 'B', 'A', 'H']
+ }
+
}