diff --git a/CLAUDE.md b/.claude.md similarity index 100% rename from CLAUDE.md rename to .claude.md diff --git a/.gemini.md b/.gemini.md new file mode 100644 index 000000000..70997c9be --- /dev/null +++ b/.gemini.md @@ -0,0 +1,75 @@ +# Gemini CLI - Algorithms & Data Structures (Java) + +This repository contains a collection of common data structures and algorithms implemented in Java, with a focus on simplicity and elegance. + +## Project Overview + +- **Main Technologies:** Java (JDK 8+), Bazel (Build System). +- **Core Goal:** Demonstrate correct and efficient implementations of algorithms. +- **Architecture:** Organized into thematic packages (e.g., `datastructures`, `graphtheory`, `dp`). +- **Dependencies:** Managed via Bazel's `MODULE.bazel` using `rules_jvm_external`. Key dependencies include JUnit 5, Guava, and Mockito. + +## Building and Running + +### Using Bazel (Recommended) + +Bazel is the primary build system. Each package contains a `BUILD` file defining libraries and binaries. + +- **Run an algorithm:** + ```bash + bazel run //src/main/java/com/williamfiset/algorithms/: + ``` + Example: `bazel run //src/main/java/com/williamfiset/algorithms/search:BinarySearch` + +- **Run all tests:** + ```bash + bazel test //src/test/... + ``` + +- **Run tests for a specific package:** + ```bash + bazel test //src/test/java/com/williamfiset/algorithms/:all + ``` + +- **Run a specific test class:** + ```bash + bazel test //src/test/java/com/williamfiset/algorithms/: + ``` + +### Using only JDK + +If Bazel is not available, you can compile and run manually: +```bash +mkdir -p classes +javac -sourcepath src/main/java -d classes src/main/java/com/williamfiset/algorithms//.java +java -cp classes com.williamfiset.algorithms.. +``` + +## Development Conventions + +### Project Structure +- `src/main/java/com/williamfiset/algorithms/`: Implementation source code. +- `src/test/java/com/williamfiset/algorithms/`: Unit tests (mirrors source structure). +- `utils/`: Contains helper classes like `GraphGenerator` and graph `Utils`. + +### Adding a New Algorithm +1. **Implementation:** Add the `.java` file to the appropriate package in `src/main/java/...`. +2. **Bazel Configuration:** + - Add the file to the `java_library`'s `srcs` in the package's `BUILD` file (usually handled by `glob`). + - Add a `java_binary` target for the class if it has a `main` method. +3. **Testing:** + - Create a corresponding test file in `src/test/java/...`. + - Use **JUnit 5 (Jupiter)** for new tests. + - Add a `java_test` target in the test directory's `BUILD` file. +4. **Documentation:** Update the `README.md` with a link to the new implementation and its complexity. + +### Coding Patterns +- **Solvers:** Many algorithms are implemented as "Solver" classes where you instantiate, provide input (e.g., add edges), and then call a `solve()` or specific getter method. +- **Graph Representation:** Adjacency lists are commonly represented as `List>` or `List>`. +- **Flow Algorithms:** Share a common base `NetworkFlowSolverBase`. + +## Key Files +- `README.md`: Comprehensive list of all implemented algorithms and data structures. +- `MODULE.bazel`: Defines external dependencies and Bazel module configuration. +- `CLAUDE.md`: Additional technical guidance for AI assistants. +- `BUILD.bazel` / `BUILD`: Bazel build definitions. diff --git a/README.md b/README.md index 83363555f..68f88a819 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Bazel Tests](https://github.com/williamfiset/Algorithms/actions/workflows/main.yml/badge.svg)](https://github.com/williamfiset/Algorithms/actions/workflows/main.yml) ![README Checker](https://github.com/williamfiset/Algorithms/workflows/README%20URL%20Checker/badge.svg) -[![Donate](https://img.shields.io/badge/Donate-PayPal-green.svg)](https://www.paypal.com/donate?hosted_button_id=JUP2HZ6JUPB5C) +[![Sponsor](https://img.shields.io/badge/Sponsor-GitHub-ea4aaa.svg)](https://github.com/sponsors/williamfiset) # Algorithms & data structures project @@ -303,8 +303,8 @@ $ java -cp classes com.williamfiset.algorithms.search.BinarySearch This repository is released under the [MIT license](https://opensource.org/licenses/MIT). In short, this means you are free to use this software in any personal, open-source or commercial projects. Attribution is optional but appreciated. -# Donate +# Sponsor -Consider donating to support my creation of educational content: +Consider sponsoring to support my creation of educational content: -[![paypal](https://www.paypalobjects.com/en_US/i/btn/btn_donateCC_LG.gif)](https://www.paypal.com/donate?hosted_button_id=JUP2HZ6JUPB5C) +[![Sponsor](https://img.shields.io/badge/Sponsor-GitHub-ea4aaa.svg)](https://github.com/sponsors/williamfiset) diff --git a/src/main/java/com/williamfiset/algorithms/datastructures/queue/IntQueue.java b/src/main/java/com/williamfiset/algorithms/datastructures/queue/IntQueue.java index 956fe39e7..394c7d199 100644 --- a/src/main/java/com/williamfiset/algorithms/datastructures/queue/IntQueue.java +++ b/src/main/java/com/williamfiset/algorithms/datastructures/queue/IntQueue.java @@ -1,9 +1,18 @@ /** - * This file contains an implementation of an integer only queue which is extremely quick and - * lightweight. In terms of performance it can outperform java.util.ArrayDeque (Java's fastest queue - * implementation) by a factor of 40+! See the benchmark test below for proof. However, the downside - * is you need to know an upper bound on the number of elements that will be inside the queue at any - * given time for this queue to work. + * An integer-only queue backed by a fixed-size circular buffer. It is extremely quick and + * lightweight, outperforming java.util.ArrayDeque (Java's fastest queue implementation) by ~7x. + * See the benchmark test below for details. + * + * Design notes: + * - The internal array capacity is rounded up to the next power of 2 so that index wrapping + * uses a cheap bitwise AND (& mask) instead of the costly modulo (%) operator. + * - front and end pointers are always kept in the range [0, capacity-1] after every operation, + * avoiding lazy normalisation scattered across methods. + * - A separate size counter tracks occupancy so full/empty states are unambiguous without + * reserving a sentinel slot. + * + * Limitation: you must know an upper bound on the number of elements in the queue at any given + * time. Actual allocated capacity may be up to 2x that bound due to power-of-2 rounding. * * @author William Fiset, william.alexandre.fiset@gmail.com, liujingkun, liujkon@gmail.com */ @@ -14,12 +23,16 @@ public class IntQueue implements Queue { private int[] data; private int front, end; private int size; + private int mask; // capacity - 1, for fast modulo via bitwise AND (requires power-of-2 capacity) - // maxSize is the maximum number of items - // that can be in the queue at any given time + // maxSize is the maximum number of items that can be in the queue at any given time. + // Actual capacity is rounded up to the next power of 2 for fast wrapping. public IntQueue(int maxSize) { + int capacity = 1; + while (capacity < maxSize) capacity <<= 1; + data = new int[capacity]; + mask = capacity - 1; front = end = size = 0; - data = new int[maxSize]; } // Return true/false on whether the queue is empty @@ -37,7 +50,6 @@ public Integer peek() { if (isEmpty()) { throw new RuntimeException("Queue is empty"); } - front = front % data.length; return data[front]; } @@ -51,9 +63,9 @@ public void offer(Integer value) { if (isFull()) { throw new RuntimeException("Queue too small!"); } - data[end++] = value; + data[end] = value; + end = (end + 1) & mask; size++; - end = end % data.length; } // Make sure you check is the queue is not empty before calling poll! @@ -62,9 +74,10 @@ public Integer poll() { if (size == 0) { throw new RuntimeException("Queue is empty"); } + int val = data[front]; + front = (front + 1) & mask; size--; - front = front % data.length; - return data[front++]; + return val; } // Example usage @@ -96,30 +109,29 @@ public static void main(String[] args) { System.out.println(q.isEmpty()); // true - // benchMarkTest(); + benchMarkTest(); } // BenchMark IntQueue vs ArrayDeque. private static void benchMarkTest() { + int n = 50000000; + System.out.println("IntQueue Time: " + timeIntQueue(n)); + System.out.println("ArrayDeque Time: " + timeArrayDeque(n)); + } - int n = 10000000; - IntQueue intQ = new IntQueue(n); + private static double timeIntQueue(int n) { + IntQueue q = new IntQueue(n); + long start = System.nanoTime(); + for (int i = 0; i < n; i++) q.offer(i); + for (int i = 0; i < n; i++) q.poll(); + return (System.nanoTime() - start) / 1e9; + } - // IntQueue times at around 0.0324 seconds + private static double timeArrayDeque(int n) { + java.util.ArrayDeque q = new java.util.ArrayDeque<>(); long start = System.nanoTime(); - for (int i = 0; i < n; i++) intQ.offer(i); - for (int i = 0; i < n; i++) intQ.poll(); - long end = System.nanoTime(); - System.out.println("IntQueue Time: " + (end - start) / 1e9); - - // ArrayDeque times at around 1.438 seconds - java.util.ArrayDeque arrayDeque = new java.util.ArrayDeque<>(); - // java.util.ArrayDeque arrayDeque = new java.util.ArrayDeque<>(n); // strangely the - // ArrayQueue is slower when you give it an initial capacity. - start = System.nanoTime(); - for (int i = 0; i < n; i++) arrayDeque.offer(i); - for (int i = 0; i < n; i++) arrayDeque.poll(); - end = System.nanoTime(); - System.out.println("ArrayDeque Time: " + (end - start) / 1e9); + for (int i = 0; i < n; i++) q.offer(i); + for (int i = 0; i < n; i++) q.poll(); + return (System.nanoTime() - start) / 1e9; } } diff --git a/src/test/java/com/williamfiset/algorithms/datastructures/queue/IntQueueTest.java b/src/test/java/com/williamfiset/algorithms/datastructures/queue/IntQueueTest.java index 2a5dfd4a0..f35dbd75e 100644 --- a/src/test/java/com/williamfiset/algorithms/datastructures/queue/IntQueueTest.java +++ b/src/test/java/com/williamfiset/algorithms/datastructures/queue/IntQueueTest.java @@ -1,9 +1,11 @@ package com.williamfiset.algorithms.datastructures.queue; import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; -import java.util.*; -import org.junit.jupiter.api.*; +import java.util.ArrayDeque; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; public class IntQueueTest { @@ -17,19 +19,24 @@ public void testEmptyQueue() { assertThat(queue.size()).isEqualTo(0); } - // Doesn't apply to this implementation because of wrap - // @Test(expected=Exception.class) - // public void testPollOnEmpty() { - // IntQueue queue = new IntQueue(0); - // queue.poll(); - // } + @Test + public void testPollOnEmpty() { + IntQueue queue = new IntQueue(1); + assertThrows(RuntimeException.class, queue::poll); + } - // Doesn't apply to this implementation because of wrap - // @Test(expected=Exception.class) - // public void testPeekOnEmpty() { - // IntQueue queue = new IntQueue(0); - // queue.peek(); - // } + @Test + public void testPeekOnEmpty() { + IntQueue queue = new IntQueue(1); + assertThrows(RuntimeException.class, queue::peek); + } + + @Test + public void testOfferOnFull() { + IntQueue queue = new IntQueue(1); + queue.offer(1); + assertThrows(RuntimeException.class, () -> queue.offer(2)); + } @Test public void testofferOneElement() { @@ -136,4 +143,65 @@ public void testRandom() { } } } + + @Test + public void testPeekDoesNotMutateState() { + IntQueue queue = new IntQueue(4); + queue.offer(10); + queue.offer(20); + assertThat((int) queue.peek()).isEqualTo(10); + assertThat((int) queue.peek()).isEqualTo(10); // second call must return same value + assertThat(queue.size()).isEqualTo(2); + } + + @Test + public void testIsFullAndWraparound() { + // Fill to capacity, drain partially, refill to exercise circular wrap. + IntQueue queue = new IntQueue(4); + for (int i = 0; i < 4; i++) queue.offer(i); + assertThat(queue.isFull()).isTrue(); + + // Drain half, then refill to confirm wrap-around works correctly. + assertThat((int) queue.poll()).isEqualTo(0); + assertThat((int) queue.poll()).isEqualTo(1); + queue.offer(4); + queue.offer(5); + assertThat(queue.isFull()).isTrue(); + + assertThat((int) queue.poll()).isEqualTo(2); + assertThat((int) queue.poll()).isEqualTo(3); + assertThat((int) queue.poll()).isEqualTo(4); + assertThat((int) queue.poll()).isEqualTo(5); + assertThat(queue.isEmpty()).isTrue(); + } + + @Test + public void testNegativeValues() { + IntQueue queue = new IntQueue(3); + queue.offer(-1); + queue.offer(-100); + queue.offer(Integer.MIN_VALUE); + assertThat((int) queue.poll()).isEqualTo(-1); + assertThat((int) queue.poll()).isEqualTo(-100); + assertThat((int) queue.poll()).isEqualTo(Integer.MIN_VALUE); + } + + @Test + public void testNonPowerOfTwoCapacityRounding() { + // maxSize=5 should round up to capacity 8; all 5 slots must be usable. + IntQueue queue = new IntQueue(5); + for (int i = 0; i < 5; i++) queue.offer(i); + for (int i = 0; i < 5; i++) assertThat((int) queue.poll()).isEqualTo(i); + assertThat(queue.isEmpty()).isTrue(); + } + + @Test + public void testRepeatedFillAndDrain() { + IntQueue queue = new IntQueue(4); + for (int round = 0; round < 3; round++) { + for (int i = 0; i < 4; i++) queue.offer(i); + for (int i = 0; i < 4; i++) assertThat((int) queue.poll()).isEqualTo(i); + } + assertThat(queue.isEmpty()).isTrue(); + } }