Skip to content

Commit 41f686c

Browse files
committed
Path finding classes for day 15
1 parent ab80b39 commit 41f686c

File tree

3 files changed

+253
-0
lines changed

3 files changed

+253
-0
lines changed
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
package com.sbaars.adventofcode2019.pathfinding;
2+
3+
import java.util.HashSet;
4+
import java.util.List;
5+
import java.util.Set;
6+
7+
/**
8+
* Creates nodes and neighbours from a 2d grid. Each point in the map has an
9+
* integer value that specifies the cost of crossing that point. If this value
10+
* is negative, the point is unreachable.
11+
*
12+
* If diagonal movement is allowed, the Chebyshev distance is used, else
13+
* Manhattan distance is used.
14+
*
15+
* @author Ben Ruijl
16+
*
17+
*/
18+
public class Grid2d {
19+
private final double[][] map;
20+
private final boolean allowDiagonal;
21+
22+
/**
23+
* A node in a 2d map. This is simply the coordinates of the point.
24+
*
25+
* @author Ben Ruijl
26+
*
27+
*/
28+
public class MapNode implements Node<MapNode> {
29+
private final int x, y;
30+
31+
public MapNode(int x, int y) {
32+
this.x = x;
33+
this.y = y;
34+
}
35+
36+
public double getHeuristic(MapNode goal) {
37+
if (allowDiagonal) {
38+
return Math.max(Math.abs(x - goal.x), Math.abs(y - goal.y));
39+
} else {
40+
return Math.abs(x - goal.x) + Math.abs(y - goal.y);
41+
}
42+
}
43+
44+
public double getTraversalCost(MapNode neighbour) {
45+
return 1 + map[neighbour.y][neighbour.x];
46+
}
47+
48+
public Set<MapNode> getNeighbours() {
49+
Set<MapNode> neighbours = new HashSet<MapNode>();
50+
51+
for (int i = x - 1; i <= x + 1; i++) {
52+
for (int j = y - 1; j <= y + 1; j++) {
53+
if ((i == x && j == y) || i < 0 || j < 0 || j >= map.length
54+
|| i >= map[j].length) {
55+
continue;
56+
}
57+
58+
if (!allowDiagonal &&
59+
((i < x && j < y) ||
60+
(i < x && j > y) ||
61+
(i > x && j > y) ||
62+
(i > x && j < y))) {
63+
continue;
64+
}
65+
66+
if (map[j][i] == 1) {
67+
continue;
68+
}
69+
70+
// TODO: create cache instead of recreation
71+
neighbours.add(new MapNode(i, j));
72+
}
73+
}
74+
75+
return neighbours;
76+
}
77+
78+
@Override
79+
public String toString() {
80+
return "(" + x + ", " + y + ")";
81+
}
82+
83+
@Override
84+
public int hashCode() {
85+
final int prime = 31;
86+
int result = 1;
87+
result = prime * result + getOuterType().hashCode();
88+
result = prime * result + x;
89+
result = prime * result + y;
90+
return result;
91+
}
92+
93+
@Override
94+
public boolean equals(Object obj) {
95+
if (this == obj)
96+
return true;
97+
if (obj == null)
98+
return false;
99+
if (getClass() != obj.getClass())
100+
return false;
101+
MapNode other = (MapNode) obj;
102+
if (!getOuterType().equals(other.getOuterType()))
103+
return false;
104+
if (x != other.x)
105+
return false;
106+
if (y != other.y)
107+
return false;
108+
return true;
109+
}
110+
111+
private Grid2d getOuterType() {
112+
return Grid2d.this;
113+
}
114+
115+
}
116+
117+
public Grid2d(double[][] map, boolean allowDiagonal) {
118+
this.map = map;
119+
this.allowDiagonal = allowDiagonal;
120+
}
121+
122+
public List<MapNode> findPath(int xStart, int yStart, int xGoal, int yGoal) {
123+
return PathFinding.doAStar(new MapNode(xStart, yStart), new MapNode(
124+
xGoal, yGoal));
125+
}
126+
127+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.sbaars.adventofcode2019.pathfinding;
2+
import java.util.Set;
3+
4+
/**
5+
* A node in a graph, useful for pathfinding.
6+
*
7+
* @author Ben Ruijl
8+
*
9+
* @param <T>
10+
* Actual type of the node
11+
*/
12+
public interface Node<T> {
13+
/**
14+
* The heuristic cost to move from the current node to the goal. In most
15+
* cases this is the Manhattan distance or Chebyshev distance.
16+
*
17+
* @param goal
18+
* @return
19+
*/
20+
double getHeuristic(T goal);
21+
22+
/**
23+
* The cost of moving from the current node to the neighbour. In most cases
24+
* this is just the distance between the nodes, but additional terrain cost
25+
* can be added as well (for example climbing a mountain is more expensive
26+
* than walking on a road).
27+
*
28+
* @param neighbour
29+
* Neighbour of current node
30+
* @return Traversal cost
31+
*/
32+
double getTraversalCost(T neighbour);
33+
34+
/**
35+
* Gets the set of neighbouring nodes.
36+
*
37+
* @return Neighbouring nodes
38+
*/
39+
Set<T> getNeighbours();
40+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package com.sbaars.adventofcode2019.pathfinding;
2+
import java.util.Comparator;
3+
import java.util.HashMap;
4+
import java.util.HashSet;
5+
import java.util.LinkedList;
6+
import java.util.List;
7+
import java.util.Map;
8+
import java.util.PriorityQueue;
9+
import java.util.Set;
10+
11+
/**
12+
* Helper class containing pathfinding algorithms.
13+
*
14+
* @author Ben Ruijl
15+
*
16+
*/
17+
public class PathFinding {
18+
19+
/**
20+
* A Star pathfinding. Note that the heuristic has to be monotonic:
21+
* {@code h(x) <=
22+
* d(x, y) + h(y)}.
23+
*
24+
* @param start
25+
* Starting node
26+
* @param goal
27+
* Goal node
28+
* @return Shortest path from start to goal, or null if none found
29+
*/
30+
public static <T extends Node<T>> List<T> doAStar(T start, T goal) {
31+
Set<T> closed = new HashSet<T>();
32+
Map<T, T> fromMap = new HashMap<T, T>();
33+
List<T> route = new LinkedList<T>();
34+
Map<T, Double> gScore = new HashMap<T, Double>();
35+
final Map<T, Double> fScore = new HashMap<T, Double>();
36+
PriorityQueue<T> open = new PriorityQueue<T>(11, new Comparator<T>() {
37+
38+
public int compare(T nodeA, T nodeB) {
39+
return Double.compare(fScore.get(nodeA), fScore.get(nodeB));
40+
}
41+
});
42+
43+
gScore.put(start, 0.0);
44+
fScore.put(start, start.getHeuristic(goal));
45+
open.offer(start);
46+
47+
while (!open.isEmpty()) {
48+
T current = open.poll();
49+
if (current.equals(goal)) {
50+
while (current != null) {
51+
route.add(0, current);
52+
current = fromMap.get(current);
53+
}
54+
55+
return route;
56+
}
57+
58+
closed.add(current);
59+
60+
for (T neighbour : current.getNeighbours()) {
61+
if (closed.contains(neighbour)) {
62+
continue;
63+
}
64+
65+
double tentG = gScore.get(current)
66+
+ current.getTraversalCost(neighbour);
67+
68+
boolean contains = open.contains(neighbour);
69+
if (!contains || tentG < gScore.get(neighbour)) {
70+
gScore.put(neighbour, tentG);
71+
fScore.put(neighbour, tentG + neighbour.getHeuristic(goal));
72+
73+
if (contains) {
74+
open.remove(neighbour);
75+
}
76+
77+
open.offer(neighbour);
78+
fromMap.put(neighbour, current);
79+
}
80+
}
81+
}
82+
83+
return null;
84+
}
85+
86+
}

0 commit comments

Comments
 (0)