From bdd85a8588e2852330b4c4f69a5476da184192f9 Mon Sep 17 00:00:00 2001 From: Stathis Veinoglou Date: Thu, 14 Aug 2025 11:38:07 +0300 Subject: [PATCH 1/8] Add DFS with parent-completion constraint for DAG traversal --- .../thealgorithms/graph/GraphTraversal.java | 154 ++++++++++++++++++ .../graph/GraphTraversalTest.java | 87 ++++++++++ 2 files changed, 241 insertions(+) create mode 100644 src/main/java/com/thealgorithms/graph/GraphTraversal.java create mode 100644 src/test/java/com/thealgorithms/graph/GraphTraversalTest.java diff --git a/src/main/java/com/thealgorithms/graph/GraphTraversal.java b/src/main/java/com/thealgorithms/graph/GraphTraversal.java new file mode 100644 index 000000000000..25793315b951 --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/GraphTraversal.java @@ -0,0 +1,154 @@ +package com.thealgorithms.graph; + +import java.util.*; + +/** + * Graph traversals and utilities. + * + *

This class includes a DFS variant that visits a successor only when + * all of its predecessors have already been visited. For each step, + * it emits an event either for a visit (with an increasing order) or for a skip + * explaining that not all parents were visited yet.

+ * + *

Input is an adjacency list of successors. If a predecessor map is not + * provided, it is derived once from the successors map.

+ */ +public final class GraphTraversal { + + private GraphTraversal() { + // utility class + } + + /** An event emitted by the traversal: either a VISIT with an order, or a SKIP with a note. */ + public static final class TraversalEvent { + private final T node; + private final Integer order; // non-null for visit, null for skip + private final String note; // non-null for skip, null for visit + + private TraversalEvent(T node, Integer order, String note) { + this.node = node; + this.order = order; + this.note = note; + } + + /** A visit event with an increasing order (0,1,2,...) */ + public static TraversalEvent visit(T node, int order) { + return new TraversalEvent<>(Objects.requireNonNull(node), order, null); + } + + /** A skip event with an explanatory note (e.g., not all parents visited yet). */ + public static TraversalEvent skip(T node, String note) { + return new TraversalEvent<>(Objects.requireNonNull(node), null, Objects.requireNonNull(note)); + } + + public boolean isVisit() { return order != null; } + public boolean isSkip() { return order == null; } + public T node() { return node; } + public Integer order() { return order; } + public String note() { return note; } + + @Override public String toString() { + return isVisit() ? "VISIT(" + node + ", order=" + order + ")" : "SKIP(" + node + ", " + note + ")"; + } + } + + /** + * DFS (recursive) that records the order of first visit starting at {@code start}, + * but only recurses to a child when all its predecessors have been visited. + * If a child is encountered early (some parent unvisited), a SKIP event is recorded. + * + *

Equivalent idea to the Python pseudo in the user's description (with successors and predecessors), + * but implemented in Java and returning a sequence of {@link TraversalEvent}s.

+ * + * @param successors adjacency list: for each node, its outgoing neighbors + * @param start start node + * @return immutable list of traversal events (VISITs with monotonically increasing order and SKIPs with messages) + * @throws IllegalArgumentException if {@code successors} is null + */ + public static List> dfsRecursiveOrder(Map> successors, T start) { + if (successors == null) { + throw new IllegalArgumentException("successors must not be null"); + } + // derive predecessors once + Map> predecessors = derivePredecessors(successors); + return dfsRecursiveOrder(successors, predecessors, start); + } + + /** + * Same as {@link #dfsRecursiveOrder(Map, Object)} but with an explicit predecessors map. + */ + public static List> dfsRecursiveOrder( + Map> successors, Map> predecessors, T start) { + + if (successors == null || predecessors == null) { + throw new IllegalArgumentException("successors and predecessors must not be null"); + } + if (start == null) { + return List.of(); + } + if (!successors.containsKey(start) && !appearsAnywhere(successors, start)) { + return List.of(); // start not present in graph + } + + List> events = new ArrayList<>(); + Set visited = new HashSet<>(); + int[] order = {0}; + dfs(start, successors, predecessors, visited, order, events); + return Collections.unmodifiableList(events); + } + + private static void dfs( + T u, + Map> succ, + Map> pred, + Set visited, + int[] order, + List> out) { + + if (!visited.add(u)) { + return; // already visited + } + out.add(TraversalEvent.visit(u, order[0]++)); // record visit and increment + + for (T v : succ.getOrDefault(u, List.of())) { + if (visited.contains(v)) { + continue; + } + if (allParentsVisited(v, visited, pred)) { + dfs(v, succ, pred, visited, order, out); + } else { + out.add(TraversalEvent.skip(v, "⛔ Skipping " + v + ": not all parents are visited yet.")); + // do not mark visited; it may be visited later from another parent + } + } + } + + private static boolean allParentsVisited(T node, Set visited, Map> pred) { + for (T p : pred.getOrDefault(node, List.of())) { + if (!visited.contains(p)) { + return false; + } + } + return true; + } + + private static boolean appearsAnywhere(Map> succ, T node) { + if (succ.containsKey(node)) return true; + for (List nbrs : succ.values()) { + if (nbrs != null && nbrs.contains(node)) return true; + } + return false; + } + + private static Map> derivePredecessors(Map> succ) { + Map> pred = new HashMap<>(); + // ensure keys exist for all nodes appearing anywhere + for (Map.Entry> e : succ.entrySet()) { + pred.computeIfAbsent(e.getKey(), k -> new ArrayList<>()); + for (T v : e.getValue()) { + pred.computeIfAbsent(v, k -> new ArrayList<>()).add(e.getKey()); + } + } + return pred; + } +} diff --git a/src/test/java/com/thealgorithms/graph/GraphTraversalTest.java b/src/test/java/com/thealgorithms/graph/GraphTraversalTest.java new file mode 100644 index 000000000000..b2fe86914b74 --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/GraphTraversalTest.java @@ -0,0 +1,87 @@ +package com.thealgorithms.graph; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import com.thealgorithms.graph.GraphTraversal.TraversalEvent; +import java.util.*; +import org.junit.jupiter.api.Test; + +class GraphTraversalTest { + + // A -> B, A -> C, B -> D, C -> D (classic diamond) + private static Map> diamond() { + Map> g = new LinkedHashMap<>(); + g.put("A", List.of("B", "C")); + g.put("B", List.of("D")); + g.put("C", List.of("D")); + g.put("D", List.of()); + return g; + } + + @Test + void dfsRecursiveOrder_emitsSkipUntilAllParentsVisited() { + List> events = GraphTraversal.dfsRecursiveOrder(diamond(), "A"); + + // Expect visits in order and a skip for first time we meet D (via B) before C is visited. + var visits = events.stream().filter(TraversalEvent::isVisit).toList(); + var skips = events.stream().filter(TraversalEvent::isSkip).toList(); + + // Visits should be A(0), B(1), C(2), D(3) in some deterministic order given adjacency + assertThat(visits).hasSize(4); + assertThat(visits.get(0).node()).isEqualTo("A"); + assertThat(visits.get(0).order()).isEqualTo(0); + assertThat(visits.get(1).node()).isEqualTo("B"); + assertThat(visits.get(1).order()).isEqualTo(1); + assertThat(visits.get(2).node()).isEqualTo("C"); + assertThat(visits.get(2).order()).isEqualTo(2); + assertThat(visits.get(3).node()).isEqualTo("D"); + assertThat(visits.get(3).order()).isEqualTo(3); + + // One skip when we first encountered D from B (before C was visited) + assertThat(skips).hasSize(1); + assertThat(skips.get(0).node()).isEqualTo("D"); + assertThat(skips.get(0).note()).contains("not all parents"); + } + + @Test + void returnsEmptyWhenStartNotInGraph() { + Map> g = Map.of(1, List.of(2), 2, List.of(1)); + assertThat(GraphTraversal.dfsRecursiveOrder(g, 99)).isEmpty(); + } + + @Test + void nullSuccessorsThrows() { + assertThrows(IllegalArgumentException.class, () -> GraphTraversal.dfsRecursiveOrder(null, "A")); + } + + @Test + void worksWithExplicitPredecessors() { + Map> succ = new HashMap<>(); + succ.put(10, List.of(20)); + succ.put(20, List.of(30)); + succ.put(30, List.of()); + + Map> pred = new HashMap<>(); + pred.put(10, List.of()); + pred.put(20, List.of(10)); + pred.put(30, List.of(20)); + + var events = GraphTraversal.dfsRecursiveOrder(succ, pred, 10); + var visitNodes = events.stream().filter(TraversalEvent::isVisit).map(TraversalEvent::node).toList(); + assertThat(visitNodes).containsExactly(10, 20, 30); + } + + @Test + void cycleProducesSkipsButNoInfiniteRecursion() { + Map> succ = new LinkedHashMap<>(); + succ.put("X", List.of("Y")); + succ.put("Y", List.of("X")); // 2-cycle + + var events = GraphTraversal.dfsRecursiveOrder(succ, "X"); + // Only X is visited; encountering Y from X causes skip because Y's parent X is visited, + // but when recursing to Y we'd hit back to X (already visited) and stop; no infinite loop. + assertThat(events.stream().anyMatch(TraversalEvent::isVisit)).isTrue(); + assertThat(events.stream().filter(TraversalEvent::isVisit).map(TraversalEvent::node)).contains("X"); + } +} From 7193d96ceee8383d488fbbbbd0a2a57d3aaf2126 Mon Sep 17 00:00:00 2001 From: Stathis Veinoglou Date: Thu, 14 Aug 2025 11:39:38 +0300 Subject: [PATCH 2/8] warning in PartitionProblem.java affecting tests --- .../com/thealgorithms/dynamicprogramming/PartitionProblem.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/PartitionProblem.java b/src/main/java/com/thealgorithms/dynamicprogramming/PartitionProblem.java index 49c4a0a3a008..e1cdbf094d24 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/PartitionProblem.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/PartitionProblem.java @@ -1,4 +1,4 @@ -/** +/* * @author Md Asif Joardar * * Description: The partition problem is a classic problem in computer science From aa9c093a4642d890cf9bba43cc8f4935e1160934 Mon Sep 17 00:00:00 2001 From: Stathis Veinoglou Date: Thu, 14 Aug 2025 11:54:26 +0300 Subject: [PATCH 3/8] added clang-format and updated javadoc --- .../thealgorithms/graph/GraphTraversal.java | 58 +++++++++++-------- .../graph/GraphTraversalTest.java | 2 +- 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/src/main/java/com/thealgorithms/graph/GraphTraversal.java b/src/main/java/com/thealgorithms/graph/GraphTraversal.java index 25793315b951..077319b58dc7 100644 --- a/src/main/java/com/thealgorithms/graph/GraphTraversal.java +++ b/src/main/java/com/thealgorithms/graph/GraphTraversal.java @@ -3,16 +3,20 @@ import java.util.*; /** - * Graph traversals and utilities. - * - *

This class includes a DFS variant that visits a successor only when - * all of its predecessors have already been visited. For each step, - * it emits an event either for a visit (with an increasing order) or for a skip - * explaining that not all parents were visited yet.

- * - *

Input is an adjacency list of successors. If a predecessor map is not - * provided, it is derived once from the successors map.

+ * DFS that visits a successor only when all its predecessors are already visited, + * emitting VISIT and SKIP events. + *

+ * This class includes a DFS variant that visits a successor only when all of its + * predecessors have already been visited + *

+ *

Related reading: + *

+ *

*/ + public final class GraphTraversal { private GraphTraversal() { @@ -23,7 +27,7 @@ private GraphTraversal() { public static final class TraversalEvent { private final T node; private final Integer order; // non-null for visit, null for skip - private final String note; // non-null for skip, null for visit + private final String note; // non-null for skip, null for visit private TraversalEvent(T node, Integer order, String note) { this.node = node; @@ -41,13 +45,24 @@ public static TraversalEvent skip(T node, String note) { return new TraversalEvent<>(Objects.requireNonNull(node), null, Objects.requireNonNull(note)); } - public boolean isVisit() { return order != null; } - public boolean isSkip() { return order == null; } - public T node() { return node; } - public Integer order() { return order; } - public String note() { return note; } + public boolean isVisit() { + return order != null; + } + public boolean isSkip() { + return order == null; + } + public T node() { + return node; + } + public Integer order() { + return order; + } + public String note() { + return note; + } - @Override public String toString() { + @Override + public String toString() { return isVisit() ? "VISIT(" + node + ", order=" + order + ")" : "SKIP(" + node + ", " + note + ")"; } } @@ -77,8 +92,7 @@ public static List> dfsRecursiveOrder(Map> succ /** * Same as {@link #dfsRecursiveOrder(Map, Object)} but with an explicit predecessors map. */ - public static List> dfsRecursiveOrder( - Map> successors, Map> predecessors, T start) { + public static List> dfsRecursiveOrder(Map> successors, Map> predecessors, T start) { if (successors == null || predecessors == null) { throw new IllegalArgumentException("successors and predecessors must not be null"); @@ -97,13 +111,7 @@ public static List> dfsRecursiveOrder( return Collections.unmodifiableList(events); } - private static void dfs( - T u, - Map> succ, - Map> pred, - Set visited, - int[] order, - List> out) { + private static void dfs(T u, Map> succ, Map> pred, Set visited, int[] order, List> out) { if (!visited.add(u)) { return; // already visited diff --git a/src/test/java/com/thealgorithms/graph/GraphTraversalTest.java b/src/test/java/com/thealgorithms/graph/GraphTraversalTest.java index b2fe86914b74..185543bf5202 100644 --- a/src/test/java/com/thealgorithms/graph/GraphTraversalTest.java +++ b/src/test/java/com/thealgorithms/graph/GraphTraversalTest.java @@ -25,7 +25,7 @@ void dfsRecursiveOrder_emitsSkipUntilAllParentsVisited() { // Expect visits in order and a skip for first time we meet D (via B) before C is visited. var visits = events.stream().filter(TraversalEvent::isVisit).toList(); - var skips = events.stream().filter(TraversalEvent::isSkip).toList(); + var skips = events.stream().filter(TraversalEvent::isSkip).toList(); // Visits should be A(0), B(1), C(2), D(3) in some deterministic order given adjacency assertThat(visits).hasSize(4); From cfdad197d81d07a0d0f7e92e8cd9310564b8ef41 Mon Sep 17 00:00:00 2001 From: Stathis Veinoglou Date: Thu, 14 Aug 2025 12:08:25 +0300 Subject: [PATCH 4/8] optimized imports and rechecked camelCase format in tests --- .../java/com/thealgorithms/graph/GraphTraversalTest.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/test/java/com/thealgorithms/graph/GraphTraversalTest.java b/src/test/java/com/thealgorithms/graph/GraphTraversalTest.java index 185543bf5202..c702f7bd89d4 100644 --- a/src/test/java/com/thealgorithms/graph/GraphTraversalTest.java +++ b/src/test/java/com/thealgorithms/graph/GraphTraversalTest.java @@ -4,7 +4,10 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import com.thealgorithms.graph.GraphTraversal.TraversalEvent; -import java.util.*; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import org.junit.jupiter.api.Test; class GraphTraversalTest { @@ -20,7 +23,7 @@ private static Map> diamond() { } @Test - void dfsRecursiveOrder_emitsSkipUntilAllParentsVisited() { + void dfsRecursiveOrderEmitsSkipUntilAllParentsVisited() { List> events = GraphTraversal.dfsRecursiveOrder(diamond(), "A"); // Expect visits in order and a skip for first time we meet D (via B) before C is visited. From 87d208e0b9a2b1d5e8b12e77ac0e99cdf21b090f Mon Sep 17 00:00:00 2001 From: Stathis Veinoglou Date: Thu, 14 Aug 2025 12:18:01 +0300 Subject: [PATCH 5/8] removed .* import and made small visual change --- .../com/thealgorithms/graph/GraphTraversal.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/thealgorithms/graph/GraphTraversal.java b/src/main/java/com/thealgorithms/graph/GraphTraversal.java index 077319b58dc7..660cb5212fd6 100644 --- a/src/main/java/com/thealgorithms/graph/GraphTraversal.java +++ b/src/main/java/com/thealgorithms/graph/GraphTraversal.java @@ -1,6 +1,13 @@ package com.thealgorithms.graph; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; /** * DFS that visits a successor only when all its predecessors are already visited, @@ -141,7 +148,9 @@ private static boolean allParentsVisited(T node, Set visited, Map boolean appearsAnywhere(Map> succ, T node) { - if (succ.containsKey(node)) return true; + if (succ.containsKey(node)) { + return true; + } for (List nbrs : succ.values()) { if (nbrs != null && nbrs.contains(node)) return true; } From 0a990c60fbee9497bb9cf92d9b7cf6d543560ebf Mon Sep 17 00:00:00 2001 From: Stathis Veinoglou Date: Thu, 14 Aug 2025 12:22:42 +0300 Subject: [PATCH 6/8] replaced a inline return with correct {} block --- src/main/java/com/thealgorithms/graph/GraphTraversal.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/thealgorithms/graph/GraphTraversal.java b/src/main/java/com/thealgorithms/graph/GraphTraversal.java index 660cb5212fd6..7f35c4c036e2 100644 --- a/src/main/java/com/thealgorithms/graph/GraphTraversal.java +++ b/src/main/java/com/thealgorithms/graph/GraphTraversal.java @@ -152,7 +152,9 @@ private static boolean appearsAnywhere(Map> succ, T node) { return true; } for (List nbrs : succ.values()) { - if (nbrs != null && nbrs.contains(node)) return true; + if (nbrs != null && nbrs.contains(node)) { + return true; + } } return false; } From b113746feb371183d934cd9bd40e49487de4c949 Mon Sep 17 00:00:00 2001 From: StathisVeinoglou Date: Fri, 15 Aug 2025 00:30:04 +0300 Subject: [PATCH 7/8] Removed changed in PartitionProblem.java, Renamed class name to be straightforward about the implementation.Added full names instead of shortcuts, and included record. --- .../dynamicprogramming/PartitionProblem.java | 2 +- ...al.java => PredecessorConstrainedDfs.java} | 83 +++++++++---------- ...ava => PredecessorConstrainedDfsTest.java} | 38 ++++----- 3 files changed, 58 insertions(+), 65 deletions(-) rename src/main/java/com/thealgorithms/graph/{GraphTraversal.java => PredecessorConstrainedDfs.java} (66%) rename src/test/java/com/thealgorithms/graph/{GraphTraversalTest.java => PredecessorConstrainedDfsTest.java} (67%) diff --git a/src/main/java/com/thealgorithms/dynamicprogramming/PartitionProblem.java b/src/main/java/com/thealgorithms/dynamicprogramming/PartitionProblem.java index e1cdbf094d24..49c4a0a3a008 100644 --- a/src/main/java/com/thealgorithms/dynamicprogramming/PartitionProblem.java +++ b/src/main/java/com/thealgorithms/dynamicprogramming/PartitionProblem.java @@ -1,4 +1,4 @@ -/* +/** * @author Md Asif Joardar * * Description: The partition problem is a classic problem in computer science diff --git a/src/main/java/com/thealgorithms/graph/GraphTraversal.java b/src/main/java/com/thealgorithms/graph/PredecessorConstrainedDfs.java similarity index 66% rename from src/main/java/com/thealgorithms/graph/GraphTraversal.java rename to src/main/java/com/thealgorithms/graph/PredecessorConstrainedDfs.java index 7f35c4c036e2..0fa01295c00e 100644 --- a/src/main/java/com/thealgorithms/graph/GraphTraversal.java +++ b/src/main/java/com/thealgorithms/graph/PredecessorConstrainedDfs.java @@ -24,53 +24,46 @@ *

*/ -public final class GraphTraversal { +public final class PredecessorConstrainedDfs { - private GraphTraversal() { + private PredecessorConstrainedDfs() { // utility class } /** An event emitted by the traversal: either a VISIT with an order, or a SKIP with a note. */ - public static final class TraversalEvent { - private final T node; - private final Integer order; // non-null for visit, null for skip - private final String note; // non-null for skip, null for visit - - private TraversalEvent(T node, Integer order, String note) { - this.node = node; - this.order = order; - this.note = note; + public record TraversalEvent( + T node, + Integer order, // non-null for visit, null for skip + String note // non-null for skip, null for visit + ) { + public TraversalEvent { + Objects.requireNonNull(node); + // order and note can be null based on event type } /** A visit event with an increasing order (0,1,2,...) */ public static TraversalEvent visit(T node, int order) { - return new TraversalEvent<>(Objects.requireNonNull(node), order, null); + return new TraversalEvent<>(node, order, null); } /** A skip event with an explanatory note (e.g., not all parents visited yet). */ public static TraversalEvent skip(T node, String note) { - return new TraversalEvent<>(Objects.requireNonNull(node), null, Objects.requireNonNull(note)); + return new TraversalEvent<>(node, null, Objects.requireNonNull(note)); } public boolean isVisit() { return order != null; } + public boolean isSkip() { return order == null; } - public T node() { - return node; - } - public Integer order() { - return order; - } - public String note() { - return note; - } @Override public String toString() { - return isVisit() ? "VISIT(" + node + ", order=" + order + ")" : "SKIP(" + node + ", " + note + ")"; + return isVisit() + ? "VISIT(" + node + ", order=" + order + ")" + : "SKIP(" + node + ", " + note + ")"; } } @@ -118,56 +111,56 @@ public static List> dfsRecursiveOrder(Map> succ return Collections.unmodifiableList(events); } - private static void dfs(T u, Map> succ, Map> pred, Set visited, int[] order, List> out) { + private static void dfs(T currentNode, Map> successors, Map> predecessors, Set visited, int[] order, List> result) { - if (!visited.add(u)) { + if (!visited.add(currentNode)) { return; // already visited } - out.add(TraversalEvent.visit(u, order[0]++)); // record visit and increment + result.add(TraversalEvent.visit(currentNode, order[0]++)); // record visit and increment - for (T v : succ.getOrDefault(u, List.of())) { - if (visited.contains(v)) { + for (T childNode : successors.getOrDefault(currentNode, List.of())) { + if (visited.contains(childNode)) { continue; } - if (allParentsVisited(v, visited, pred)) { - dfs(v, succ, pred, visited, order, out); + if (allParentsVisited(childNode, visited, predecessors)) { + dfs(childNode, successors, predecessors, visited, order, result); } else { - out.add(TraversalEvent.skip(v, "⛔ Skipping " + v + ": not all parents are visited yet.")); + result.add(TraversalEvent.skip(childNode, "⛔ Skipping " + childNode + ": not all parents are visited yet.")); // do not mark visited; it may be visited later from another parent } } } - private static boolean allParentsVisited(T node, Set visited, Map> pred) { - for (T p : pred.getOrDefault(node, List.of())) { - if (!visited.contains(p)) { + private static boolean allParentsVisited(T node, Set visited, Map> predecessors) { + for (T parent : predecessors.getOrDefault(node, List.of())) { + if (!visited.contains(parent)) { return false; } } return true; } - private static boolean appearsAnywhere(Map> succ, T node) { - if (succ.containsKey(node)) { + private static boolean appearsAnywhere(Map> successors, T node) { + if (successors.containsKey(node)) { return true; } - for (List nbrs : succ.values()) { - if (nbrs != null && nbrs.contains(node)) { + for (List neighbours : successors.values()) { + if (neighbours != null && neighbours.contains(node)) { return true; } } return false; } - private static Map> derivePredecessors(Map> succ) { - Map> pred = new HashMap<>(); + private static Map> derivePredecessors(Map> successors) { + Map> predecessors = new HashMap<>(); // ensure keys exist for all nodes appearing anywhere - for (Map.Entry> e : succ.entrySet()) { - pred.computeIfAbsent(e.getKey(), k -> new ArrayList<>()); - for (T v : e.getValue()) { - pred.computeIfAbsent(v, k -> new ArrayList<>()).add(e.getKey()); + for (Map.Entry> entry : successors.entrySet()) { + predecessors.computeIfAbsent(entry.getKey(), key -> new ArrayList<>()); + for (T childNode : entry.getValue()) { + predecessors.computeIfAbsent(childNode, key -> new ArrayList<>()).add(entry.getKey()); } } - return pred; + return predecessors; } } diff --git a/src/test/java/com/thealgorithms/graph/GraphTraversalTest.java b/src/test/java/com/thealgorithms/graph/PredecessorConstrainedDfsTest.java similarity index 67% rename from src/test/java/com/thealgorithms/graph/GraphTraversalTest.java rename to src/test/java/com/thealgorithms/graph/PredecessorConstrainedDfsTest.java index c702f7bd89d4..e2c6d468768f 100644 --- a/src/test/java/com/thealgorithms/graph/GraphTraversalTest.java +++ b/src/test/java/com/thealgorithms/graph/PredecessorConstrainedDfsTest.java @@ -3,14 +3,14 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import com.thealgorithms.graph.GraphTraversal.TraversalEvent; +import com.thealgorithms.graph.PredecessorConstrainedDfs.TraversalEvent; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.junit.jupiter.api.Test; -class GraphTraversalTest { +class PredecessorConstrainedDfsTest { // A -> B, A -> C, B -> D, C -> D (classic diamond) private static Map> diamond() { @@ -24,7 +24,7 @@ private static Map> diamond() { @Test void dfsRecursiveOrderEmitsSkipUntilAllParentsVisited() { - List> events = GraphTraversal.dfsRecursiveOrder(diamond(), "A"); + List> events = PredecessorConstrainedDfs.dfsRecursiveOrder(diamond(), "A"); // Expect visits in order and a skip for first time we meet D (via B) before C is visited. var visits = events.stream().filter(TraversalEvent::isVisit).toList(); @@ -49,39 +49,39 @@ void dfsRecursiveOrderEmitsSkipUntilAllParentsVisited() { @Test void returnsEmptyWhenStartNotInGraph() { - Map> g = Map.of(1, List.of(2), 2, List.of(1)); - assertThat(GraphTraversal.dfsRecursiveOrder(g, 99)).isEmpty(); + Map> graph = Map.of(1, List.of(2), 2, List.of(1)); + assertThat(PredecessorConstrainedDfs.dfsRecursiveOrder(graph, 99)).isEmpty(); } @Test void nullSuccessorsThrows() { - assertThrows(IllegalArgumentException.class, () -> GraphTraversal.dfsRecursiveOrder(null, "A")); + assertThrows(IllegalArgumentException.class, () -> PredecessorConstrainedDfs.dfsRecursiveOrder(null, "A")); } @Test void worksWithExplicitPredecessors() { - Map> succ = new HashMap<>(); - succ.put(10, List.of(20)); - succ.put(20, List.of(30)); - succ.put(30, List.of()); + Map> successors = new HashMap<>(); + successors.put(10, List.of(20)); + successors.put(20, List.of(30)); + successors.put(30, List.of()); - Map> pred = new HashMap<>(); - pred.put(10, List.of()); - pred.put(20, List.of(10)); - pred.put(30, List.of(20)); + Map> predecessors = new HashMap<>(); + predecessors.put(10, List.of()); + predecessors.put(20, List.of(10)); + predecessors.put(30, List.of(20)); - var events = GraphTraversal.dfsRecursiveOrder(succ, pred, 10); + var events = PredecessorConstrainedDfs.dfsRecursiveOrder(successors, predecessors, 10); var visitNodes = events.stream().filter(TraversalEvent::isVisit).map(TraversalEvent::node).toList(); assertThat(visitNodes).containsExactly(10, 20, 30); } @Test void cycleProducesSkipsButNoInfiniteRecursion() { - Map> succ = new LinkedHashMap<>(); - succ.put("X", List.of("Y")); - succ.put("Y", List.of("X")); // 2-cycle + Map> successors = new LinkedHashMap<>(); + successors.put("X", List.of("Y")); + successors.put("Y", List.of("X")); // 2-cycle - var events = GraphTraversal.dfsRecursiveOrder(succ, "X"); + var events = PredecessorConstrainedDfs.dfsRecursiveOrder(successors, "X"); // Only X is visited; encountering Y from X causes skip because Y's parent X is visited, // but when recursing to Y we'd hit back to X (already visited) and stop; no infinite loop. assertThat(events.stream().anyMatch(TraversalEvent::isVisit)).isTrue(); From f0fe5f76b2a56bad17c2ba51f8bb59b755372af1 Mon Sep 17 00:00:00 2001 From: StathisVeinoglou Date: Fri, 15 Aug 2025 00:36:27 +0300 Subject: [PATCH 8/8] updated for clang format --- .../graph/PredecessorConstrainedDfs.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/thealgorithms/graph/PredecessorConstrainedDfs.java b/src/main/java/com/thealgorithms/graph/PredecessorConstrainedDfs.java index 0fa01295c00e..2cf4ed23c44f 100644 --- a/src/main/java/com/thealgorithms/graph/PredecessorConstrainedDfs.java +++ b/src/main/java/com/thealgorithms/graph/PredecessorConstrainedDfs.java @@ -31,10 +31,9 @@ private PredecessorConstrainedDfs() { } /** An event emitted by the traversal: either a VISIT with an order, or a SKIP with a note. */ - public record TraversalEvent( - T node, - Integer order, // non-null for visit, null for skip - String note // non-null for skip, null for visit + public record TraversalEvent(T node, + Integer order, // non-null for visit, null for skip + String note // non-null for skip, null for visit ) { public TraversalEvent { Objects.requireNonNull(node); @@ -61,9 +60,7 @@ public boolean isSkip() { @Override public String toString() { - return isVisit() - ? "VISIT(" + node + ", order=" + order + ")" - : "SKIP(" + node + ", " + note + ")"; + return isVisit() ? "VISIT(" + node + ", order=" + order + ")" : "SKIP(" + node + ", " + note + ")"; } }