Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Build System

This project uses **Bazel** as its build system (requires JDK 8+). Dependencies are managed via Maven through `rules_jvm_external` and declared in `MODULE.bazel`.

## Commands

**Run all tests:**
```bash
bazel test //src/test/...
```

**Run tests for a specific package:**
```bash
bazel test //src/test/java/com/williamfiset/algorithms/graphtheory:all
bazel test //src/test/java/com/williamfiset/algorithms/sorting:all
```

**Run a single test class:**
```bash
bazel test //src/test/java/com/williamfiset/algorithms/graphtheory:BoruvkasTest
```

**Run a specific algorithm (with `main` method):**
```bash
bazel run //src/main/java/com/williamfiset/algorithms/graphtheory:BinarySearch
bazel run //src/main/java/com/williamfiset/algorithms/search:BinarySearch
```

**Alternative (without Bazel):**
```bash
mkdir classes
javac -sourcepath src/main/java -d classes src/main/java/com/williamfiset/algorithms/search/BinarySearch.java
java -cp classes com.williamfiset.algorithms.search.BinarySearch
```

## Project Structure

```
src/
main/java/com/williamfiset/algorithms/
datastructures/ # Data structure implementations
dp/ # Dynamic programming algorithms + examples/
geometry/ # Computational geometry
graphtheory/ # Graph algorithms
networkflow/ # Max flow, min cost flow, bipartite matching
treealgorithms/ # Tree-specific algorithms (LCA, rooting, isomorphism)
linearalgebra/ # Matrix operations
math/ # Number theory, primes, FFT
other/ # Miscellaneous (bit manipulation, permutations, etc.)
search/ # Search algorithms
sorting/ # Sorting algorithms
strings/ # String algorithms
utils/ # Shared utilities
graphutils/ # Graph construction helpers

test/java/com/williamfiset/algorithms/
<mirrors main structure>
```

## Architecture Patterns

**BUILD files:** Every package has a Bazel `BUILD` file. The main source `BUILD` files define a `java_library` target (named after the package, e.g., `graphtheory`) plus individual `java_binary` targets per class. Test `BUILD` files define `java_test` targets per test class using JUnit 5 via `ConsoleLauncher`.

**Test framework:** Tests use JUnit 5 (Jupiter) with some legacy JUnit 4. New tests should use JUnit 5.

**Graph representation:** Most graph algorithms accept adjacency lists built with `List<List<Edge>>` or similar structures. Many solvers are implemented as classes where you construct the solver, add edges, then call a `solve()` method.

**Network flow base:** Flow algorithms in `networkflow/` share a common abstract solver pattern — `NetworkFlowSolverBase` is extended by concrete implementations (Dinic's, Ford-Fulkerson, etc.).

**When adding a new algorithm:**
1. Add the `.java` file in the appropriate `src/main/java/...` package
2. Add a `java_binary` entry in that package's `BUILD` file
3. Add a test file in the corresponding `src/test/java/...` package
4. Add a `java_test` entry in the test `BUILD` file
5. Update `README.md` with the new algorithm entry
11 changes: 11 additions & 0 deletions src/test/java/com/williamfiset/algorithms/graphtheory/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -167,5 +167,16 @@ java_test(
deps = TEST_DEPS,
)

# bazel test //src/test/java/com/williamfiset/algorithms/graphtheory:BellmanFordAdjacencyListTest
java_test(
name = "BellmanFordAdjacencyListTest",
srcs = ["BellmanFordAdjacencyListTest.java"],
main_class = "org.junit.platform.console.ConsoleLauncher",
use_testrunner = False,
args = ["--select-class=com.williamfiset.algorithms.graphtheory.BellmanFordAdjacencyListTest"],
runtime_deps = JUNIT5_RUNTIME_DEPS,
deps = TEST_DEPS,
)

# Run all tests
# bazel test //src/test/java/com/williamfiset/algorithms/graphtheory:all
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
package com.williamfiset.algorithms.graphtheory;

import static com.google.common.truth.Truth.assertThat;

import java.util.List;
import org.junit.jupiter.api.Test;

public class BellmanFordAdjacencyListTest {

// -------------------------------------------------------------------------
// Unreachable node relaxation
// -------------------------------------------------------------------------

/**
* An unreachable intermediate node must not corrupt the distance of a node
* that IS reachable via a separate path.
*
* Graph (start = 0):
* 0 --5--> 2
* 1 --(-100)--> 2 (node 1 is unreachable from 0)
*
* The edge 1→2 has a cheaper cost, but because dist[1] = +Inf the
* relaxation dist[1] + (-100) = +Inf must not update dist[2].
* Expected: dist[2] = 5, not -100 or NaN.
*/
@Test
public void unreachableNodeDoesNotPolluteCostOfReachableNeighbor() {
int V = 4;
List<BellmanFordAdjacencyList.Edge>[] g = BellmanFordAdjacencyList.createGraph(V);
BellmanFordAdjacencyList.addEdge(g, 0, 2, 5);
BellmanFordAdjacencyList.addEdge(g, 1, 2, -100); // 1 unreachable

double[] dist = BellmanFordAdjacencyList.bellmanFord(g, V, 0);

assertThat(dist[0]).isEqualTo(0.0);
assertThat(dist[1]).isPositiveInfinity();
assertThat(dist[2]).isEqualTo(5.0);
assertThat(dist[3]).isPositiveInfinity();
}

/**
* A node reachable only through an unreachable intermediary must itself
* remain unreachable (+Inf).
*
* Graph (start = 0):
* 1 --5--> 2 (node 1 is unreachable from 0)
*
* Expected: dist[1] = +Inf, dist[2] = +Inf.
*/
@Test
public void nodeReachableOnlyThroughUnreachableIntermediaryStaysUnreachable() {
int V = 3;
List<BellmanFordAdjacencyList.Edge>[] g = BellmanFordAdjacencyList.createGraph(V);
BellmanFordAdjacencyList.addEdge(g, 1, 2, 5); // 1 unreachable

double[] dist = BellmanFordAdjacencyList.bellmanFord(g, V, 0);

assertThat(dist[0]).isEqualTo(0.0);
assertThat(dist[1]).isPositiveInfinity();
assertThat(dist[2]).isPositiveInfinity();
}

/**
* An unreachable node involved in a negative cycle must not mark reachable
* nodes as -Inf during the cycle-detection pass.
*
* Graph (start = 0):
* 0 --10--> 3
* 1 --(-1)--> 2 (negative cycle: 1→2→1, but both unreachable)
* 2 --(-1)--> 1
* 2 --5--> 3
*
* Nodes 1 and 2 form a negative cycle but are unreachable from 0.
* Node 3 is reachable with cost 10 and must NOT be marked -Inf.
*/
@Test
public void unreachableNegativeCycleDoesNotTaintReachableNode() {
int V = 4;
List<BellmanFordAdjacencyList.Edge>[] g = BellmanFordAdjacencyList.createGraph(V);
BellmanFordAdjacencyList.addEdge(g, 0, 3, 10);
BellmanFordAdjacencyList.addEdge(g, 1, 2, -1); // unreachable negative cycle
BellmanFordAdjacencyList.addEdge(g, 2, 1, -1);
BellmanFordAdjacencyList.addEdge(g, 2, 3, 5); // path from cycle to node 3

double[] dist = BellmanFordAdjacencyList.bellmanFord(g, V, 0);

assertThat(dist[0]).isEqualTo(0.0);
assertThat(dist[1]).isPositiveInfinity();
assertThat(dist[2]).isPositiveInfinity();
assertThat(dist[3]).isEqualTo(10.0); // must NOT be -Inf
}

// -------------------------------------------------------------------------
// General cases
// -------------------------------------------------------------------------

@Test
public void singleNode() {
int V = 1;
List<BellmanFordAdjacencyList.Edge>[] g = BellmanFordAdjacencyList.createGraph(V);

double[] dist = BellmanFordAdjacencyList.bellmanFord(g, V, 0);

assertThat(dist[0]).isEqualTo(0.0);
}

@Test
public void twoNodesDirectEdge() {
int V = 2;
List<BellmanFordAdjacencyList.Edge>[] g = BellmanFordAdjacencyList.createGraph(V);
BellmanFordAdjacencyList.addEdge(g, 0, 1, 7);

double[] dist = BellmanFordAdjacencyList.bellmanFord(g, V, 0);

assertThat(dist[0]).isEqualTo(0.0);
assertThat(dist[1]).isEqualTo(7.0);
}

@Test
public void shortestPathChosenOverLonger() {
// Two paths from 0 to 2: 0→2 (cost 10) and 0→1→2 (cost 3+4=7)
int V = 3;
List<BellmanFordAdjacencyList.Edge>[] g = BellmanFordAdjacencyList.createGraph(V);
BellmanFordAdjacencyList.addEdge(g, 0, 2, 10);
BellmanFordAdjacencyList.addEdge(g, 0, 1, 3);
BellmanFordAdjacencyList.addEdge(g, 1, 2, 4);

double[] dist = BellmanFordAdjacencyList.bellmanFord(g, V, 0);

assertThat(dist[2]).isEqualTo(7.0);
}

@Test
public void negativeEdgeWeightWithoutCycle() {
// 0 --1--> 1 --(-2)--> 2; shortest path to 2 is -1
int V = 3;
List<BellmanFordAdjacencyList.Edge>[] g = BellmanFordAdjacencyList.createGraph(V);
BellmanFordAdjacencyList.addEdge(g, 0, 1, 1);
BellmanFordAdjacencyList.addEdge(g, 1, 2, -2);

double[] dist = BellmanFordAdjacencyList.bellmanFord(g, V, 0);

assertThat(dist[0]).isEqualTo(0.0);
assertThat(dist[1]).isEqualTo(1.0);
assertThat(dist[2]).isEqualTo(-1.0);
}

@Test
public void reachableNegativeCycleMarkedNegativeInfinity() {
// 0 --1--> 1 --1--> 2 --(-3)--> 1 (negative cycle: 1→2→1)
int V = 3;
List<BellmanFordAdjacencyList.Edge>[] g = BellmanFordAdjacencyList.createGraph(V);
BellmanFordAdjacencyList.addEdge(g, 0, 1, 1);
BellmanFordAdjacencyList.addEdge(g, 1, 2, 1);
BellmanFordAdjacencyList.addEdge(g, 2, 1, -3);

double[] dist = BellmanFordAdjacencyList.bellmanFord(g, V, 0);

assertThat(dist[0]).isEqualTo(0.0);
assertThat(dist[1]).isNegativeInfinity();
assertThat(dist[2]).isNegativeInfinity();
}

@Test
public void nodeDownstreamOfNegativeCycleMarkedNegativeInfinity() {
// Negative cycle 1→2→1, with 2→3 leading out of the cycle.
// Node 3 is downstream and must also be -Inf.
int V = 4;
List<BellmanFordAdjacencyList.Edge>[] g = BellmanFordAdjacencyList.createGraph(V);
BellmanFordAdjacencyList.addEdge(g, 0, 1, 1);
BellmanFordAdjacencyList.addEdge(g, 1, 2, 1);
BellmanFordAdjacencyList.addEdge(g, 2, 1, -3);
BellmanFordAdjacencyList.addEdge(g, 2, 3, 5);

double[] dist = BellmanFordAdjacencyList.bellmanFord(g, V, 0);

assertThat(dist[3]).isNegativeInfinity();
}

@Test
public void disconnectedGraph() {
// Nodes 2 and 3 have no path from node 0.
int V = 4;
List<BellmanFordAdjacencyList.Edge>[] g = BellmanFordAdjacencyList.createGraph(V);
BellmanFordAdjacencyList.addEdge(g, 0, 1, 3);
BellmanFordAdjacencyList.addEdge(g, 2, 3, 1); // separate component

double[] dist = BellmanFordAdjacencyList.bellmanFord(g, V, 0);

assertThat(dist[0]).isEqualTo(0.0);
assertThat(dist[1]).isEqualTo(3.0);
assertThat(dist[2]).isPositiveInfinity();
assertThat(dist[3]).isPositiveInfinity();
}

@Test
public void exampleFromMain() {
// Reproduces the graph and expected output from the main() method.
int V = 9;
List<BellmanFordAdjacencyList.Edge>[] g = BellmanFordAdjacencyList.createGraph(V);
BellmanFordAdjacencyList.addEdge(g, 0, 1, 1);
BellmanFordAdjacencyList.addEdge(g, 1, 2, 1);
BellmanFordAdjacencyList.addEdge(g, 2, 4, 1);
BellmanFordAdjacencyList.addEdge(g, 4, 3, -3);
BellmanFordAdjacencyList.addEdge(g, 3, 2, 1);
BellmanFordAdjacencyList.addEdge(g, 1, 5, 4);
BellmanFordAdjacencyList.addEdge(g, 1, 6, 4);
BellmanFordAdjacencyList.addEdge(g, 5, 6, 5);
BellmanFordAdjacencyList.addEdge(g, 6, 7, 4);
BellmanFordAdjacencyList.addEdge(g, 5, 7, 3);

double[] dist = BellmanFordAdjacencyList.bellmanFord(g, V, 0);

assertThat(dist[0]).isEqualTo(0.0);
assertThat(dist[1]).isEqualTo(1.0);
assertThat(dist[2]).isNegativeInfinity();
assertThat(dist[3]).isNegativeInfinity();
assertThat(dist[4]).isNegativeInfinity();
assertThat(dist[5]).isEqualTo(5.0);
assertThat(dist[6]).isEqualTo(5.0);
assertThat(dist[7]).isEqualTo(8.0);
assertThat(dist[8]).isPositiveInfinity();
}
}
Loading