From 74afa68eaf98ae7390e5cf371bf060b6e5e99154 Mon Sep 17 00:00:00 2001 From: Jegors Cemisovs Date: Tue, 4 Jan 2022 20:54:44 +0200 Subject: [PATCH 1/6] Add subproject --- .gitignore | 3 +- .idea/gradle.xml | 1 + algorithm/build.gradle | 44 ++++++++++++ .../algorithm/graph/BreadthFirstSearch.java | 48 +++++++++++++ .../algorithm/graph/DijkstrasAlgorithm.java | 48 +++++++++++++ .../java/lv/id/jc/algorithm/graph/Graph.java | 71 +++++++++++++++++++ .../jc/algorithm/graph/SearchAlgorithm.java | 23 ++++++ .../id/jc/algorithm/graph/package-info.java | 4 ++ algorithm/src/main/java/module-info.java | 8 +++ settings.gradle | 4 +- 10 files changed, 252 insertions(+), 2 deletions(-) create mode 100644 algorithm/build.gradle create mode 100644 algorithm/src/main/java/lv/id/jc/algorithm/graph/BreadthFirstSearch.java create mode 100644 algorithm/src/main/java/lv/id/jc/algorithm/graph/DijkstrasAlgorithm.java create mode 100644 algorithm/src/main/java/lv/id/jc/algorithm/graph/Graph.java create mode 100644 algorithm/src/main/java/lv/id/jc/algorithm/graph/SearchAlgorithm.java create mode 100644 algorithm/src/main/java/lv/id/jc/algorithm/graph/package-info.java create mode 100644 algorithm/src/main/java/module-info.java diff --git a/.gitignore b/.gitignore index f6119d5..d391d30 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,5 @@ out/ ############################## ## OS X ############################## -.DS_Store \ No newline at end of file +.DS_Store +/algorithm/gradle.properties diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 1b3f3e9..6f52909 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -11,6 +11,7 @@ diff --git a/algorithm/build.gradle b/algorithm/build.gradle new file mode 100644 index 0000000..8c6049f --- /dev/null +++ b/algorithm/build.gradle @@ -0,0 +1,44 @@ +plugins { + id 'java-library' + id 'maven-publish' +} +sourceCompatibility = JavaVersion.VERSION_17 + +group 'lv.id.jc' +version '1.1' + +repositories { + mavenCentral() +} + +// Configures the publishing +publishing { + repositories { + // The target repository + maven { + // Choose whatever name you want + name = "algorithms" + // The url of the repository, where the artifacts will be published + url = "https://maven.pkg.github.com/rabestro/algorithms" + credentials { + // The credentials (described in the next section) + username = project.findProperty("gpr.user") + password = project.findProperty("gpr.key") + } + } + } + publications { + gpr(MavenPublication) { + from(components.java) + // Fixes the error with dynamic versions when using Spring Boot + versionMapping { + usage('java-api') { + fromResolutionOf('runtimeClasspath') + } + usage('java-runtime') { + fromResolutionResult() + } + } + } + } +} \ No newline at end of file diff --git a/algorithm/src/main/java/lv/id/jc/algorithm/graph/BreadthFirstSearch.java b/algorithm/src/main/java/lv/id/jc/algorithm/graph/BreadthFirstSearch.java new file mode 100644 index 0000000..6c6cfa2 --- /dev/null +++ b/algorithm/src/main/java/lv/id/jc/algorithm/graph/BreadthFirstSearch.java @@ -0,0 +1,48 @@ +package lv.id.jc.algorithm.graph; + +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; + +/** + * Algorithm for finding the shortest paths between nodes in a graph. + * + * The algorithm doesn't take into account the distance between nodes. + * + * @author Jegors Čemisovs + * @param the type of vertex + * @since 1.0 + */ +public class BreadthFirstSearch implements SearchAlgorithm { + + @Override + public List findPath(Graph graph, T source, T target) { + var queue = new LinkedList(); + var visited = new HashSet(); + var previous = new HashMap(); + queue.add(source); + + while (!queue.isEmpty()) { + var node = queue.removeFirst(); + if (target.equals(node)) { + var path = new LinkedList(); + iterate(node, Objects::nonNull, previous::get).forEach(path::addFirst); + return path; + } + visited.add(node); + graph.edges(node).keySet().stream() + .filter(not(visited::contains)) + .forEach(it -> { + previous.computeIfAbsent(it, x -> node); + queue.addLast(it); + }); + } + return List.of(); + } + +} diff --git a/algorithm/src/main/java/lv/id/jc/algorithm/graph/DijkstrasAlgorithm.java b/algorithm/src/main/java/lv/id/jc/algorithm/graph/DijkstrasAlgorithm.java new file mode 100644 index 0000000..c6769c7 --- /dev/null +++ b/algorithm/src/main/java/lv/id/jc/algorithm/graph/DijkstrasAlgorithm.java @@ -0,0 +1,48 @@ +package lv.id.jc.algorithm.graph; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; + +import static java.util.stream.Stream.iterate; + +/** + * Algorithm for finding the fastest paths between nodes in a graph. + *

+ * The algorithm uses information about edge's distance to find the fastest path. + * + * @author Jegors Čemisovs + * @param the type of vertex + * @since 1.0 + */ +public class DijkstrasAlgorithm implements SearchAlgorithm { + + @Override + public List findPath(Graph graph, T source, T target) { + var queue = new LinkedList(); + var distances = new HashMap(); + var previous = new HashMap(); + queue.add(source); + distances.put(source, .0); + + while (!queue.isEmpty()) { + var prev = queue.removeFirst(); + graph.edges(prev).forEach((node, time) -> { + var distance = distances.get(prev) + time.doubleValue(); + if (distance < distances.getOrDefault(node, Double.MAX_VALUE)) { + previous.put(node, prev); + distances.put(node, distance); + queue.addLast(node); + } + }); + } + if (previous.containsKey(target) || source.equals(target)) { + var path = new LinkedList(); + iterate(target, Objects::nonNull, previous::get).forEach(path::addFirst); + return path; + } + return List.of(); + } + +} diff --git a/algorithm/src/main/java/lv/id/jc/algorithm/graph/Graph.java b/algorithm/src/main/java/lv/id/jc/algorithm/graph/Graph.java new file mode 100644 index 0000000..6ed2e9c --- /dev/null +++ b/algorithm/src/main/java/lv/id/jc/algorithm/graph/Graph.java @@ -0,0 +1,71 @@ +package lv.id.jc.algorithm.graph; + +import java.util.List; +import java.util.Map; +import java.util.stream.IntStream; + +/** + * An interface for weighted directed graph (network) + * + * @param the type of vertex in this graph + * @author Jegors Čemisovs + * @since 1.1 + */ +@FunctionalInterface +public interface Graph { + /** + * The schema of this graph. + * + * In a graph schema, each vertex is assigned an edge map. + * If the vertex has no edges, then it should be assigned an empty map. + * + * @return the graph scheme + */ + Map> schema(); + + /** + * Returns the edges of the given vertex, + * or {@code null} if this graph contains no given vertex. + * + *

A return value of {@code null} does not necessarily + * indicate that the specified vertex is not present in the graph; + * it's also possible that in the graph schema, {@code null} was specified + * for the edges of this vertex instead of an empty map. + * + * @param vertex vertex + * @return all links for the given vertex + * or null if no such vertex in the graph + */ + default Map edges(T vertex) { + return schema().get(vertex); + } + + /** + * Calculate the distance for the given path + * + * @param path the list of vertices representing the path + * @return distance for the given path as double + * @throws NullPointerException if {@code path} is incorrect and contains more than one vertex + */ + default double getDistance(List path) { + return IntStream + .range(1, path.size()) + .mapToObj(i -> edges(path.get(i - 1)).get(path.get(i))) + .mapToDouble(Number::doubleValue) + .sum(); + } + + /** + * Creates a Graph object by given schema. + * + * In a graph schema, each vertex is assigned an edge map. + * If the vertex has no edges, then it should be assigned an empty map. + * + * @param schema of the graph + * @param the type of vertex in this graph + * @return graph object with given schema + */ + static Graph of(Map> schema) { + return () -> schema; + } +} diff --git a/algorithm/src/main/java/lv/id/jc/algorithm/graph/SearchAlgorithm.java b/algorithm/src/main/java/lv/id/jc/algorithm/graph/SearchAlgorithm.java new file mode 100644 index 0000000..79692d7 --- /dev/null +++ b/algorithm/src/main/java/lv/id/jc/algorithm/graph/SearchAlgorithm.java @@ -0,0 +1,23 @@ +package lv.id.jc.algorithm.graph; + +import java.util.List; + +/** + * A functional interface for graph search algorithm + * + * @author Jegors Čemisovs + * @param the type of vertex + * @since 1.0 + */ +@FunctionalInterface +public interface SearchAlgorithm { + /** + * Find the path from the source node to the target + * + * @param graph The graph in which we search for the path + * @param source Search starting point identifier + * @param target Search finish point identifier + * @return Path found or empty list if path cannot be found + */ + List findPath(Graph graph, T source, T target); +} diff --git a/algorithm/src/main/java/lv/id/jc/algorithm/graph/package-info.java b/algorithm/src/main/java/lv/id/jc/algorithm/graph/package-info.java new file mode 100644 index 0000000..28436e8 --- /dev/null +++ b/algorithm/src/main/java/lv/id/jc/algorithm/graph/package-info.java @@ -0,0 +1,4 @@ +/** + * This package contains graph pathfinding algorithms. + */ +package lv.id.jc.algorithm.graph; \ No newline at end of file diff --git a/algorithm/src/main/java/module-info.java b/algorithm/src/main/java/module-info.java new file mode 100644 index 0000000..941d8df --- /dev/null +++ b/algorithm/src/main/java/module-info.java @@ -0,0 +1,8 @@ +/** + * The module contains an interface for a graph and an interface for a graph search algorithm. + * There is an implementation of two search algorithms: + * Dijkstra's algorithm and Breadth First Search algorithm. + */ +module lv.id.jc.algorithm.graph { + exports lv.id.jc.algorithm.graph; +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 89fef3f..9516f98 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,3 @@ -rootProject.name = 'search-algorithm' +rootProject.name = 'algorithms' +include 'algorithm' + From 81c809e6bc8f78c2d0e1b2bb1573cc309b1e6cb1 Mon Sep 17 00:00:00 2001 From: Jegors Cemisovs Date: Tue, 4 Jan 2022 20:59:08 +0200 Subject: [PATCH 2/6] Add tests for subproject --- algorithm/build.gradle | 21 ++- .../id/jc/graph/BreadthFirstSearchSpec.groovy | 95 +++++++++++++ .../id/jc/graph/DijkstrasAlgorithmSpec.groovy | 127 ++++++++++++++++++ .../groovy/lv/id/jc/graph/GraphSpec.groovy | 100 ++++++++++++++ .../lv/id/jc/graph/SearchAlgorithmSpec.groovy | 63 +++++++++ .../src/test/resources/SpockConfig.groovy | 12 ++ 6 files changed, 417 insertions(+), 1 deletion(-) create mode 100644 algorithm/src/test/groovy/lv/id/jc/graph/BreadthFirstSearchSpec.groovy create mode 100644 algorithm/src/test/groovy/lv/id/jc/graph/DijkstrasAlgorithmSpec.groovy create mode 100644 algorithm/src/test/groovy/lv/id/jc/graph/GraphSpec.groovy create mode 100644 algorithm/src/test/groovy/lv/id/jc/graph/SearchAlgorithmSpec.groovy create mode 100644 algorithm/src/test/resources/SpockConfig.groovy diff --git a/algorithm/build.gradle b/algorithm/build.gradle index 8c6049f..af5b777 100644 --- a/algorithm/build.gradle +++ b/algorithm/build.gradle @@ -1,4 +1,5 @@ plugins { + id 'groovy' id 'java-library' id 'maven-publish' } @@ -41,4 +42,22 @@ publishing { } } } -} \ No newline at end of file +} + +dependencies { + // Spock Framework + testImplementation 'org.spockframework:spock-core:2.0-groovy-3.0' + testImplementation 'org.codehaus.groovy:groovy-all:3.0.9' + + // Spock Reports + testRuntimeClasspath( "com.athaydes:spock-reports:2.1.1-groovy-3.0" ) { + transitive = false // this avoids affecting your version of Groovy/Spock + } + // Required for spock-reports + testImplementation 'org.slf4j:slf4j-api:1.7.32' + testRuntimeClasspath 'org.slf4j:slf4j-simple:1.7.32' +} + +test { + useJUnitPlatform() +} diff --git a/algorithm/src/test/groovy/lv/id/jc/graph/BreadthFirstSearchSpec.groovy b/algorithm/src/test/groovy/lv/id/jc/graph/BreadthFirstSearchSpec.groovy new file mode 100644 index 0000000..7feffbd --- /dev/null +++ b/algorithm/src/test/groovy/lv/id/jc/graph/BreadthFirstSearchSpec.groovy @@ -0,0 +1,95 @@ +package lv.id.jc.graph + +import lv.id.jc.algorithm.graph.BreadthFirstSearch +import lv.id.jc.algorithm.graph.Graph +import spock.lang.* + +@Title("Breadth First Search Algorithm") +@See("https://en.wikipedia.org/wiki/Breadth-first_search") +@Narrative(""" +Breadth First Search algorithm for finding the shortest paths between nodes in a graph +""") +class BreadthFirstSearchSpec extends Specification { + @Subject + def algorithm = new BreadthFirstSearch() + + def 'should find a route for simple graph'() { + given: 'A simple graph' + def graph = Graph.of([ + A: [B: 7, C: 2], + B: [A: 3, C: 5], + C: [A: 1, B: 3] + ]) + + when: 'we use Breadth First Search algorithm to find a path' + def path = algorithm.findPath(graph, source, target) + + then: 'we get the shortest path' + path == shortest + + and: 'the distance calculated correctly' + 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'] + } + + def 'should find a route for complex graph'() { + given: 'A complex graph' + def graph = Graph.of([ + 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: 'we use Breadth First Search algorithm to find a path' + def path = algorithm.findPath(graph, source, target) + + then: 'we get the shortest path' + path == shortest + + and: 'the distance calculated correctly' + 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 + } + + def 'should thrown NPE path for an empty graph'() { + given: 'an empty graph' + def graph = Graph.of([:]) + + when: "we use Dijkstra's algorithm to find a path" + algorithm.findPath(graph, 'A', 'B') + + then: 'the exception was thrown' + thrown NullPointerException + } + + def "should return an empty path if can't find a route"() { + given: 'a simple graph with no edge between nodes' + def graph = Graph.of([A: [:], B: [:]]) + + when: 'we use Breadth First Search algorithm to find a path' + def path = algorithm.findPath(graph, 'A', 'B') + + then: 'we get an empty path' + path == [] + } +} diff --git a/algorithm/src/test/groovy/lv/id/jc/graph/DijkstrasAlgorithmSpec.groovy b/algorithm/src/test/groovy/lv/id/jc/graph/DijkstrasAlgorithmSpec.groovy new file mode 100644 index 0000000..db283c6 --- /dev/null +++ b/algorithm/src/test/groovy/lv/id/jc/graph/DijkstrasAlgorithmSpec.groovy @@ -0,0 +1,127 @@ +package lv.id.jc.graph + +import lv.id.jc.algorithm.graph.DijkstrasAlgorithm +import lv.id.jc.algorithm.graph.Graph +import spock.lang.* + +@Title("Dijkstra's Algorithm") +@See("https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm") +@Narrative("Dijkstra's algorithm is an algorithm for finding the fastest paths between nodes in a graph") +class DijkstrasAlgorithmSpec extends Specification { + @Subject + def algorithm = new DijkstrasAlgorithm() + + def 'should find a route for a simple graph'() { + given: 'A simple graph' + def graph = Graph.of([ + A: [B: 7, C: 2], + B: [A: 3, C: 5], + C: [A: 1, B: 3] + ]) + + when: "we use Dijkstra's algorithm to find a path" + def path = algorithm.findPath(graph, source, target) + + then: 'we get the fastest way' + path == fastest + + and: 'the distance calculated correctly' + graph.getDistance(path) == time as double + + where: + source | target || time | fastest + 'A' | 'A' || 0 | ['A'] + 'B' | 'B' || 0 | ['B'] + 'C' | 'C' || 0 | ['C'] + 'A' | 'B' || 5 | ['A', 'C', 'B'] + } + + def 'should find a route for a medium graph'() { + given: 'A medium graph' + def graph = Graph.of([ + A: [B: 5], + B: [A: 5, C: 10], + C: [B: 20, D: 5], + D: [E: 5], + E: [B: 5] + ]) + + when: "we use Dijkstra's algorithm to find a path" + def path = algorithm.findPath(graph, source, target) + + then: 'we get the fastest way' + path == fastest + + and: 'the distance calculated correctly' + 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' || 15 | ['A', 'B', 'C'] + 'C' | 'A' || 20 | ['C', 'D', 'E', 'B', 'A'] + } + + def 'should find a route for a complex graph'() { + given: 'A complex graph' + def graph = Graph.of([ + 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: "we use Dijkstra's algorithm to find a path" + def path = algorithm.findPath(graph, source, target) + + then: 'we get the fastest way' + path == fastest + + and: 'the distance calculated correctly' + 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'] + } + + def 'should thrown NPE for an empty graph'() { + given: 'an empty graph' + def graph = Graph.of([:]) + + when: "we use Dijkstra's algorithm to find a path" + algorithm.findPath(graph, 'A', 'B') + + then: 'the exception was thrown' + thrown NullPointerException + } + + def "should return an empty path if can't find a route"() { + given: 'a simple graph with no edge between nodes' + def graph = Graph.of([A: [:], B: [:]]) + + when: "we use Dijkstra's algorithm to find a path" + def path = algorithm.findPath(graph, 'A', 'B') + + then: 'we get an empty path' + path == [] + } +} diff --git a/algorithm/src/test/groovy/lv/id/jc/graph/GraphSpec.groovy b/algorithm/src/test/groovy/lv/id/jc/graph/GraphSpec.groovy new file mode 100644 index 0000000..5d87b77 --- /dev/null +++ b/algorithm/src/test/groovy/lv/id/jc/graph/GraphSpec.groovy @@ -0,0 +1,100 @@ +package lv.id.jc.graph + +import lv.id.jc.algorithm.graph.Graph +import spock.lang.Narrative +import spock.lang.Specification +import spock.lang.Title + +@Title("Generic Graph") +@Narrative("A generic implementation of Graph structure") +class GraphSpec extends Specification { + + def "should return edges for a given node"() { + given: 'a simple graph with three nodes' + def graph = Graph.of([ + A: [B: 7, C: 2], + B: [A: 3, C: 5], + C: [A: 1, B: 3] + ]) + + expect: 'The method returns expected edges for given node' + graph.edges(node) == expected + + where: + node | expected + 'A' | [B: 7, C: 2] + 'B' | [A: 3, C: 5] + 'C' | [A: 1, B: 3] + } + + def 'should calculate distance for a path'() { + given: "a complex graph with eight nodes" + def graph = Graph.of([ + 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] + ]) + + expect: 'the distance for a path correctly calculated' + graph.getDistance(path) == distance as double + + where: 'path and expected distance' + path | distance + ['A'] | 0 + ['A', 'B'] | 5 + ['B', 'A'] | 5 + ['A', 'B', 'A'] | 10 + ['A', 'B', 'A', 'B'] | 15 + ['C', 'D'] | 3 + ['D', 'C'] | 20 + ['D', 'E', 'F', 'G', 'C'] | 19 + } + + def 'should be zero distance for an empty path'() { + given: 'any graph' + def graph = Graph.of(_ as Map) + + expect: 'the distance is zero for an empty path' + graph.getDistance([]) == 0 + } + + def 'should be zero distance for any one node path'() { + given: 'any graph' + def graph = Graph.of(_ as Map) + + expect: 'the zero distance for any one-node path' + graph.getDistance(oneNodePath) == 0 + + where: 'the node may be of any type and even non-existent' + oneNodePath << [ + ['A'], ['B'], [2], ['X' as char], [12.56] + ] + } + + def 'should throw NPE for incorrect path'() { + given: "a medium graph with five nodes" + def graph = Graph.of([ + A: [B: 5], + B: [A: 5, C: 10], + C: [B: 20, D: 5], + D: [E: 5], + E: [B: 5] + ]) + + when: 'we call the method with incorrect path' + graph.getDistance(incorrectPath) + + then: 'the NPE thrown' + thrown NullPointerException + + where: 'path is more then one node' + incorrectPath << [ + ['E', 'D'], ['A', 'C'], ['A', 'B', 'D'] + ] + } +} \ No newline at end of file diff --git a/algorithm/src/test/groovy/lv/id/jc/graph/SearchAlgorithmSpec.groovy b/algorithm/src/test/groovy/lv/id/jc/graph/SearchAlgorithmSpec.groovy new file mode 100644 index 0000000..e3b51a2 --- /dev/null +++ b/algorithm/src/test/groovy/lv/id/jc/graph/SearchAlgorithmSpec.groovy @@ -0,0 +1,63 @@ +package lv.id.jc.graph + +import lv.id.jc.algorithm.graph.BreadthFirstSearch +import lv.id.jc.algorithm.graph.DijkstrasAlgorithm +import lv.id.jc.algorithm.graph.Graph +import spock.lang.Specification +import spock.lang.Subject +import spock.lang.Title + +@Title("Comparison of two algorithms") +class SearchAlgorithmSpec extends Specification { + @Subject + def bfsAlgorithm = new BreadthFirstSearch() + + @Subject + def dijkstras = new DijkstrasAlgorithm() + + def 'should find a route for a complex graph'() { + given: 'A complex graph sample' + def graph = Graph.of([ + 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: 'we use Breadth First Search algorithm for the first route' + def routeOne = bfsAlgorithm.findPath(graph, source, target) + + and: 'we use Dijkstras algorithm for the second route' + def routeTwo = dijkstras.findPath(graph, source, target) + + then: "the first route is the shortest" + routeOne == shortest + + and: 'the second route is the fastest' + routeTwo == fastest + + and: 'the distance calculated correctly' + graph.getDistance(routeOne) == d1 as double + graph.getDistance(routeTwo) == d2 as double + + where: + source | target || d1 | shortest | d2 | fastest + 'A' | 'A' || 0 | ['A'] | 0 | ['A'] + 'B' | 'B' || 0 | ['B'] | 0 | ['B'] + 'A' | 'B' || 5 | ['A', 'B'] | 5 | ['A', 'B'] + 'B' | 'A' || 5 | ['B', 'A'] | 5 | ['B', 'A'] + 'A' | 'C' || 12 | ['A', 'B', 'C'] | 9 | ['A', 'H', 'G', 'C'] + 'C' | 'A' || 12 | ['C', 'B', 'A'] | 12 | ['C', 'B', 'A'] + 'A' | 'G' || 5 | ['A', 'H', 'G'] | 5 | ['A', 'H', 'G'] + 'C' | 'D' || 3 | ['C', 'D'] | 3 | ['C', 'D'] + 'D' | 'C' || 20 | ['D', 'C'] | 19 | ['D', 'E', 'F', 'G', 'C'] + 'B' | 'D' || 10 | ['B', 'C', 'D'] | 10 | ['B', 'C', 'D'] + 'D' | 'B' || 27 | ['D', 'C', 'B'] | 26 | ['D', 'E', 'F', 'G', 'C', 'B'] + 'D' | 'H' || 34 | ['D', 'C', 'B', 'A', 'H'] | 33 | ['D', 'E', 'F', 'G', 'C', 'B', 'A', 'H'] + } + +} diff --git a/algorithm/src/test/resources/SpockConfig.groovy b/algorithm/src/test/resources/SpockConfig.groovy new file mode 100644 index 0000000..55671ba --- /dev/null +++ b/algorithm/src/test/resources/SpockConfig.groovy @@ -0,0 +1,12 @@ +spockReports { + + set(['com.athaydes.spockframework.report.showCodeBlocks' : true, + 'com.athaydes.spockframework.report.outputDir' : 'docs/spock-reports', + 'com.athaydes.spockframework.report.projectName' : 'Graph search algorithms', + 'com.athaydes.spockframework.report.projectVersion' : 1.1, + 'com.athaydes.spockframework.report.internal.HtmlReportCreator.enabled': true, + 'com.athaydes.spockframework.report.IReportCreator' : 'com.athaydes.spockframework.report.internal.HtmlReportCreator' + ]) + // com.athaydes.spockframework.report.template.TemplateReportCreator + // com.athaydes.spockframework.report.internal.HtmlReportCreator +} \ No newline at end of file From ff580845c75112aa9225ea2b8738474d411a472f Mon Sep 17 00:00:00 2001 From: Jegors Cemisovs Date: Tue, 4 Jan 2022 21:02:20 +0200 Subject: [PATCH 3/6] Add subproject sample --- .idea/gradle.xml | 1 + sample/build.gradle | 15 +++++++++++++++ settings.gradle | 1 + 3 files changed, 17 insertions(+) create mode 100644 sample/build.gradle diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 6f52909..dc522cc 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -12,6 +12,7 @@ diff --git a/sample/build.gradle b/sample/build.gradle new file mode 100644 index 0000000..bff2cf3 --- /dev/null +++ b/sample/build.gradle @@ -0,0 +1,15 @@ +plugins { + id 'groovy' + id 'java' +} + +group 'lv.id.jc' +version '1.1' + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.codehaus.groovy:groovy-all:3.0.9' +} diff --git a/settings.gradle b/settings.gradle index 9516f98..710d931 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,4 @@ rootProject.name = 'algorithms' include 'algorithm' +include 'sample' From cd64289c67cc531c9b6e722714da481018b3f3d1 Mon Sep 17 00:00:00 2001 From: Jegors Cemisovs Date: Tue, 4 Jan 2022 21:25:18 +0200 Subject: [PATCH 4/6] Add subproject sample --- .gitignore | 1 + .idea/jarRepositories.xml | 5 + algorithm/build.gradle | 2 +- build.gradle | 26 ---- sample/build.gradle | 16 +++ .../main/java/lv/id/jc/sample/GraphApp.java | 51 +++++++ .../algorithm/graph/BreadthFirstSearch.java | 48 ------- .../algorithm/graph/DijkstrasAlgorithm.java | 48 ------- .../java/lv/id/jc/algorithm/graph/Graph.java | 71 ---------- .../jc/algorithm/graph/SearchAlgorithm.java | 23 ---- .../id/jc/algorithm/graph/package-info.java | 4 - src/main/java/module-info.java | 8 -- .../graph/BreadthFirstSearchSpec.groovy | 95 ------------- .../graph/DijkstrasAlgorithmSpec.groovy | 127 ------------------ src/test/groovy/graph/GraphSpec.groovy | 100 -------------- .../groovy/graph/SearchAlgorithmSpec.groovy | 63 --------- src/test/resources/SpockConfig.groovy | 12 -- 17 files changed, 74 insertions(+), 626 deletions(-) create mode 100644 sample/src/main/java/lv/id/jc/sample/GraphApp.java delete mode 100644 src/main/java/lv/id/jc/algorithm/graph/BreadthFirstSearch.java delete mode 100644 src/main/java/lv/id/jc/algorithm/graph/DijkstrasAlgorithm.java delete mode 100644 src/main/java/lv/id/jc/algorithm/graph/Graph.java delete mode 100644 src/main/java/lv/id/jc/algorithm/graph/SearchAlgorithm.java delete mode 100644 src/main/java/lv/id/jc/algorithm/graph/package-info.java delete mode 100644 src/main/java/module-info.java delete mode 100644 src/test/groovy/graph/BreadthFirstSearchSpec.groovy delete mode 100644 src/test/groovy/graph/DijkstrasAlgorithmSpec.groovy delete mode 100644 src/test/groovy/graph/GraphSpec.groovy delete mode 100644 src/test/groovy/graph/SearchAlgorithmSpec.groovy delete mode 100644 src/test/resources/SpockConfig.groovy diff --git a/.gitignore b/.gitignore index d391d30..b1b10da 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,4 @@ out/ ############################## .DS_Store /algorithm/gradle.properties +/sample/gradle.properties diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml index fdc392f..73a34ad 100644 --- a/.idea/jarRepositories.xml +++ b/.idea/jarRepositories.xml @@ -16,5 +16,10 @@

- * The algorithm uses information about edge's distance to find the fastest path. - * - * @author Jegors Čemisovs - * @param the type of vertex - * @since 1.0 - */ -public class DijkstrasAlgorithm implements SearchAlgorithm { - - @Override - public List findPath(Graph graph, T source, T target) { - var queue = new LinkedList(); - var distances = new HashMap(); - var previous = new HashMap(); - queue.add(source); - distances.put(source, .0); - - while (!queue.isEmpty()) { - var prev = queue.removeFirst(); - graph.edges(prev).forEach((node, time) -> { - var distance = distances.get(prev) + time.doubleValue(); - if (distance < distances.getOrDefault(node, Double.MAX_VALUE)) { - previous.put(node, prev); - distances.put(node, distance); - queue.addLast(node); - } - }); - } - if (previous.containsKey(target) || source.equals(target)) { - var path = new LinkedList(); - iterate(target, Objects::nonNull, previous::get).forEach(path::addFirst); - return path; - } - return List.of(); - } - -} diff --git a/src/main/java/lv/id/jc/algorithm/graph/Graph.java b/src/main/java/lv/id/jc/algorithm/graph/Graph.java deleted file mode 100644 index 6ed2e9c..0000000 --- a/src/main/java/lv/id/jc/algorithm/graph/Graph.java +++ /dev/null @@ -1,71 +0,0 @@ -package lv.id.jc.algorithm.graph; - -import java.util.List; -import java.util.Map; -import java.util.stream.IntStream; - -/** - * An interface for weighted directed graph (network) - * - * @param the type of vertex in this graph - * @author Jegors Čemisovs - * @since 1.1 - */ -@FunctionalInterface -public interface Graph { - /** - * The schema of this graph. - * - * In a graph schema, each vertex is assigned an edge map. - * If the vertex has no edges, then it should be assigned an empty map. - * - * @return the graph scheme - */ - Map> schema(); - - /** - * Returns the edges of the given vertex, - * or {@code null} if this graph contains no given vertex. - * - *

A return value of {@code null} does not necessarily - * indicate that the specified vertex is not present in the graph; - * it's also possible that in the graph schema, {@code null} was specified - * for the edges of this vertex instead of an empty map. - * - * @param vertex vertex - * @return all links for the given vertex - * or null if no such vertex in the graph - */ - default Map edges(T vertex) { - return schema().get(vertex); - } - - /** - * Calculate the distance for the given path - * - * @param path the list of vertices representing the path - * @return distance for the given path as double - * @throws NullPointerException if {@code path} is incorrect and contains more than one vertex - */ - default double getDistance(List path) { - return IntStream - .range(1, path.size()) - .mapToObj(i -> edges(path.get(i - 1)).get(path.get(i))) - .mapToDouble(Number::doubleValue) - .sum(); - } - - /** - * Creates a Graph object by given schema. - * - * In a graph schema, each vertex is assigned an edge map. - * If the vertex has no edges, then it should be assigned an empty map. - * - * @param schema of the graph - * @param the type of vertex in this graph - * @return graph object with given schema - */ - static Graph of(Map> schema) { - return () -> schema; - } -} diff --git a/src/main/java/lv/id/jc/algorithm/graph/SearchAlgorithm.java b/src/main/java/lv/id/jc/algorithm/graph/SearchAlgorithm.java deleted file mode 100644 index 79692d7..0000000 --- a/src/main/java/lv/id/jc/algorithm/graph/SearchAlgorithm.java +++ /dev/null @@ -1,23 +0,0 @@ -package lv.id.jc.algorithm.graph; - -import java.util.List; - -/** - * A functional interface for graph search algorithm - * - * @author Jegors Čemisovs - * @param the type of vertex - * @since 1.0 - */ -@FunctionalInterface -public interface SearchAlgorithm { - /** - * Find the path from the source node to the target - * - * @param graph The graph in which we search for the path - * @param source Search starting point identifier - * @param target Search finish point identifier - * @return Path found or empty list if path cannot be found - */ - List findPath(Graph graph, T source, T target); -} diff --git a/src/main/java/lv/id/jc/algorithm/graph/package-info.java b/src/main/java/lv/id/jc/algorithm/graph/package-info.java deleted file mode 100644 index 28436e8..0000000 --- a/src/main/java/lv/id/jc/algorithm/graph/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * This package contains graph pathfinding algorithms. - */ -package lv.id.jc.algorithm.graph; \ No newline at end of file diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java deleted file mode 100644 index 941d8df..0000000 --- a/src/main/java/module-info.java +++ /dev/null @@ -1,8 +0,0 @@ -/** - * The module contains an interface for a graph and an interface for a graph search algorithm. - * There is an implementation of two search algorithms: - * Dijkstra's algorithm and Breadth First Search algorithm. - */ -module lv.id.jc.algorithm.graph { - exports lv.id.jc.algorithm.graph; -} \ No newline at end of file diff --git a/src/test/groovy/graph/BreadthFirstSearchSpec.groovy b/src/test/groovy/graph/BreadthFirstSearchSpec.groovy deleted file mode 100644 index 1d76485..0000000 --- a/src/test/groovy/graph/BreadthFirstSearchSpec.groovy +++ /dev/null @@ -1,95 +0,0 @@ -package graph - -import lv.id.jc.algorithm.graph.BreadthFirstSearch -import lv.id.jc.algorithm.graph.Graph -import spock.lang.* - -@Title("Breadth First Search Algorithm") -@See("https://en.wikipedia.org/wiki/Breadth-first_search") -@Narrative(""" -Breadth First Search algorithm for finding the shortest paths between nodes in a graph -""") -class BreadthFirstSearchSpec extends Specification { - @Subject - def algorithm = new BreadthFirstSearch() - - def 'should find a route for simple graph'() { - given: 'A simple graph' - def graph = Graph.of([ - A: [B: 7, C: 2], - B: [A: 3, C: 5], - C: [A: 1, B: 3] - ]) - - when: 'we use Breadth First Search algorithm to find a path' - def path = algorithm.findPath(graph, source, target) - - then: 'we get the shortest path' - path == shortest - - and: 'the distance calculated correctly' - 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'] - } - - def 'should find a route for complex graph'() { - given: 'A complex graph' - def graph = Graph.of([ - 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: 'we use Breadth First Search algorithm to find a path' - def path = algorithm.findPath(graph, source, target) - - then: 'we get the shortest path' - path == shortest - - and: 'the distance calculated correctly' - 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 - } - - def 'should thrown NPE path for an empty graph'() { - given: 'an empty graph' - def graph = Graph.of([:]) - - when: "we use Dijkstra's algorithm to find a path" - algorithm.findPath(graph, 'A', 'B') - - then: 'the exception was thrown' - thrown NullPointerException - } - - def "should return an empty path if can't find a route"() { - given: 'a simple graph with no edge between nodes' - def graph = Graph.of([A: [:], B: [:]]) - - when: 'we use Breadth First Search algorithm to find a path' - def path = algorithm.findPath(graph, 'A', 'B') - - then: 'we get an empty path' - path == [] - } -} diff --git a/src/test/groovy/graph/DijkstrasAlgorithmSpec.groovy b/src/test/groovy/graph/DijkstrasAlgorithmSpec.groovy deleted file mode 100644 index 9ba35d4..0000000 --- a/src/test/groovy/graph/DijkstrasAlgorithmSpec.groovy +++ /dev/null @@ -1,127 +0,0 @@ -package graph - -import lv.id.jc.algorithm.graph.DijkstrasAlgorithm -import lv.id.jc.algorithm.graph.Graph -import spock.lang.* - -@Title("Dijkstra's Algorithm") -@See("https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm") -@Narrative("Dijkstra's algorithm is an algorithm for finding the fastest paths between nodes in a graph") -class DijkstrasAlgorithmSpec extends Specification { - @Subject - def algorithm = new DijkstrasAlgorithm() - - def 'should find a route for a simple graph'() { - given: 'A simple graph' - def graph = Graph.of([ - A: [B: 7, C: 2], - B: [A: 3, C: 5], - C: [A: 1, B: 3] - ]) - - when: "we use Dijkstra's algorithm to find a path" - def path = algorithm.findPath(graph, source, target) - - then: 'we get the fastest way' - path == fastest - - and: 'the distance calculated correctly' - graph.getDistance(path) == time as double - - where: - source | target || time | fastest - 'A' | 'A' || 0 | ['A'] - 'B' | 'B' || 0 | ['B'] - 'C' | 'C' || 0 | ['C'] - 'A' | 'B' || 5 | ['A', 'C', 'B'] - } - - def 'should find a route for a medium graph'() { - given: 'A medium graph' - def graph = Graph.of([ - A: [B: 5], - B: [A: 5, C: 10], - C: [B: 20, D: 5], - D: [E: 5], - E: [B: 5] - ]) - - when: "we use Dijkstra's algorithm to find a path" - def path = algorithm.findPath(graph, source, target) - - then: 'we get the fastest way' - path == fastest - - and: 'the distance calculated correctly' - 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' || 15 | ['A', 'B', 'C'] - 'C' | 'A' || 20 | ['C', 'D', 'E', 'B', 'A'] - } - - def 'should find a route for a complex graph'() { - given: 'A complex graph' - def graph = Graph.of([ - 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: "we use Dijkstra's algorithm to find a path" - def path = algorithm.findPath(graph, source, target) - - then: 'we get the fastest way' - path == fastest - - and: 'the distance calculated correctly' - 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'] - } - - def 'should thrown NPE for an empty graph'() { - given: 'an empty graph' - def graph = Graph.of([:]) - - when: "we use Dijkstra's algorithm to find a path" - algorithm.findPath(graph, 'A', 'B') - - then: 'the exception was thrown' - thrown NullPointerException - } - - def "should return an empty path if can't find a route"() { - given: 'a simple graph with no edge between nodes' - def graph = Graph.of([A: [:], B: [:]]) - - when: "we use Dijkstra's algorithm to find a path" - def path = algorithm.findPath(graph, 'A', 'B') - - then: 'we get an empty path' - path == [] - } -} diff --git a/src/test/groovy/graph/GraphSpec.groovy b/src/test/groovy/graph/GraphSpec.groovy deleted file mode 100644 index 7264018..0000000 --- a/src/test/groovy/graph/GraphSpec.groovy +++ /dev/null @@ -1,100 +0,0 @@ -package graph - -import lv.id.jc.algorithm.graph.Graph -import spock.lang.Narrative -import spock.lang.Specification -import spock.lang.Title - -@Title("Generic Graph") -@Narrative("A generic implementation of Graph structure") -class GraphSpec extends Specification { - - def "should return edges for a given node"() { - given: 'a simple graph with three nodes' - def graph = Graph.of([ - A: [B: 7, C: 2], - B: [A: 3, C: 5], - C: [A: 1, B: 3] - ]) - - expect: 'The method returns expected edges for given node' - graph.edges(node) == expected - - where: - node | expected - 'A' | [B: 7, C: 2] - 'B' | [A: 3, C: 5] - 'C' | [A: 1, B: 3] - } - - def 'should calculate distance for a path'() { - given: "a complex graph with eight nodes" - def graph = Graph.of([ - 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] - ]) - - expect: 'the distance for a path correctly calculated' - graph.getDistance(path) == distance as double - - where: 'path and expected distance' - path | distance - ['A'] | 0 - ['A', 'B'] | 5 - ['B', 'A'] | 5 - ['A', 'B', 'A'] | 10 - ['A', 'B', 'A', 'B'] | 15 - ['C', 'D'] | 3 - ['D', 'C'] | 20 - ['D', 'E', 'F', 'G', 'C'] | 19 - } - - def 'should be zero distance for an empty path'() { - given: 'any graph' - def graph = Graph.of(_ as Map) - - expect: 'the distance is zero for an empty path' - graph.getDistance([]) == 0 - } - - def 'should be zero distance for any one node path'() { - given: 'any graph' - def graph = Graph.of(_ as Map) - - expect: 'the zero distance for any one-node path' - graph.getDistance(oneNodePath) == 0 - - where: 'the node may be of any type and even non-existent' - oneNodePath << [ - ['A'], ['B'], [2], ['X' as char], [12.56] - ] - } - - def 'should throw NPE for incorrect path'() { - given: "a medium graph with five nodes" - def graph = Graph.of([ - A: [B: 5], - B: [A: 5, C: 10], - C: [B: 20, D: 5], - D: [E: 5], - E: [B: 5] - ]) - - when: 'we call the method with incorrect path' - graph.getDistance(incorrectPath) - - then: 'the NPE thrown' - thrown NullPointerException - - where: 'path is more then one node' - incorrectPath << [ - ['E', 'D'], ['A', 'C'], ['A', 'B', 'D'] - ] - } -} \ No newline at end of file diff --git a/src/test/groovy/graph/SearchAlgorithmSpec.groovy b/src/test/groovy/graph/SearchAlgorithmSpec.groovy deleted file mode 100644 index 337841f..0000000 --- a/src/test/groovy/graph/SearchAlgorithmSpec.groovy +++ /dev/null @@ -1,63 +0,0 @@ -package graph - -import lv.id.jc.algorithm.graph.BreadthFirstSearch -import lv.id.jc.algorithm.graph.DijkstrasAlgorithm -import lv.id.jc.algorithm.graph.Graph -import spock.lang.Specification -import spock.lang.Subject -import spock.lang.Title - -@Title("Comparison of two algorithms") -class SearchAlgorithmSpec extends Specification { - @Subject - def bfsAlgorithm = new BreadthFirstSearch() - - @Subject - def dijkstras = new DijkstrasAlgorithm() - - def 'should find a route for a complex graph'() { - given: 'A complex graph sample' - def graph = Graph.of([ - 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: 'we use Breadth First Search algorithm for the first route' - def routeOne = bfsAlgorithm.findPath(graph, source, target) - - and: 'we use Dijkstras algorithm for the second route' - def routeTwo = dijkstras.findPath(graph, source, target) - - then: "the first route is the shortest" - routeOne == shortest - - and: 'the second route is the fastest' - routeTwo == fastest - - and: 'the distance calculated correctly' - graph.getDistance(routeOne) == d1 as double - graph.getDistance(routeTwo) == d2 as double - - where: - source | target || d1 | shortest | d2 | fastest - 'A' | 'A' || 0 | ['A'] | 0 | ['A'] - 'B' | 'B' || 0 | ['B'] | 0 | ['B'] - 'A' | 'B' || 5 | ['A', 'B'] | 5 | ['A', 'B'] - 'B' | 'A' || 5 | ['B', 'A'] | 5 | ['B', 'A'] - 'A' | 'C' || 12 | ['A', 'B', 'C'] | 9 | ['A', 'H', 'G', 'C'] - 'C' | 'A' || 12 | ['C', 'B', 'A'] | 12 | ['C', 'B', 'A'] - 'A' | 'G' || 5 | ['A', 'H', 'G'] | 5 | ['A', 'H', 'G'] - 'C' | 'D' || 3 | ['C', 'D'] | 3 | ['C', 'D'] - 'D' | 'C' || 20 | ['D', 'C'] | 19 | ['D', 'E', 'F', 'G', 'C'] - 'B' | 'D' || 10 | ['B', 'C', 'D'] | 10 | ['B', 'C', 'D'] - 'D' | 'B' || 27 | ['D', 'C', 'B'] | 26 | ['D', 'E', 'F', 'G', 'C', 'B'] - 'D' | 'H' || 34 | ['D', 'C', 'B', 'A', 'H'] | 33 | ['D', 'E', 'F', 'G', 'C', 'B', 'A', 'H'] - } - -} diff --git a/src/test/resources/SpockConfig.groovy b/src/test/resources/SpockConfig.groovy deleted file mode 100644 index 55671ba..0000000 --- a/src/test/resources/SpockConfig.groovy +++ /dev/null @@ -1,12 +0,0 @@ -spockReports { - - set(['com.athaydes.spockframework.report.showCodeBlocks' : true, - 'com.athaydes.spockframework.report.outputDir' : 'docs/spock-reports', - 'com.athaydes.spockframework.report.projectName' : 'Graph search algorithms', - 'com.athaydes.spockframework.report.projectVersion' : 1.1, - 'com.athaydes.spockframework.report.internal.HtmlReportCreator.enabled': true, - 'com.athaydes.spockframework.report.IReportCreator' : 'com.athaydes.spockframework.report.internal.HtmlReportCreator' - ]) - // com.athaydes.spockframework.report.template.TemplateReportCreator - // com.athaydes.spockframework.report.internal.HtmlReportCreator -} \ No newline at end of file From 2e988b6b853fdb1dab17eb189269d4a980e8f99d Mon Sep 17 00:00:00 2001 From: Jegors Cemisovs Date: Thu, 6 Jan 2022 18:57:20 +0200 Subject: [PATCH 5/6] Fixed gradle files --- .idea/gradle.xml | 2 +- algorithm/build.gradle | 1 - algorithm/src/main/java/module-info.java | 5 --- application/build.gradle | 13 ++++++++ .../java/lv/id/jc/application}/GraphApp.java | 27 +++++++--------- application/src/main/java/module-info.java | 4 +++ build.gradle | 3 -- sample/build.gradle | 31 ------------------- settings.gradle | 6 ++-- 9 files changed, 31 insertions(+), 61 deletions(-) create mode 100644 application/build.gradle rename {sample/src/main/java/lv/id/jc/sample => application/src/main/java/lv/id/jc/application}/GraphApp.java (63%) create mode 100644 application/src/main/java/module-info.java delete mode 100644 build.gradle delete mode 100644 sample/build.gradle diff --git a/.idea/gradle.xml b/.idea/gradle.xml index dc522cc..7e20a16 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -12,7 +12,7 @@ diff --git a/algorithm/build.gradle b/algorithm/build.gradle index 4e6f088..828691e 100644 --- a/algorithm/build.gradle +++ b/algorithm/build.gradle @@ -3,7 +3,6 @@ plugins { id 'java-library' id 'maven-publish' } -sourceCompatibility = JavaVersion.VERSION_17 group 'lv.id.jc' version '1.1' diff --git a/algorithm/src/main/java/module-info.java b/algorithm/src/main/java/module-info.java index 941d8df..8a8fdf6 100644 --- a/algorithm/src/main/java/module-info.java +++ b/algorithm/src/main/java/module-info.java @@ -1,8 +1,3 @@ -/** - * The module contains an interface for a graph and an interface for a graph search algorithm. - * There is an implementation of two search algorithms: - * Dijkstra's algorithm and Breadth First Search algorithm. - */ module lv.id.jc.algorithm.graph { exports lv.id.jc.algorithm.graph; } \ No newline at end of file diff --git a/application/build.gradle b/application/build.gradle new file mode 100644 index 0000000..7d38e08 --- /dev/null +++ b/application/build.gradle @@ -0,0 +1,13 @@ +plugins { + id 'application' +} + +dependencies { + implementation project(':algorithm') +} + +application { + mainModule = 'lv.id.jc.application' + mainClass = 'lv.id.jc.application.GraphApp' + applicationDefaultJvmArgs = ['-Dgreeting.language=en'] +} diff --git a/sample/src/main/java/lv/id/jc/sample/GraphApp.java b/application/src/main/java/lv/id/jc/application/GraphApp.java similarity index 63% rename from sample/src/main/java/lv/id/jc/sample/GraphApp.java rename to application/src/main/java/lv/id/jc/application/GraphApp.java index 8b6956f..a6c90b3 100644 --- a/sample/src/main/java/lv/id/jc/sample/GraphApp.java +++ b/application/src/main/java/lv/id/jc/application/GraphApp.java @@ -1,4 +1,4 @@ -package lv.id.jc.sample; +package lv.id.jc.application; import lv.id.jc.algorithm.graph.BreadthFirstSearch; import lv.id.jc.algorithm.graph.DijkstrasAlgorithm; @@ -7,10 +7,8 @@ import java.util.Map; -import static java.lang.System.*; - public class GraphApp { - private static final Graph COMPLEX_GRAPH = Graph.of(Map.of( + private static final Graph graph = Graph.of(Map.of( 'A', Map.of('B', 5, 'H', 2), 'B', Map.of('A', 5, 'C', 7), 'C', Map.of('B', 7, 'D', 3, 'G', 4), @@ -24,19 +22,16 @@ public class GraphApp { private static final SearchAlgorithm shortest = new BreadthFirstSearch<>(); public static void main(String[] args) { - out.println(COMPLEX_GRAPH); - - printRoute(COMPLEX_GRAPH, 'D', 'C'); - printRoute(COMPLEX_GRAPH, 'A', 'G'); - printRoute(COMPLEX_GRAPH, 'D', 'H'); + printRoute('D', 'C'); + printRoute('A', 'G'); + printRoute('D', 'H'); } - private static void printRoute(final Graph graph, - final Character source, - final Character target) { - final var routeOne = shortest.findPath(graph, source, target); - final var routeTwo = fastest.findPath(graph, source, target); - final var message = """ + @SuppressWarnings("squid:S106") + private static void printRoute(Character source, Character target) { + var routeOne = shortest.findPath(graph, source, target); + var routeTwo = fastest.findPath(graph, source, target); + var message = """ Find the path from %s to %s - the shortest take %.0f min and the path is %s @@ -46,6 +41,6 @@ private static void printRoute(final Graph graph, graph.getDistance(routeOne), routeOne, graph.getDistance(routeTwo), routeTwo); - out.println(message); + System.out.println(message); } } diff --git a/application/src/main/java/module-info.java b/application/src/main/java/module-info.java new file mode 100644 index 0000000..1ffe06e --- /dev/null +++ b/application/src/main/java/module-info.java @@ -0,0 +1,4 @@ +module lv.id.jc.application { + requires lv.id.jc.algorithm.graph; + exports lv.id.jc.application; +} \ No newline at end of file diff --git a/build.gradle b/build.gradle deleted file mode 100644 index b4ff6b1..0000000 --- a/build.gradle +++ /dev/null @@ -1,3 +0,0 @@ -group 'lv.id.jc' -version '1.1' - diff --git a/sample/build.gradle b/sample/build.gradle deleted file mode 100644 index 44bec47..0000000 --- a/sample/build.gradle +++ /dev/null @@ -1,31 +0,0 @@ -plugins { - id 'groovy' - id 'java' -} - -group 'lv.id.jc' -version '1.1' - -sourceCompatibility = JavaVersion.VERSION_17 - -repositories { - mavenCentral() - - maven { - // Choose whatever name you like - name = "Algorithms" - // The url of the repository that contains the published artifacts - url = "https://maven.pkg.github.com/rabestro/algorithms" - credentials { - // The credentials (described in the next section) - username = project.findProperty("gpr.user") - password = project.findProperty("gpr.key") - } - } -} - -dependencies { - implementation 'org.codehaus.groovy:groovy-all:3.0.9' - - compileOnly 'lv.id.jc:algorithm:1.1' -} diff --git a/settings.gradle b/settings.gradle index 710d931..b469ed7 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,4 +1,2 @@ -rootProject.name = 'algorithms' -include 'algorithm' -include 'sample' - +rootProject.name = 'graphs-algorithms' +include 'algorithm', 'application' From ea5a251b188f4d37dae4e1cb760b337377c2ff70 Mon Sep 17 00:00:00 2001 From: Jegors Cemisovs Date: Thu, 6 Jan 2022 19:02:24 +0200 Subject: [PATCH 6/6] Fixed issue #13 --- .../java/lv/id/jc/algorithm/graph/BreadthFirstSearch.java | 7 ++++--- .../java/lv/id/jc/algorithm/graph/DijkstrasAlgorithm.java | 5 +++-- algorithm/src/main/java/module-info.java | 5 +++++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/algorithm/src/main/java/lv/id/jc/algorithm/graph/BreadthFirstSearch.java b/algorithm/src/main/java/lv/id/jc/algorithm/graph/BreadthFirstSearch.java index 6c6cfa2..b7e927d 100644 --- a/algorithm/src/main/java/lv/id/jc/algorithm/graph/BreadthFirstSearch.java +++ b/algorithm/src/main/java/lv/id/jc/algorithm/graph/BreadthFirstSearch.java @@ -1,5 +1,6 @@ package lv.id.jc.algorithm.graph; +import java.util.ArrayDeque; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; @@ -11,18 +12,18 @@ /** * Algorithm for finding the shortest paths between nodes in a graph. - * + *

* The algorithm doesn't take into account the distance between nodes. * - * @author Jegors Čemisovs * @param the type of vertex + * @author Jegors Čemisovs * @since 1.0 */ public class BreadthFirstSearch implements SearchAlgorithm { @Override public List findPath(Graph graph, T source, T target) { - var queue = new LinkedList(); + var queue = new ArrayDeque(); var visited = new HashSet(); var previous = new HashMap(); queue.add(source); diff --git a/algorithm/src/main/java/lv/id/jc/algorithm/graph/DijkstrasAlgorithm.java b/algorithm/src/main/java/lv/id/jc/algorithm/graph/DijkstrasAlgorithm.java index c6769c7..41f7817 100644 --- a/algorithm/src/main/java/lv/id/jc/algorithm/graph/DijkstrasAlgorithm.java +++ b/algorithm/src/main/java/lv/id/jc/algorithm/graph/DijkstrasAlgorithm.java @@ -1,5 +1,6 @@ package lv.id.jc.algorithm.graph; +import java.util.ArrayDeque; import java.util.HashMap; import java.util.LinkedList; import java.util.List; @@ -12,15 +13,15 @@ *

* The algorithm uses information about edge's distance to find the fastest path. * - * @author Jegors Čemisovs * @param the type of vertex + * @author Jegors Čemisovs * @since 1.0 */ public class DijkstrasAlgorithm implements SearchAlgorithm { @Override public List findPath(Graph graph, T source, T target) { - var queue = new LinkedList(); + var queue = new ArrayDeque(); var distances = new HashMap(); var previous = new HashMap(); queue.add(source); diff --git a/algorithm/src/main/java/module-info.java b/algorithm/src/main/java/module-info.java index 8a8fdf6..941d8df 100644 --- a/algorithm/src/main/java/module-info.java +++ b/algorithm/src/main/java/module-info.java @@ -1,3 +1,8 @@ +/** + * The module contains an interface for a graph and an interface for a graph search algorithm. + * There is an implementation of two search algorithms: + * Dijkstra's algorithm and Breadth First Search algorithm. + */ module lv.id.jc.algorithm.graph { exports lv.id.jc.algorithm.graph; } \ No newline at end of file