Repository: TheAlgorithms/Java
Pull request #5603 (2024-10-06 06:48:59)

Add tests for `AStar.java`, enhance documentation
<!--
Thank you for your contribution!
In order to reduce the number of notifications sent to the maintainers, please:
- create your PR as draft, cf. https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests#draft-pull-requests,
- make sure that all of the CI checks pass,
- mark your PR as ready for review, cf. https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/changing-the-stage-of-a-pull-request#marking-a-pull-request-as-ready-for-review
-->

<!-- For completed items, change [ ] to [x] -->

- [x] I have read [CONTRIBUTING.md](https://github.com/TheAlgorithms/Java/blob/master/CONTRIBUTING.md).
- [x] This pull request is all my own work -- I have not plagiarized it.
- [x] All filenames are in PascalCase.
- [x] All functions and variable names follow Java naming conventions.
- [x] All new algorithms have a URL in their comments that points to Wikipedia or other similar explanations.
- [x] All new code is formatted with `clang-format -i --style=file path/to/your/file.java`

codecov-commenter: ## [Codecov](https://app.codecov.io/gh/TheAlgorithms/Java/pull/5603?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TheAlgorithms) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 54.95%. Comparing base [(`f34fe4d`)](https://app.codecov.io/gh/TheAlgorithms/Java/commit/f34fe4d8408ac35cef900cfae022ffadfc007ea3?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TheAlgorithms) to head [(`0215a81`)](https://app.codecov.io/gh/TheAlgorithms/Java/commit/0215a8106a38fd78a235dd84d24be3c5170a71d4?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TheAlgorithms).
> Report is 1 commits behind head on master.

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff              @@
##             master    #5603      +/-   ##
============================================
+ Coverage     54.56%   54.95%   +0.39%     
- Complexity     3413     3428      +15     
============================================
  Files           535      536       +1     
  Lines         15383    15399      +16     
  Branches       2935     2937       +2     
============================================
+ Hits           8393     8462      +69     
+ Misses         6637     6584      -53     
  Partials        353      353              
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/TheAlgorithms/Java/pull/5603?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TheAlgorithms).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TheAlgorithms).

Hardvan: Thanks!

Patch:
diff --git a/DIRECTORY.md b/DIRECTORY.md
index 7e5d0d3d..87922528 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -694,6 +694,7 @@
             * dynamicarray
               * [DynamicArrayTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/dynamicarray/DynamicArrayTest.java)
             * graphs
+              * [AStarTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/AStarTest.java)
               * [BipartiteGraphDFSTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/BipartiteGraphDFSTest.java)
               * [BoruvkaAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/BoruvkaAlgorithmTest.java)
               * [DijkstraAlgorithmTest](https://github.com/TheAlgorithms/Java/blob/master/src/test/java/com/thealgorithms/datastructures/graphs/DijkstraAlgorithmTest.java)
diff --git a/src/main/java/com/thealgorithms/datastructures/graphs/AStar.java b/src/main/java/com/thealgorithms/datastructures/graphs/AStar.java
index 54fb5fba..460c05e0 100644
--- a/src/main/java/com/thealgorithms/datastructures/graphs/AStar.java
+++ b/src/main/java/com/thealgorithms/datastructures/graphs/AStar.java
@@ -1,25 +1,26 @@
-/*
-        Time Complexity = O(E), where E is equal to the number of edges
- */
 package com.thealgorithms.datastructures.graphs;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Comparator;
 import java.util.List;
 import java.util.PriorityQueue;
 
+/**
+ * AStar class implements the A* pathfinding algorithm to find the shortest path in a graph.
+ * The graph is represented using an adjacency list, and the algorithm uses a heuristic to estimate
+ * the cost to reach the destination node.
+ * Time Complexity = O(E), where E is equal to the number of edges
+ */
 public final class AStar {
     private AStar() {
     }
 
-    private static class Graph {
-
-        // Graph's structure can be changed only applying changes to this class.
-
+    /**
+     * Represents a graph using an adjacency list.
+     */
+    static class Graph {
         private ArrayList<ArrayList<Edge>> graph;
 
-        // Initialise ArrayLists in Constructor
         Graph(int size) {
             this.graph = new ArrayList<>();
             for (int i = 0; i < size; i++) {
@@ -31,15 +32,17 @@ public final class AStar {
             return this.graph.get(from);
         }
 
-        // Graph is bidirectional, for just one direction remove second instruction of this method.
+        // Add a bidirectional edge to the graph
         private void addEdge(Edge edge) {
             this.graph.get(edge.getFrom()).add(new Edge(edge.getFrom(), edge.getTo(), edge.getWeight()));
             this.graph.get(edge.getTo()).add(new Edge(edge.getTo(), edge.getFrom(), edge.getWeight()));
         }
     }
 
+    /**
+     * Represents an edge in the graph with a start node, end node, and weight.
+     */
     private static class Edge {
-
         private int from;
         private int to;
         private int weight;
@@ -63,12 +66,13 @@ public final class AStar {
         }
     }
 
-    // class to iterate during the algorithm execution, and also used to return the solution.
-    private static class PathAndDistance {
-
-        private int distance; // distance advanced so far.
-        private ArrayList<Integer> path; // list of visited nodes in this path.
-        private int estimated; // heuristic value associated to the last node od the path (current node).
+    /**
+     * Contains information about the path and its total distance.
+     */
+    static class PathAndDistance {
+        private int distance; // total distance from the start node
+        private ArrayList<Integer> path; // list of nodes in the path
+        private int estimated; // heuristic estimate for reaching the destination
 
         PathAndDistance(int distance, ArrayList<Integer> path, int estimated) {
             this.distance = distance;
@@ -87,112 +91,54 @@ public final class AStar {
         public int getEstimated() {
             return estimated;
         }
-
-        private void printSolution() {
-            if (this.path != null) {
-                System.out.println("Optimal path: " + this.path + ", distance: " + this.distance);
-            } else {
-                System.out.println("There is no path available to connect the points");
-            }
-        }
     }
 
-    private static void initializeGraph(Graph graph, ArrayList<Integer> data) {
+    // Initializes the graph with edges defined in the input data
+    static void initializeGraph(Graph graph, ArrayList<Integer> data) {
         for (int i = 0; i < data.size(); i += 4) {
             graph.addEdge(new Edge(data.get(i), data.get(i + 1), data.get(i + 2)));
         }
-        /*
-    .x. node
-    (y) cost
-    - or | or / bidirectional connection
-
-                          ( 98)- .7. -(86)- .4.
-                            |
-                    ( 85)- .17. -(142)- .18. -(92)- .8. -(87)- .11.
-                      |
-                     . 1. -------------------- (160)
-                      |  \                       |
-                    (211) \                     .6.
-                      |    \                     |
-                     . 5.  (101)-.13. -(138)   (115)
-                      |           |     |     /
-                    ( 99)       ( 97)   |    /
-                      |           |     |   /
-        .12. -(151)- .15. -(80)- .14.   |  /
-         |            |           |     | /
-       ( 71)        (140)       (146)- .2. -(120)
-         |            |                       |
-        .19. -( 75)- . 0.        .10. -(75)- .3.
-                      |            |
-                    (118)        ( 70)
-                      |            |
-                     .16. -(111)- .9.
-         */
-    }
-
-    public static void main(String[] args) {
-        // heuristic function optimistic values
-        int[] heuristic = {
-            366,
-            0,
-            160,
-            242,
-            161,
-            178,
-            77,
-            151,
-            226,
-            244,
-            241,
-            234,
-            380,
-            98,
-            193,
-            253,
-            329,
-            80,
-            199,
-            374,
-        };
-
-        Graph graph = new Graph(20);
-        ArrayList<Integer> graphData = new ArrayList<>(Arrays.asList(0, 19, 75, null, 0, 15, 140, null, 0, 16, 118, null, 19, 12, 71, null, 12, 15, 151, null, 16, 9, 111, null, 9, 10, 70, null, 10, 3, 75, null, 3, 2, 120, null, 2, 14, 146, null, 2, 13, 138, null, 2, 6, 115, null, 15, 14, 80, null,
-            15, 5, 99, null, 14, 13, 97, null, 5, 1, 211, null, 13, 1, 101, null, 6, 1, 160, null, 1, 17, 85, null, 17, 7, 98, null, 7, 4, 86, null, 17, 18, 142, null, 18, 8, 92, null, 8, 11, 87));
-        initializeGraph(graph, graphData);
-
-        PathAndDistance solution = aStar(3, 1, graph, heuristic);
-        solution.printSolution();
     }
 
+    /**
+     * Implements the A* pathfinding algorithm to find the shortest path from a start node to a destination node.
+     *
+     * @param from     the starting node
+     * @param to       the destination node
+     * @param graph    the graph representation of the problem
+     * @param heuristic the heuristic estimates for each node
+     * @return a PathAndDistance object containing the shortest path and its distance
+     */
     public static PathAndDistance aStar(int from, int to, Graph graph, int[] heuristic) {
-        // nodes are prioritised by the less value of the current distance of their paths, and the
-        // estimated value
-        // given by the heuristic function to reach the destination point from the current point.
+        // PriorityQueue to explore nodes based on their distance and estimated cost to reach the destination
         PriorityQueue<PathAndDistance> queue = new PriorityQueue<>(Comparator.comparingInt(a -> (a.getDistance() + a.getEstimated())));
 
-        // dummy data to start the algorithm from the beginning point
-        queue.add(new PathAndDistance(0, new ArrayList<>(List.of(from)), 0));
+        // Start with the initial node
+        queue.add(new PathAndDistance(0, new ArrayList<>(List.of(from)), heuristic[from]));
 
         boolean solutionFound = false;
         PathAndDistance currentData = new PathAndDistance(-1, null, -1);
+
         while (!queue.isEmpty() && !solutionFound) {
-            currentData = queue.poll(); // first in the queue, best node so keep exploring.
-            int currentPosition = currentData.getPath().get(currentData.getPath().size() - 1); // current node.
+            currentData = queue.poll(); // get the best node from the queue
+            int currentPosition = currentData.getPath().get(currentData.getPath().size() - 1); // current node
+
+            // Check if the destination has been reached
             if (currentPosition == to) {
                 solutionFound = true;
             } else {
                 for (Edge edge : graph.getNeighbours(currentPosition)) {
-                    if (!currentData.getPath().contains(edge.getTo())) { // Avoid Cycles
+                    // Avoid cycles by checking if the next node is already in the path
+                    if (!currentData.getPath().contains(edge.getTo())) {
                         ArrayList<Integer> updatedPath = new ArrayList<>(currentData.getPath());
-                        updatedPath.add(edge.getTo()); // Add the new node to the path, update the distance,
-                        // and the heuristic function value associated to that path.
+                        updatedPath.add(edge.getTo());
+
+                        // Update the distance and heuristic for the new path
                         queue.add(new PathAndDistance(currentData.getDistance() + edge.getWeight(), updatedPath, heuristic[edge.getTo()]));
                     }
                 }
             }
         }
         return (solutionFound) ? currentData : new PathAndDistance(-1, null, -1);
-        // Out of while loop, if there is a solution, the current Data stores the optimal path, and
-        // its distance
     }
 }


Test patch:
diff --git a/src/test/java/com/thealgorithms/datastructures/graphs/AStarTest.java b/src/test/java/com/thealgorithms/datastructures/graphs/AStarTest.java
new file mode 100644
index 00000000..dce5a6ed
--- /dev/null
+++ b/src/test/java/com/thealgorithms/datastructures/graphs/AStarTest.java
@@ -0,0 +1,46 @@
+package com.thealgorithms.datastructures.graphs;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+public class AStarTest {
+
+    private AStar.Graph graph;
+    private int[] heuristic;
+
+    @BeforeEach
+    public void setUp() {
+        // Initialize graph and heuristic values for testing
+        graph = new AStar.Graph(5);
+        ArrayList<Integer> graphData = new ArrayList<>(Arrays.asList(0, 1, 1, null, 0, 2, 2, null, 1, 3, 1, null, 2, 3, 1, null, 3, 4, 1, null));
+        AStar.initializeGraph(graph, graphData);
+
+        heuristic = new int[] {5, 4, 3, 2, 0}; // Heuristic values for each node
+    }
+
+    @Test
+    public void testAStarFindsPath() {
+        AStar.PathAndDistance result = AStar.aStar(0, 4, graph, heuristic);
+        assertEquals(3, result.getDistance(), "Expected distance from 0 to 4 is 3");
+        assertEquals(Arrays.asList(0, 1, 3, 4), result.getPath(), "Expected path from 0 to 4");
+    }
+
+    @Test
+    public void testAStarPathNotFound() {
+        AStar.PathAndDistance result = AStar.aStar(0, 5, graph, heuristic); // Node 5 does not exist
+        assertEquals(-1, result.getDistance(), "Expected distance when path not found is -1");
+        assertNull(result.getPath(), "Expected path should be null when no path exists");
+    }
+
+    @Test
+    public void testAStarSameNode() {
+        AStar.PathAndDistance result = AStar.aStar(0, 0, graph, heuristic);
+        assertEquals(0, result.getDistance(), "Expected distance from 0 to 0 is 0");
+        assertEquals(Arrays.asList(0), result.getPath(), "Expected path should only contain the start node");
+    }
+}