From 1f87c3f4454fac48903fa7cc81708c2a1f10e893 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jegors=20=C4=8Cemisovs?= Date: Sun, 26 Dec 2021 16:55:41 +0200 Subject: [PATCH 1/3] Add BreadthFirstSearch --- .../java/algorithm/BreadthFirstSearch.java | 36 +++++++++++++++++++ src/main/java/algorithm/Dijkstras.java | 1 + .../algorithm/BreadthFirstSearchSpec.groovy | 36 +++++++++++++++++++ 3 files changed, 73 insertions(+) create mode 100644 src/main/java/algorithm/BreadthFirstSearch.java create mode 100644 src/test/groovy/algorithm/BreadthFirstSearchSpec.groovy diff --git a/src/main/java/algorithm/BreadthFirstSearch.java b/src/main/java/algorithm/BreadthFirstSearch.java new file mode 100644 index 0000000..ec862c4 --- /dev/null +++ b/src/main/java/algorithm/BreadthFirstSearch.java @@ -0,0 +1,36 @@ +package algorithm; + +import java.util.*; + +import static java.util.function.Predicate.not; +import static java.util.stream.Stream.iterate; + +public class BreadthFirstSearch implements Algorithm { + @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.pollFirst(); + 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.put(it, node); + queue.add(it); + }); + } + return new LinkedList<>(); + } + +} diff --git a/src/main/java/algorithm/Dijkstras.java b/src/main/java/algorithm/Dijkstras.java index b7cd187..688034c 100644 --- a/src/main/java/algorithm/Dijkstras.java +++ b/src/main/java/algorithm/Dijkstras.java @@ -12,6 +12,7 @@ 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(); diff --git a/src/test/groovy/algorithm/BreadthFirstSearchSpec.groovy b/src/test/groovy/algorithm/BreadthFirstSearchSpec.groovy new file mode 100644 index 0000000..7a53cad --- /dev/null +++ b/src/test/groovy/algorithm/BreadthFirstSearchSpec.groovy @@ -0,0 +1,36 @@ +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 #expected") + def 'should find a route for sample one'() { + 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 == expected + + and: + graph.getDistance(path) == time as double + + where: + source | target || time | expected + 'A' | 'A' || 0 | ['A'] + 'A' | 'B' || 7 | ['A', 'B'] + 'B' | 'C' || 5 | ['B', 'C'] + } + +} From eb57ac69335ea663bcf139bb04ca0e48d428a743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jegors=20=C4=8Cemisovs?= Date: Sun, 26 Dec 2021 18:05:21 +0200 Subject: [PATCH 2/3] Add SearchAlgorithm --- .../java/algorithm/BreadthFirstSearch.java | 16 +++-- src/main/java/algorithm/Dijkstras.java | 14 ++-- src/main/java/algorithm/Graph.java | 2 +- .../{Algorithm.java => SearchAlgorithm.java} | 3 +- .../algorithm/BreadthFirstSearchSpec.groovy | 43 ++++++++++-- .../groovy/algorithm/DijkstrasSpec.groovy | 69 +++++++++++++++---- 6 files changed, 116 insertions(+), 31 deletions(-) rename src/main/java/algorithm/{Algorithm.java => SearchAlgorithm.java} (63%) diff --git a/src/main/java/algorithm/BreadthFirstSearch.java b/src/main/java/algorithm/BreadthFirstSearch.java index ec862c4..51f2e09 100644 --- a/src/main/java/algorithm/BreadthFirstSearch.java +++ b/src/main/java/algorithm/BreadthFirstSearch.java @@ -1,22 +1,24 @@ 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 static java.util.function.Predicate.not; import static java.util.stream.Stream.iterate; -public class BreadthFirstSearch implements Algorithm { +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.pollFirst(); + final var node = queue.removeFirst(); if (target.equals(node)) { final var path = new LinkedList(); iterate(node, Objects::nonNull, previous::get).forEach(path::addFirst); @@ -26,8 +28,8 @@ public List findPath(Graph graph, T source, T target) { graph.nodes().get(node).keySet().stream() .filter(not(visited::contains)) .forEach(it -> { - previous.put(it, node); - queue.add(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 688034c..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) { @@ -15,12 +19,12 @@ public List findPath(Graph graph, T source, T target) { 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 index 7a53cad..0220364 100644 --- a/src/test/groovy/algorithm/BreadthFirstSearchSpec.groovy +++ b/src/test/groovy/algorithm/BreadthFirstSearchSpec.groovy @@ -8,8 +8,8 @@ class BreadthFirstSearchSpec extends Specification { @Subject def algorithm = new BreadthFirstSearch() - @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 #shortest") + def 'should find a route for simple graph'() { given: def graph = new Graph([ A: [B: 7, C: 2], @@ -21,16 +21,51 @@ class BreadthFirstSearchSpec extends Specification { def path = algorithm.findPath(graph, source, target) then: - path == expected + path == shortest and: graph.getDistance(path) == time as double where: - source | target || time | expected + 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'] + } + } From 0cab4bfe031635de939b5939e7a09d4c8d5b6e3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jegors=20=C4=8Cemisovs?= Date: Sun, 26 Dec 2021 18:07:31 +0200 Subject: [PATCH 3/3] Add codeStyleConfig.xml --- .idea/codeStyles/codeStyleConfig.xml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .idea/codeStyles/codeStyleConfig.xml 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