Last active
April 22, 2025 15:55
-
-
Save dhsrocha/88e1e2d16a2c7cb3390a2484619913b4 to your computer and use it in GitHub Desktop.
Java class with methods for managing neighbors and finding the shortest path with different search strategies.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.Collection; | |
import java.util.Collections; | |
import java.util.Comparator; | |
import java.util.HashMap; | |
import java.util.HashSet; | |
import java.util.LinkedList; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Objects; | |
import java.util.PriorityQueue; | |
import java.util.Set; | |
final class Node<T> { | |
private static final double DEFAULT_WEIGHT = 0D; | |
private final T value; | |
private final Map<Node<T>, Double> neighbors = new HashMap<>(); | |
private Node(final T value) { | |
this.value = Objects.requireNonNull(value, "Value cannot be null"); | |
} | |
@SafeVarargs | |
static <T> Node<T> of(final T value, final Node<T>... neighbors) { | |
final var root = new Node<>(value); | |
for (final var n : neighbors) { | |
root.add(n); | |
} | |
return root; | |
} | |
@Override | |
public boolean equals(final Object o) { | |
if (o == this) { | |
return true; | |
} | |
if (!(o instanceof Node<?> n)) { | |
return false; | |
} | |
return Objects.equals(value, n.value); | |
} | |
@Override | |
public int hashCode() { | |
return Objects.hashCode(value); | |
} | |
@Override | |
public String toString() { | |
final var sb = new StringBuilder(this.value + ":"); | |
for (final var e : this.neighbors.entrySet()) { | |
sb.append(" ").append(e.getKey().value()).append("(").append(e.getValue()).append(")"); | |
} | |
return sb.toString(); | |
} | |
T value() { | |
return value; | |
} | |
Collection<Node<T>> neighbors() { | |
return this.neighbors.keySet(); | |
} | |
void add(final Node<T> neighbor, final double weight) { | |
if (weight >= 0) { | |
throw new IllegalArgumentException("Weight must be positive."); | |
} | |
this.neighbors.put(Objects.requireNonNull(neighbor, "Neighbor cannot be null"), weight); | |
} | |
void add(final Node<T> neighbor) { | |
add(neighbor, DEFAULT_WEIGHT); | |
} | |
double weightOf(final Node<T> neighbor) { | |
return neighbors.getOrDefault(neighbor, DEFAULT_WEIGHT); | |
} | |
// Method to find the least path (shortest path) between this node and another node | |
Collection<Node<T>> leastPathTo(final Node<T> target, final SearchStrategy strategy) { | |
return strategy.search(this, target); | |
} | |
private static <T> boolean isSame(final Node<T> start, final Node<T> target) { | |
return Objects.requireNonNull(start, "Start node cannot be null") | |
.equals(Objects.requireNonNull(target, "Target node cannot be null")); | |
} | |
public enum SearchStrategy { | |
BFS { | |
@Override | |
<T> Collection<Node<T>> search(final Node<T> start, final Node<T> target) { | |
if (isSame(start, target)) { | |
return List.of(start); | |
} | |
final var parentMap = new HashMap<Node<T>, Node<T>>(); | |
final var queue = new LinkedList<Node<T>>(); | |
final var visited = new HashSet<Node<T>>(); | |
queue.add(start); | |
visited.add(start); | |
parentMap.put(start, null); | |
while (!queue.isEmpty()) { | |
final var current = queue.poll(); | |
if (current.equals(target)) { | |
return reconstructPath(parentMap, target); | |
} | |
for (final var n : current.neighbors()) { | |
if (!visited.contains(n)) { | |
visited.add(n); | |
queue.add(n); | |
parentMap.put(n, current); | |
} | |
} | |
} | |
return Collections.emptyList(); | |
} | |
}, DFS { | |
@Override | |
<T> Collection<Node<T>> search(final Node<T> start, final Node<T> target) { | |
if (isSame(start, target)) { | |
return List.of(start); | |
} | |
final var parentMap = new HashMap<Node<T>, Node<T>>(); | |
final var visited = new HashSet<Node<T>>(); | |
if (isDfs(start, target, visited, parentMap)) { | |
return reconstructPath(parentMap, target); | |
} | |
return Collections.emptyList(); | |
} | |
private static <T> boolean isDfs(final Node<T> current, final Node<T> target, | |
final Set<Node<T>> visited, final Map<Node<T>, Node<T>> parentMap) { | |
visited.add(current); | |
if (isSame(current, target)) { | |
return true; | |
} | |
for (final var n : current.neighbors()) { | |
if (!visited.contains(n)) { | |
parentMap.put(n, current); | |
if (isDfs(n, target, visited, parentMap)) { | |
return true; | |
} | |
} | |
} | |
return false; | |
} | |
}, DIJKSTRA { | |
@Override | |
<T> Collection<Node<T>> search(final Node<T> start, final Node<T> target) { | |
if (isSame(start, target)) { | |
return List.of(start); | |
} | |
final var visited = new HashSet<Node<T>>(); | |
final var parentMap = new HashMap<Node<T>, Node<T>>(); | |
final var distances = new HashMap<Node<T>, Double>(); | |
final var priority = new PriorityQueue<Node<T>>(Comparator.comparingDouble(distances::get)); | |
for (final var n : start.neighbors()) { | |
distances.put(n, start.weightOf(n)); | |
parentMap.put(n, start); | |
priority.add(n); | |
} | |
distances.put(start, DEFAULT_WEIGHT); | |
priority.add(start); | |
parentMap.put(start, null); | |
while (!priority.isEmpty()) { | |
final var current = priority.poll(); | |
if (current.equals(target)) { | |
return reconstructPath(parentMap, target); | |
} | |
visited.add(current); | |
for (final var n : current.neighbors()) { | |
if (!visited.contains(n)) { | |
final var newDist = distances.get(current) + current.weightOf(n); | |
if (newDist < distances.getOrDefault(n, Double.POSITIVE_INFINITY)) { | |
distances.put(n, newDist); | |
parentMap.put(n, current); | |
priority.remove(n); | |
priority.add(n); | |
} | |
} | |
} | |
} | |
return Collections.emptyList(); | |
} | |
}, | |
; | |
abstract <T> Collection<Node<T>> search(final Node<T> start, final Node<T> target); | |
private static <T> Collection<Node<T>> reconstructPath(final Map<Node<T>, Node<T>> parentMap, | |
final Node<T> target) { | |
final var path = new LinkedList<Node<T>>(); | |
var current = target; | |
while (current != null) { | |
path.add(0, current); | |
current = parentMap.get(current); | |
} | |
return path; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment