Skip to content

Commit eb57ac6

Browse files
committed
Add SearchAlgorithm
1 parent 1f87c3f commit eb57ac6

File tree

6 files changed

+116
-31
lines changed

6 files changed

+116
-31
lines changed

src/main/java/algorithm/BreadthFirstSearch.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
11
package algorithm;
22

3-
import java.util.*;
3+
import java.util.HashMap;
4+
import java.util.HashSet;
5+
import java.util.LinkedList;
6+
import java.util.List;
7+
import java.util.Objects;
48

59
import static java.util.function.Predicate.not;
610
import static java.util.stream.Stream.iterate;
711

8-
public class BreadthFirstSearch<T> implements Algorithm<T> {
12+
public class BreadthFirstSearch<T> implements SearchAlgorithm<T> {
913
@Override
1014
public List<T> findPath(Graph<T> graph, T source, T target) {
1115
final var queue = new LinkedList<T>();
12-
1316
final var visited = new HashSet<T>();
1417
final var previous = new HashMap<T, T>();
15-
1618
queue.add(source);
1719

1820
while (!queue.isEmpty()) {
19-
final var node = queue.pollFirst();
21+
final var node = queue.removeFirst();
2022
if (target.equals(node)) {
2123
final var path = new LinkedList<T>();
2224
iterate(node, Objects::nonNull, previous::get).forEach(path::addFirst);
@@ -26,8 +28,8 @@ public List<T> findPath(Graph<T> graph, T source, T target) {
2628
graph.nodes().get(node).keySet().stream()
2729
.filter(not(visited::contains))
2830
.forEach(it -> {
29-
previous.put(it, node);
30-
queue.add(it);
31+
previous.computeIfAbsent(it, x -> node);
32+
queue.addLast(it);
3133
});
3234
}
3335
return new LinkedList<>();

src/main/java/algorithm/Dijkstras.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
package algorithm;
22

3-
import java.util.*;
3+
import java.util.HashMap;
4+
import java.util.HashSet;
5+
import java.util.LinkedList;
6+
import java.util.List;
7+
import java.util.Objects;
48
import java.util.stream.Stream;
59

6-
public class Dijkstras<T> implements Algorithm<T> {
10+
public class Dijkstras<T> implements SearchAlgorithm<T> {
711

812
@Override
913
public List<T> findPath(Graph<T> graph, T source, T target) {
@@ -15,12 +19,12 @@ public List<T> findPath(Graph<T> graph, T source, T target) {
1519
distances.put(source, .0);
1620

1721
while (!queue.isEmpty()) {
18-
final var prev = queue.pollFirst();
22+
final var prev = queue.removeFirst();
1923
final var edges = graph.nodes().get(prev);
2024
edges.forEach((node, time) -> {
21-
final var distance = distances.getOrDefault(prev, .0) + time.doubleValue();
25+
final var distance = distances.get(prev) + time.doubleValue();
2226
if (!visited.contains(node)) {
23-
queue.add(node);
27+
queue.addLast(node);
2428
visited.add(node);
2529
}
2630
if (distance < distances.getOrDefault(node, Double.MAX_VALUE)) {

src/main/java/algorithm/Graph.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
public record Graph<T>(Map<T, Map<T, Number>> nodes) {
77

8-
public double getDistance(List<T> path) {
8+
public double getDistance(final List<T> path) {
99
double distance = 0;
1010
for (int i = 1; i < path.size(); ++i) {
1111
final var previous = nodes.get(path.get(i - 1));

src/main/java/algorithm/Algorithm.java renamed to src/main/java/algorithm/SearchAlgorithm.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.util.List;
44

5-
public interface Algorithm<T> {
5+
@FunctionalInterface
6+
public interface SearchAlgorithm<T> {
67
List<T> findPath(Graph<T> graph, T source, T target);
78
}

src/test/groovy/algorithm/BreadthFirstSearchSpec.groovy

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ class BreadthFirstSearchSpec extends Specification {
88
@Subject
99
def algorithm = new BreadthFirstSearch()
1010

11-
@Unroll("from #source to #target the time is #time and the path is #expected")
12-
def 'should find a route for sample one'() {
11+
@Unroll("from #source to #target the time is #time and the path is #shortest")
12+
def 'should find a route for simple graph'() {
1313
given:
1414
def graph = new Graph([
1515
A: [B: 7, C: 2],
@@ -21,16 +21,51 @@ class BreadthFirstSearchSpec extends Specification {
2121
def path = algorithm.findPath(graph, source, target)
2222

2323
then:
24-
path == expected
24+
path == shortest
2525

2626
and:
2727
graph.getDistance(path) == time as double
2828

2929
where:
30-
source | target || time | expected
30+
source | target || time | shortest
3131
'A' | 'A' || 0 | ['A']
3232
'A' | 'B' || 7 | ['A', 'B']
3333
'B' | 'C' || 5 | ['B', 'C']
34+
'C' | 'B' || 3 | ['C', 'B']
35+
}
36+
37+
@Unroll("from #source to #target the time is #time and the path is #shortest")
38+
def 'should find a route for complex graph'() {
39+
given:
40+
def graph = new Graph([
41+
A: [B: 1],
42+
B: [A: 1, D: 1],
43+
C: [A: 1],
44+
D: [C: 1, E: 1],
45+
E: [F: 1],
46+
F: [D: 1, E: 1]])
47+
48+
when:
49+
def path = algorithm.findPath(graph, source, target)
50+
51+
then:
52+
path == shortest
53+
54+
and:
55+
graph.getDistance(path) == time as double
56+
57+
where:
58+
source | target || shortest
59+
'A' | 'A' || ['A']
60+
'B' | 'B' || ['B']
61+
'A' | 'B' || ['A', 'B']
62+
'B' | 'A' || ['B', 'A']
63+
'A' | 'C' || ['A', 'B', 'D', 'C']
64+
'C' | 'A' || ['C', 'A']
65+
'E' | 'B' || ['E', 'F', 'D', 'C', 'A', 'B']
66+
67+
and:
68+
time = shortest.size() - 1
3469
}
3570

3671
}

src/test/groovy/algorithm/DijkstrasSpec.groovy

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,72 @@ import spock.lang.Subject
55
import spock.lang.Unroll
66

77
class DijkstrasSpec extends Specification {
8-
9-
static final SAMPLE_ONE = [
10-
A: [B: 7, C: 2],
11-
B: [A: 3, C: 5],
12-
C: [A: 1, B: 3]
13-
] as Graph<String>
14-
158
@Subject
169
def algorithm = new Dijkstras<String>()
1710

18-
@Unroll("from #source to #target the time is #time and the path is #expected")
19-
def 'should find a route for sample one'() {
11+
@Unroll("from #source to #target the time is #time and the path is #fastest")
12+
def 'should find a route for a simple graph'() {
2013
given:
21-
def graph = SAMPLE_ONE
14+
def graph = new Graph([
15+
A: [B: 7, C: 2],
16+
B: [A: 3, C: 5],
17+
C: [A: 1, B: 3]
18+
])
2219

2320
when:
2421
def path = algorithm.findPath(graph, source, target)
2522

2623
then:
27-
path == expected
24+
path == fastest
25+
26+
and:
27+
graph.getDistance(path) == time as double
2828

2929
where:
30-
source | target || time | expected
31-
'A' | 'B' || 7 | ['A', 'B']
30+
source | target || time | fastest
31+
'A' | 'A' || 0 | ['A']
32+
'B' | 'B' || 0 | ['B']
33+
'C' | 'C' || 0 | ['C']
34+
'A' | 'B' || 5 | ['A', 'C', 'B']
3235
}
36+
37+
@Unroll("from #source to #target the time is #time and the path is #fastest")
38+
def 'should find a route for a complex graph'() {
39+
given:
40+
def graph = new Graph([
41+
A: [B: 5, H: 2],
42+
B: [A: 5, C: 7],
43+
C: [B: 7, D: 3, G: 4],
44+
D: [C: 20, E: 4],
45+
E: [F: 5],
46+
F: [G: 6],
47+
G: [C: 4],
48+
H: [G: 3]
49+
])
50+
51+
when:
52+
def path = algorithm.findPath(graph, source, target)
53+
54+
then:
55+
path == fastest
56+
57+
and:
58+
graph.getDistance(path) == time as double
59+
60+
where:
61+
source | target || time | fastest
62+
'A' | 'A' || 0 | ['A']
63+
'B' | 'B' || 0 | ['B']
64+
'A' | 'B' || 5 | ['A', 'B']
65+
'B' | 'A' || 5 | ['B', 'A']
66+
'A' | 'C' || 9 | ['A', 'H', 'G', 'C']
67+
'C' | 'A' || 12 | ['C', 'B', 'A']
68+
'A' | 'G' || 5 | ['A', 'H', 'G']
69+
'C' | 'D' || 3 | ['C', 'D']
70+
'D' | 'C' || 19 | ['D', 'E', 'F', 'G', 'C']
71+
'B' | 'D' || 10 | ['B', 'C', 'D']
72+
'D' | 'B' || 26 | ['D', 'E', 'F', 'G', 'C', 'B']
73+
'D' | 'H' || 33 | ['D', 'E', 'F', 'G', 'C', 'B', 'A', 'H']
74+
}
75+
3376
}

0 commit comments

Comments
 (0)