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
File renamed without changes.
75 changes: 75 additions & 0 deletions .gemini.md
Original file line number Diff line number Diff line change
@@ -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/<subpackage>:<ClassName>
```
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/<subpackage>:all
```

- **Run a specific test class:**
```bash
bazel test //src/test/java/com/williamfiset/algorithms/<subpackage>:<TestClassName>
```

### 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/<path>/<File>.java
java -cp classes com.williamfiset.algorithms.<package>.<ClassName>
```

## 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<List<Edge>>` or `List<List<Integer>>`.
- **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.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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)
Original file line number Diff line number Diff line change
@@ -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
*/
Expand All @@ -14,12 +23,16 @@ public class IntQueue implements Queue<Integer> {
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
Expand All @@ -37,7 +50,6 @@ public Integer peek() {
if (isEmpty()) {
throw new RuntimeException("Queue is empty");
}
front = front % data.length;
return data[front];
}

Expand All @@ -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!
Expand All @@ -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
Expand Down Expand Up @@ -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<Integer> 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<Integer> arrayDeque = new java.util.ArrayDeque<>();
// java.util.ArrayDeque <Integer> 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;
}
}
Original file line number Diff line number Diff line change
@@ -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 {

Expand All @@ -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() {
Expand Down Expand Up @@ -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();
}
}
Loading