diff --git a/.editorconfig b/.editorconfig
index 9ee9a38..cf71165 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -1,32 +1,25 @@
-# Top-most EditorConfig file
root = true
-# Global settings (applicable to all files unless overridden)
[*]
-charset = utf-8 # Default character encoding
-end_of_line = lf # Use LF for line endings (Unix-style)
-indent_style = space # Use spaces for indentation
-indent_size = 4 # Default indentation size
-insert_final_newline = true # Make sure files end with a newline
-trim_trailing_whitespace = true # Remove trailing whitespace
+charset = utf-8
+end_of_line = lf
+indent_style = space
+indent_size = 4
+insert_final_newline = true
+trim_trailing_whitespace = true
-# Zig files
[*.zig]
max_line_length = 100
-# Markdown files
[*.md]
max_line_length = 120
-trim_trailing_whitespace = false # Don't remove trailing whitespace in Markdown files
+trim_trailing_whitespace = false
-# Bash scripts
[*.sh]
indent_size = 2
-# YAML files
[*.{yml,yaml}]
indent_size = 2
-# Python files
[*.py]
max_line_length = 100
diff --git a/.github/workflows/benches.yml b/.github/workflows/benches.yml
new file mode 100644
index 0000000..6f21c91
--- /dev/null
+++ b/.github/workflows/benches.yml
@@ -0,0 +1,36 @@
+name: Run Benchmarks
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - main
+ tags:
+ - 'v*'
+ pull_request:
+ branches:
+ - main
+
+permissions:
+ contents: read
+
+jobs:
+ benchmarks:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout Repository
+ uses: actions/checkout@v4
+
+ - name: Install Zig
+ uses: goto-bus-stop/setup-zig@v2
+ with:
+ version: '0.15.1'
+
+ - name: Install Dependencies
+ run: |
+ sudo apt-get update
+ sudo apt-get install -y make
+
+ - name: Run the Benchmarks
+ run: make bench
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index bd15684..916ed06 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -1,12 +1,11 @@
name: Publish API Documentation
on:
+ workflow_dispatch:
push:
tags:
- 'v*'
- workflow_dispatch:
-
permissions:
contents: write
@@ -20,7 +19,7 @@ jobs:
- name: Install Zig
uses: goto-bus-stop/setup-zig@v2
with:
- version: '0.14.1'
+ version: '0.15.1'
- name: Install System Dependencies
run: |
diff --git a/.github/workflows/lints.yml b/.github/workflows/lints.yml
index d08a97d..e57bae1 100644
--- a/.github/workflows/lints.yml
+++ b/.github/workflows/lints.yml
@@ -1,18 +1,14 @@
name: Run Linter Checks
on:
+ workflow_dispatch:
push:
- branches:
- - main
tags:
- 'v*'
-
pull_request:
branches:
- main
- workflow_dispatch:
-
permissions:
contents: read
@@ -27,7 +23,7 @@ jobs:
- name: Install Zig
uses: goto-bus-stop/setup-zig@v2
with:
- version: '0.14.1'
+ version: '0.15.1'
- name: Install Dependencies
run: |
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 3b5d45b..712b7c8 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -1,18 +1,16 @@
name: Run Tests
on:
+ workflow_dispatch:
push:
branches:
- main
tags:
- 'v*'
-
pull_request:
branches:
- main
- workflow_dispatch:
-
permissions:
contents: read
@@ -27,7 +25,7 @@ jobs:
- name: Install Zig
uses: goto-bus-stop/setup-zig@v2
with:
- version: '0.14.1'
+ version: '0.15.1'
- name: Install Dependencies
run: |
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 177c140..98ec238 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -22,21 +22,21 @@ repos:
- repo: local
hooks:
- id: format
- name: Format the code
+ name: Format Code
entry: make format
language: system
pass_filenames: false
stages: [ pre-commit ]
- id: lint
- name: Check code style
+ name: Check Code Style
entry: make lint
language: system
pass_filenames: false
stages: [ pre-commit ]
- id: test
- name: Run the tests
+ name: Run Test
entry: make test
language: system
pass_filenames: false
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 8e67f5c..54d08d1 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -1,3 +1,3 @@
-# Code of Conduct
+## Code of Conduct
We adhere to the [Contributor Covenant](https://www.contributor-covenant.org/version/2/1/code_of_conduct/) version 2.1.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index aa6be24..afe7029 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,24 +1,24 @@
-# Contribution Guidelines
+## Contribution Guidelines
Thank you for considering contributing to this project!
Contributions are always welcome and appreciated.
-## How to Contribute
+### How to Contribute
-Please check the [issue tracker](https://github.com/habedi/ordered/issues) to see if there is an issue you
+Please check the [issue tracker](https://github.com/CogitatorTech/ordered/issues) to see if there is an issue you
would like to work on or if it has already been resolved.
-### Reporting Bugs
+#### Reporting Bugs
-1. Open an issue on the [issue tracker](https://github.com/habedi/ordered/issues).
+1. Open an issue on the [issue tracker](https://github.com/CogitatorTech/ordered/issues).
2. Include information such as steps to reproduce the observed behavior and relevant logs or screenshots.
-### Suggesting Features
+#### Suggesting Features
-1. Open an issue on the [issue tracker](https://github.com/habedi/ordered/issues).
+1. Open an issue on the [issue tracker](https://github.com/CogitatorTech/ordered/issues).
2. Provide details about the feature, its purpose, and potential implementation ideas.
-## Submitting Pull Requests
+### Submitting Pull Requests
- Ensure all tests pass before submitting a pull request.
- Write a clear description of the changes you made and the reasons behind them.
@@ -26,9 +26,9 @@ would like to work on or if it has already been resolved.
> [!IMPORTANT]
> It's assumed that by submitting a pull request, you agree to license your contributions under the project's license.
-## Development Workflow
+### Development Workflow
-### Prerequisites
+#### Prerequisites
Install GNU Make on your system if it's not already installed.
@@ -39,22 +39,22 @@ sudo apt-get install make
- Use the `make install-deps` command to install the development dependencies.
-### Code Style
+#### Code Style
- Use the `make format` command to format the code.
-### Running Tests
+#### Running Tests
- Use the `make test` command to run the tests.
-### Running Linters
+#### Running Linters
- Use the `make lint` command to run the linters.
-### See Available Commands
+#### See Available Commands
- Run `make help` to see all available commands for managing different tasks.
-## Code of Conduct
+### Code of Conduct
-We adhere to the [Contributor Covenant](https://www.contributor-covenant.org/version/2/1/code_of_conduct/) version 2.1.
+We adhere to the project's [Code of Conduct](CODE_OF_CONDUCT.md).
diff --git a/Makefile b/Makefile
index aa5b130..8b4f788 100644
--- a/Makefile
+++ b/Makefile
@@ -1,12 +1,13 @@
# ################################################################################
# # Configuration and Variables
# ################################################################################
-ZIG ?= $(shell which zig || echo ~/.local/share/zig/0.14.1/zig)
+ZIG ?= $(shell which zig || echo ~/.local/share/zig/0.15.1/zig)
BUILD_TYPE ?= Debug
BUILD_OPTS = -Doptimize=$(BUILD_TYPE)
JOBS ?= $(shell nproc || echo 2)
SRC_DIR := src
EXAMPLES_DIR := examples
+BENCHMARKS_DIR:= benches
BUILD_DIR := zig-out
CACHE_DIR := .zig-cache
BINARY_NAME := example
@@ -18,6 +19,10 @@ JUNK_FILES := *.o *.obj *.dSYM *.dll *.so *.dylib *.a *.lib *.pdb temp/
EXAMPLES := $(patsubst %.zig,%,$(notdir $(wildcard examples/*.zig)))
EXAMPLE ?= all
+# Automatically find all benchmark names
+BENCHMARKS := $(patsubst %.zig,%,$(notdir $(wildcard benches/*.zig)))
+BENCHMARK ?= all
+
SHELL := /usr/bin/env bash
.SHELLFLAGS := -eu -o pipefail -c
@@ -25,7 +30,7 @@ SHELL := /usr/bin/env bash
# Targets
################################################################################
-.PHONY: all help build rebuild run test release clean lint format docs serve-docs install-deps setup-hooks test-hooks
+.PHONY: all help build rebuild run bench test release clean lint format docs serve-docs install-deps setup-hooks test-hooks
.DEFAULT_GOAL := help
help: ## Show the help messages for all targets
@@ -43,7 +48,7 @@ build: ## Build project (e.g. 'make build BUILD_TYPE=ReleaseSmall' or 'make buil
rebuild: clean build ## clean and build
-run: ## Run an example (e.g. 'make run EXAMPLE=sorted_set' or 'make run' to run all examples)
+run: ## Run an example (like 'make run EXAMPLE=e1_btree_map' or 'make run' to run all examples)
@if [ "$(EXAMPLE)" = "all" ]; then \
echo "--> Running all examples..."; \
for ex in $(EXAMPLES); do \
@@ -56,6 +61,19 @@ run: ## Run an example (e.g. 'make run EXAMPLE=sorted_set' or 'make run' to run
$(ZIG) build run-$(EXAMPLE) $(BUILD_OPTS); \
fi
+bench: ## Run a benchmark (like 'make bench BENCHMARK=b1_btree_map' or 'make run' to run all benchmarks)
+ @if [ "$(BENCHMARK)" = "all" ]; then \
+ echo "--> Running all benchmarks..."; \
+ for ex in $(BENCHMARKS); do \
+ echo ""; \
+ echo "--> Running '$$ex'"; \
+ $(ZIG) build bench-$$ex $(BUILD_OPTS); \
+ done; \
+ else \
+ echo "--> Running benchmark: $(BENCHMARK)"; \
+ $(ZIG) build bench-$(BENCHMARK) $(BUILD_OPTS); \
+ fi
+
test: ## Run tests
@echo "Running tests..."
@$(ZIG) build test $(BUILD_OPTS) -j$(JOBS) $(TEST_FLAGS)
diff --git a/README.md b/README.md
index 7070e5c..a55c66f 100644
--- a/README.md
+++ b/README.md
@@ -6,34 +6,51 @@
Ordered
-[](https://github.com/habedi/ordered/actions/workflows/tests.yml)
-[](https://www.codefactor.io/repository/github/habedi/ordered)
-[](https://ziglang.org/download/)
-[](https://habedi.github.io/ordered/)
-[](https://github.com/habedi/ordered/releases/latest)
-[](https://github.com/habedi/ordered/blob/main/LICENSE)
+[](https://github.com/CogitatorTech/ordered/actions/workflows/tests.yml)
+[](https://github.com/CogitatorTech/ordered/actions/workflows/benches.yml)
+[](https://www.codefactor.io/repository/github/CogitatorTech/ordered)
+[](https://CogitatorTech.github.io/ordered/)
+[](https://github.com/CogitatorTech/ordered/tree/main/examples)
+[](https://ziglang.org/download/)
+[](https://github.com/CogitatorTech/ordered/releases/latest)
+[](https://github.com/CogitatorTech/ordered/blob/main/LICENSE)
-A Zig library of common data structures that keep data in order
+A collection of data structures that keep data in order in pure Zig
---
-Ordered Zig library includes implementations of popular data structures including B-tree, skip list, trie, and
-red-black tree.
+Ordered is a Zig library that provides fast and efficient implementations of various popular data structures including
+B-tree, skip list, trie, and red-black tree for Zig programming language.
-### Features
+### Supported Data Structures
-- Implementations for common data structures that maintain the order of keys:
- - [`BTreeMap`](src/btree_map.zig): A balanced tree map that maintains order of keys.
- - [`OrderedSet`](src/sorted_set.zig): A set with ordered elements.
- - [`SkipList`](src/skip_list.zig): A probabilistic data structure that allows fast search, insertion, and deletion.
- - [`Trie`](src/trie.zig): A prefix tree for fast retrieval of keys with common prefixes.
- - [`RedBlackTree`](src/red_black_tree.zig): A self-balancing binary search tree that maintains order of keys.
+Currently supported data structures include:
+
+- [B-tree](src/ordered/btree_map.zig): A self-balancing search tree where nodes can have many children.
+- [Sorted Set](src/ordered/sorted_set.zig): A data structure that stores a collection of unique elements in a consistently sorted order.
+- [Skip List](src/ordered/skip_list.zig): A probabilistic data structure that uses multiple linked lists to create "express lanes" for fast, tree-like search.
+- [Trie](src/ordered/trie.zig): A tree where paths from the root represent prefixes which makes it extremely fast for tasks like text autocomplete.
+- [Red-black Tree](src/ordered/red_black_tree.zig): A self-balancing binary search tree that uses node colors to guarantee efficient operations.
+- [Cartesian Tree](src/ordered/cartesian_tree.zig): A binary tree that uniquely combines a binary search tree property for its keys with a heap** property for its values.
+
+| # | Data Structure | Build Complexity | Memory Complexity | Search Complexity |
+|---|----------------|------------------|-------------------|----------------------|
+| 1 | B-tree | $O(\log n)$ | $O(n)$ | $O(\log n)$ |
+| 2 | Cartesian tree | $O(\log n)$\* | $O(n)$ | $O(\log n)$\* |
+| 3 | Red-black tree | $O(\log n)$ | $O(n)$ | $O(\log n)$ |
+| 4 | Skip list | $O(\log n)$\* | $O(n)$ | $O(\log n)$\* |
+| 5 | Sorted set | $O(n)$ | $O(n)$ | $O(\log n)$ |
+| 6 | Trie | $O(m)$ | $O(n \cdot m)$ | $O(m)$ |
+
+- $n$: number of stored elements
+- $m$: maximum length of a key
+- \*: average case complexity
> [!IMPORTANT]
-> Zig-DbC is in early development, so bugs and breaking API changes are expected.
-> Please use the [issues page](https://github.com/habedi/zig-dbc/issues) to report bugs or request features.
+> Ordered is in early development, so bugs and breaking API changes are expected.
+> Please use the [issues page](https://github.com/CogitatorTech/ordered/issues) to report bugs or request features.
---
@@ -45,7 +62,7 @@ To be added.
### Documentation
-You can find the API documentation for the latest release of Ordered [here](https://habedi.github.io/ordered/).
+You can find the API documentation for the latest release of Ordered [here](https://CogitatorTech.github.io/ordered/).
Alternatively, you can use the `make docs` command to generate the documentation for the current version of Ordered.
This will generate HTML documentation in the `docs/api` directory, which you can serve locally with `make serve-docs`
@@ -53,7 +70,7 @@ and view in a web browser.
### Examples
-Check out the [examples](examples/) directory for example usages of Ordered.
+Check out the [examples](examples) directory for example usages of Ordered.
---
@@ -67,4 +84,4 @@ Ordered is licensed under the MIT License (see [LICENSE](LICENSE)).
### Acknowledgements
-* The logo is from [SVG Repo](https://www.svgrepo.com/svg/469537/zig-zag-left-right-arrow).
+* The logo is from [SVG Repo](https://www.svgrepo.com/svg/469537/zig-zag-left-right-arrow) with some modifications.
diff --git a/benches/.gitkeep b/benches/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/benches/README.md b/benches/README.md
new file mode 100644
index 0000000..ebbb48f
--- /dev/null
+++ b/benches/README.md
@@ -0,0 +1,87 @@
+## Benchmarks
+
+This directory contains benchmarks for the data structures in the `ordered` library.
+
+### Running Benchmarks
+
+Each benchmark can be run using the following command pattern:
+
+```bash
+zig build bench-
+```
+
+#### Available Benchmarks
+
+- **BTreeMap**: `zig build bench-btree_map_bench`
+- **SortedSet**: `zig build bench-sorted_set_bench`
+- **RedBlackTree**: `zig build bench-red_black_tree_bench`
+- **SkipList**: `zig build bench-skip_list_bench`
+- **Trie**: `zig build bench-trie_bench`
+- **CartesianTree**: `zig build bench-cartesian_tree_bench`
+
+### What Each Benchmark Tests
+
+#### BTreeMap Benchmark
+- **Insert**: Sequential insertion of integers
+- **Lookup**: Finding all inserted keys
+- **Delete**: Removing all keys
+
+#### SortedSet Benchmark
+- **Add**: Adding elements while maintaining a sorted order
+- **Contains**: Checking if elements exist
+- **Remove**: Removing elements from the set
+
+#### RedBlackTree Benchmark
+- **Insert**: Inserting nodes with self-balancing
+- **Find**: Searching for nodes
+- **Remove**: Deleting nodes while maintaining balance
+- **Iterator**: In-order traversal performance
+
+#### SkipList Benchmark
+- **Put**: Inserting key-value pairs with probabilistic levels
+- **Get**: Retrieving values by key
+- **Delete**: Removing key-value pairs
+
+#### Trie Benchmark
+- **Put**: Inserting strings with associated values
+- **Get**: Retrieving values by string key
+- **Contains**: Checking if strings exist
+- **Prefix Search**: Finding all keys with a common prefix
+
+#### CartesianTree Benchmark
+- **Put**: Inserting key-value pairs with random priorities
+- **Get**: Retrieving values by key
+- **Remove**: Deleting nodes
+- **Iterator**: In-order traversal performance
+
+### Benchmark Sizes
+
+Each benchmark tests with multiple dataset sizes:
+- Small: 1,000 items
+- Medium: 10,000 items
+- Large: 50,000 - 100,000 items (varies by data structure)
+
+### Build Configuration
+
+Benchmarks are compiled with `ReleaseFast` optimization mode for accurate performance measurements.
+
+### Example Output
+
+```
+=== BTreeMap Benchmark ===
+
+Insert 1000 items: 0.42 ms (420 ns/op)
+Lookup 1000 items: 0.18 ms (180 ns/op, found: 1000)
+Delete 1000 items: 0.35 ms (350 ns/op)
+
+Insert 10000 items: 5.23 ms (523 ns/op)
+Lookup 10000 items: 2.10 ms (210 ns/op, found: 10000)
+Delete 10000 items: 4.15 ms (415 ns/op)
+```
+
+### Notes
+
+- All benchmarks use a simple integer or string key type for consistency
+- Times are reported in both total milliseconds and nanoseconds per operation
+- Memory allocations use `GeneralPurposeAllocator` for simulating a more realistic memory usage
+- Results may vary based on a hardware and system load
diff --git a/benches/b1_btree_map.zig b/benches/b1_btree_map.zig
new file mode 100644
index 0000000..c7757dd
--- /dev/null
+++ b/benches/b1_btree_map.zig
@@ -0,0 +1,102 @@
+const std = @import("std");
+const ordered = @import("ordered");
+const Timer = std.time.Timer;
+
+pub fn main() !void {
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+ defer _ = gpa.deinit();
+ const allocator = gpa.allocator();
+
+ std.debug.print("=== BTreeMap Benchmark ===\n\n", .{});
+
+ const sizes = [_]usize{ 1000, 10_000, 100_000 };
+
+ inline for (sizes) |size| {
+ try benchmarkInsert(allocator, size);
+ try benchmarkLookup(allocator, size);
+ try benchmarkDelete(allocator, size);
+ std.debug.print("\n", .{});
+ }
+}
+
+fn i32Compare(lhs: i32, rhs: i32) std.math.Order {
+ return std.math.order(lhs, rhs);
+}
+
+fn benchmarkInsert(allocator: std.mem.Allocator, size: usize) !void {
+ var map = ordered.BTreeMap(i32, i32, i32Compare, 16).init(allocator);
+ defer map.deinit();
+
+ var timer = try Timer.start();
+ const start = timer.lap();
+
+ var i: i32 = 0;
+ while (i < size) : (i += 1) {
+ try map.put(i, i * 2);
+ }
+
+ const elapsed = timer.read() - start;
+ const ns_per_op = elapsed / size;
+
+ std.debug.print("Insert {} items: {d:.2} ms ({d} ns/op)\n", .{
+ size,
+ @as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
+ ns_per_op,
+ });
+}
+
+fn benchmarkLookup(allocator: std.mem.Allocator, size: usize) !void {
+ var map = ordered.BTreeMap(i32, i32, i32Compare, 16).init(allocator);
+ defer map.deinit();
+
+ var i: i32 = 0;
+ while (i < size) : (i += 1) {
+ try map.put(i, i * 2);
+ }
+
+ var timer = try Timer.start();
+ const start = timer.lap();
+
+ i = 0;
+ var found: usize = 0;
+ while (i < size) : (i += 1) {
+ if (map.get(i) != null) found += 1;
+ }
+
+ const elapsed = timer.read() - start;
+ const ns_per_op = elapsed / size;
+
+ std.debug.print("Lookup {} items: {d:.2} ms ({d} ns/op, found: {})\n", .{
+ size,
+ @as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
+ ns_per_op,
+ found,
+ });
+}
+
+fn benchmarkDelete(allocator: std.mem.Allocator, size: usize) !void {
+ var map = ordered.BTreeMap(i32, i32, i32Compare, 16).init(allocator);
+ defer map.deinit();
+
+ var i: i32 = 0;
+ while (i < size) : (i += 1) {
+ try map.put(i, i * 2);
+ }
+
+ var timer = try Timer.start();
+ const start = timer.lap();
+
+ i = 0;
+ while (i < size) : (i += 1) {
+ _ = map.remove(i);
+ }
+
+ const elapsed = timer.read() - start;
+ const ns_per_op = elapsed / size;
+
+ std.debug.print("Delete {} items: {d:.2} ms ({d} ns/op)\n", .{
+ size,
+ @as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
+ ns_per_op,
+ });
+}
diff --git a/benches/b2_sorted_set.zig b/benches/b2_sorted_set.zig
new file mode 100644
index 0000000..ece558f
--- /dev/null
+++ b/benches/b2_sorted_set.zig
@@ -0,0 +1,101 @@
+const std = @import("std");
+const ordered = @import("ordered");
+const Timer = std.time.Timer;
+
+pub fn main() !void {
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+ defer _ = gpa.deinit();
+ const allocator = gpa.allocator();
+
+ std.debug.print("=== SortedSet Benchmark ===\n\n", .{});
+
+ const sizes = [_]usize{ 1000, 10_000, 50_000 };
+
+ inline for (sizes) |size| {
+ try benchmarkAdd(allocator, size);
+ try benchmarkContains(allocator, size);
+ try benchmarkRemove(allocator, size);
+ std.debug.print("\n", .{});
+ }
+}
+
+fn i32Compare(lhs: i32, rhs: i32) std.math.Order {
+ return std.math.order(lhs, rhs);
+}
+
+fn benchmarkAdd(allocator: std.mem.Allocator, size: usize) !void {
+ var set = ordered.SortedSet(i32, i32Compare).init(allocator);
+ defer set.deinit();
+
+ var timer = try Timer.start();
+ const start = timer.lap();
+
+ var i: i32 = 0;
+ while (i < size) : (i += 1) {
+ try set.add(i);
+ }
+
+ const elapsed = timer.read() - start;
+ const ns_per_op = elapsed / size;
+
+ std.debug.print("Add {} items: {d:.2} ms ({d} ns/op)\n", .{
+ size,
+ @as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
+ ns_per_op,
+ });
+}
+
+fn benchmarkContains(allocator: std.mem.Allocator, size: usize) !void {
+ var set = ordered.SortedSet(i32, i32Compare).init(allocator);
+ defer set.deinit();
+
+ var i: i32 = 0;
+ while (i < size) : (i += 1) {
+ try set.add(i);
+ }
+
+ var timer = try Timer.start();
+ const start = timer.lap();
+
+ i = 0;
+ var found: usize = 0;
+ while (i < size) : (i += 1) {
+ if (set.contains(i)) found += 1;
+ }
+
+ const elapsed = timer.read() - start;
+ const ns_per_op = elapsed / size;
+
+ std.debug.print("Contains {} items: {d:.2} ms ({d} ns/op, found: {})\n", .{
+ size,
+ @as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
+ ns_per_op,
+ found,
+ });
+}
+
+fn benchmarkRemove(allocator: std.mem.Allocator, size: usize) !void {
+ var set = ordered.SortedSet(i32, i32Compare).init(allocator);
+ defer set.deinit();
+
+ var i: i32 = 0;
+ while (i < size) : (i += 1) {
+ try set.add(i);
+ }
+
+ var timer = try Timer.start();
+ const start = timer.lap();
+
+ while (set.items.items.len > 0) {
+ _ = set.remove(0);
+ }
+
+ const elapsed = timer.read() - start;
+ const ns_per_op = elapsed / size;
+
+ std.debug.print("Remove {} items: {d:.2} ms ({d} ns/op)\n", .{
+ size,
+ @as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
+ ns_per_op,
+ });
+}
diff --git a/benches/b3_red_black_tree.zig b/benches/b3_red_black_tree.zig
new file mode 100644
index 0000000..d571e63
--- /dev/null
+++ b/benches/b3_red_black_tree.zig
@@ -0,0 +1,137 @@
+const std = @import("std");
+const ordered = @import("ordered");
+const Timer = std.time.Timer;
+
+pub fn main() !void {
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+ defer _ = gpa.deinit();
+ const allocator = gpa.allocator();
+
+ std.debug.print("=== RedBlackTree Benchmark ===\n\n", .{});
+
+ const sizes = [_]usize{ 1000, 10_000, 100_000 };
+
+ inline for (sizes) |size| {
+ try benchmarkInsert(allocator, size);
+ try benchmarkFind(allocator, size);
+ try benchmarkRemove(allocator, size);
+ try benchmarkIterator(allocator, size);
+ std.debug.print("\n", .{});
+ }
+}
+
+const Context = struct {
+ pub fn lessThan(self: @This(), a: i32, b: i32) bool {
+ _ = self;
+ return a < b;
+ }
+};
+
+fn benchmarkInsert(allocator: std.mem.Allocator, size: usize) !void {
+ var tree = ordered.RedBlackTree(i32, Context).init(allocator, Context{});
+ defer tree.deinit();
+
+ var timer = try Timer.start();
+ const start = timer.lap();
+
+ var i: i32 = 0;
+ while (i < size) : (i += 1) {
+ try tree.insert(i);
+ }
+
+ const elapsed = timer.read() - start;
+ const ns_per_op = elapsed / size;
+
+ std.debug.print("Insert {} items: {d:.2} ms ({d} ns/op)\n", .{
+ size,
+ @as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
+ ns_per_op,
+ });
+}
+
+fn benchmarkFind(allocator: std.mem.Allocator, size: usize) !void {
+ var tree = ordered.RedBlackTree(i32, Context).init(allocator, Context{});
+ defer tree.deinit();
+
+ var i: i32 = 0;
+ while (i < size) : (i += 1) {
+ try tree.insert(i);
+ }
+
+ var timer = try Timer.start();
+ const start = timer.lap();
+
+ i = 0;
+ var found: usize = 0;
+ while (i < size) : (i += 1) {
+ if (tree.find(i) != null) found += 1;
+ }
+
+ const elapsed = timer.read() - start;
+ const ns_per_op = elapsed / size;
+
+ std.debug.print("Find {} items: {d:.2} ms ({d} ns/op, found: {})\n", .{
+ size,
+ @as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
+ ns_per_op,
+ found,
+ });
+}
+
+fn benchmarkRemove(allocator: std.mem.Allocator, size: usize) !void {
+ var tree = ordered.RedBlackTree(i32, Context).init(allocator, Context{});
+ defer tree.deinit();
+
+ var i: i32 = 0;
+ while (i < size) : (i += 1) {
+ try tree.insert(i);
+ }
+
+ var timer = try Timer.start();
+ const start = timer.lap();
+
+ i = 0;
+ while (i < size) : (i += 1) {
+ _ = tree.remove(i);
+ }
+
+ const elapsed = timer.read() - start;
+ const ns_per_op = elapsed / size;
+
+ std.debug.print("Remove {} items: {d:.2} ms ({d} ns/op)\n", .{
+ size,
+ @as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
+ ns_per_op,
+ });
+}
+
+fn benchmarkIterator(allocator: std.mem.Allocator, size: usize) !void {
+ var tree = ordered.RedBlackTree(i32, Context).init(allocator, Context{});
+ defer tree.deinit();
+
+ var i: i32 = 0;
+ while (i < size) : (i += 1) {
+ try tree.insert(i);
+ }
+
+ var timer = try Timer.start();
+ const start = timer.lap();
+
+ var iter = try tree.iterator();
+ defer iter.deinit();
+
+ var count: usize = 0;
+ while (try iter.next()) |_| {
+ count += 1;
+ }
+
+ const elapsed = timer.read() - start;
+ const ns_per_op = elapsed / size;
+
+ std.debug.print("Iterator {} items: {d:.2} ms ({d} ns/op, count: {})\n", .{
+ size,
+ @as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
+ ns_per_op,
+ count,
+ });
+}
diff --git a/benches/b4_skip_list.zig b/benches/b4_skip_list.zig
new file mode 100644
index 0000000..a534f9c
--- /dev/null
+++ b/benches/b4_skip_list.zig
@@ -0,0 +1,102 @@
+const std = @import("std");
+const ordered = @import("ordered");
+const Timer = std.time.Timer;
+
+pub fn main() !void {
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+ defer _ = gpa.deinit();
+ const allocator = gpa.allocator();
+
+ std.debug.print("=== SkipList Benchmark ===\n\n", .{});
+
+ const sizes = [_]usize{ 1000, 10_000, 100_000 };
+
+ inline for (sizes) |size| {
+ try benchmarkPut(allocator, size);
+ try benchmarkGet(allocator, size);
+ try benchmarkDelete(allocator, size);
+ std.debug.print("\n", .{});
+ }
+}
+
+fn i32Compare(lhs: i32, rhs: i32) std.math.Order {
+ return std.math.order(lhs, rhs);
+}
+
+fn benchmarkPut(allocator: std.mem.Allocator, size: usize) !void {
+ var list = try ordered.SkipList(i32, i32, i32Compare, 16).init(allocator);
+ defer list.deinit();
+
+ var timer = try Timer.start();
+ const start = timer.lap();
+
+ var i: i32 = 0;
+ while (i < size) : (i += 1) {
+ try list.put(i, i * 2);
+ }
+
+ const elapsed = timer.read() - start;
+ const ns_per_op = elapsed / size;
+
+ std.debug.print("Put {} items: {d:.2} ms ({d} ns/op)\n", .{
+ size,
+ @as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
+ ns_per_op,
+ });
+}
+
+fn benchmarkGet(allocator: std.mem.Allocator, size: usize) !void {
+ var list = try ordered.SkipList(i32, i32, i32Compare, 16).init(allocator);
+ defer list.deinit();
+
+ var i: i32 = 0;
+ while (i < size) : (i += 1) {
+ try list.put(i, i * 2);
+ }
+
+ var timer = try Timer.start();
+ const start = timer.lap();
+
+ i = 0;
+ var found: usize = 0;
+ while (i < size) : (i += 1) {
+ if (list.get(i) != null) found += 1;
+ }
+
+ const elapsed = timer.read() - start;
+ const ns_per_op = elapsed / size;
+
+ std.debug.print("Get {} items: {d:.2} ms ({d} ns/op, found: {})\n", .{
+ size,
+ @as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
+ ns_per_op,
+ found,
+ });
+}
+
+fn benchmarkDelete(allocator: std.mem.Allocator, size: usize) !void {
+ var list = try ordered.SkipList(i32, i32, i32Compare, 16).init(allocator);
+ defer list.deinit();
+
+ var i: i32 = 0;
+ while (i < size) : (i += 1) {
+ try list.put(i, i * 2);
+ }
+
+ var timer = try Timer.start();
+ const start = timer.lap();
+
+ i = 0;
+ while (i < size) : (i += 1) {
+ _ = list.delete(i);
+ }
+
+ const elapsed = timer.read() - start;
+ const ns_per_op = elapsed / size;
+
+ std.debug.print("Delete {} items: {d:.2} ms ({d} ns/op)\n", .{
+ size,
+ @as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
+ ns_per_op,
+ });
+}
diff --git a/benches/b5_trie.zig b/benches/b5_trie.zig
new file mode 100644
index 0000000..deb0c14
--- /dev/null
+++ b/benches/b5_trie.zig
@@ -0,0 +1,166 @@
+const std = @import("std");
+const ordered = @import("ordered");
+const Timer = std.time.Timer;
+
+pub fn main() !void {
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+ defer _ = gpa.deinit();
+ const allocator = gpa.allocator();
+
+ std.debug.print("=== Trie Benchmark ===\n\n", .{});
+
+ const sizes = [_]usize{ 1000, 10_000, 50_000 };
+
+ inline for (sizes) |size| {
+ try benchmarkPut(allocator, size);
+ try benchmarkGet(allocator, size);
+ try benchmarkContains(allocator, size);
+ try benchmarkPrefixSearch(allocator, size);
+ std.debug.print("\n", .{});
+ }
+}
+
+fn generateKey(allocator: std.mem.Allocator, i: usize) ![]u8 {
+ return std.fmt.allocPrint(allocator, "key_{d:0>8}", .{i});
+}
+
+fn benchmarkPut(allocator: std.mem.Allocator, size: usize) !void {
+ var trie = try ordered.Trie(i32).init(allocator);
+ defer trie.deinit();
+
+ var arena = std.heap.ArenaAllocator.init(allocator);
+ defer arena.deinit();
+ const arena_alloc = arena.allocator();
+
+ var timer = try Timer.start();
+ const start = timer.lap();
+
+ var i: usize = 0;
+ while (i < size) : (i += 1) {
+ const key = try generateKey(arena_alloc, i);
+ try trie.put(key, @intCast(i));
+ }
+
+ const elapsed = timer.read() - start;
+ const ns_per_op = elapsed / size;
+
+ std.debug.print("Put {} items: {d:.2} ms ({d} ns/op)\n", .{
+ size,
+ @as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
+ ns_per_op,
+ });
+}
+
+fn benchmarkGet(allocator: std.mem.Allocator, size: usize) !void {
+ var trie = try ordered.Trie(i32).init(allocator);
+ defer trie.deinit();
+
+ var arena = std.heap.ArenaAllocator.init(allocator);
+ defer arena.deinit();
+ const arena_alloc = arena.allocator();
+
+ var i: usize = 0;
+ while (i < size) : (i += 1) {
+ const key = try generateKey(arena_alloc, i);
+ try trie.put(key, @intCast(i));
+ }
+
+ var timer = try Timer.start();
+ const start = timer.lap();
+
+ i = 0;
+ var found: usize = 0;
+ while (i < size) : (i += 1) {
+ const key = try generateKey(arena_alloc, i);
+ if (trie.get(key) != null) found += 1;
+ }
+
+ const elapsed = timer.read() - start;
+ const ns_per_op = elapsed / size;
+
+ std.debug.print("Get {} items: {d:.2} ms ({d} ns/op, found: {})\n", .{
+ size,
+ @as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
+ ns_per_op,
+ found,
+ });
+}
+
+fn benchmarkContains(allocator: std.mem.Allocator, size: usize) !void {
+ var trie = try ordered.Trie(i32).init(allocator);
+ defer trie.deinit();
+
+ var arena = std.heap.ArenaAllocator.init(allocator);
+ defer arena.deinit();
+ const arena_alloc = arena.allocator();
+
+ var i: usize = 0;
+ while (i < size) : (i += 1) {
+ const key = try generateKey(arena_alloc, i);
+ try trie.put(key, @intCast(i));
+ }
+
+ var timer = try Timer.start();
+ const start = timer.lap();
+
+ i = 0;
+ var found: usize = 0;
+ while (i < size) : (i += 1) {
+ const key = try generateKey(arena_alloc, i);
+ if (trie.contains(key)) found += 1;
+ }
+
+ const elapsed = timer.read() - start;
+ const ns_per_op = elapsed / size;
+
+ std.debug.print("Contains {} items: {d:.2} ms ({d} ns/op, found: {})\n", .{
+ size,
+ @as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
+ ns_per_op,
+ found,
+ });
+}
+
+fn benchmarkPrefixSearch(allocator: std.mem.Allocator, size: usize) !void {
+ var trie = try ordered.Trie(i32).init(allocator);
+ defer trie.deinit();
+
+ var arena = std.heap.ArenaAllocator.init(allocator);
+ defer arena.deinit();
+ const arena_alloc = arena.allocator();
+
+ var i: usize = 0;
+ while (i < size) : (i += 1) {
+ const key = try generateKey(arena_alloc, i);
+ try trie.put(key, @intCast(i));
+ }
+
+ var timer = try Timer.start();
+ const start = timer.lap();
+
+ // Search for common prefixes
+ const num_searches = @min(100, size / 10);
+ var total_found: usize = 0;
+
+ i = 0;
+ while (i < num_searches) : (i += 1) {
+ var keys = try trie.keysWithPrefix(allocator, "key_");
+ defer {
+ for (keys.items) |key| {
+ allocator.free(key);
+ }
+ keys.deinit(allocator);
+ }
+ total_found += keys.items.len;
+ }
+
+ const elapsed = timer.read() - start;
+ const ns_per_op = elapsed / num_searches;
+
+ std.debug.print("Prefix search {d} times (avg {d} matches): {d:.2} ms ({d} ns/op)\n", .{
+ num_searches,
+ total_found / num_searches,
+ @as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
+ ns_per_op,
+ });
+}
diff --git a/benches/b6_cartesian_tree.zig b/benches/b6_cartesian_tree.zig
new file mode 100644
index 0000000..377f8a0
--- /dev/null
+++ b/benches/b6_cartesian_tree.zig
@@ -0,0 +1,130 @@
+const std = @import("std");
+const ordered = @import("ordered");
+const Timer = std.time.Timer;
+
+pub fn main() !void {
+ var gpa = std.heap.GeneralPurposeAllocator(.{}){};
+ defer _ = gpa.deinit();
+ const allocator = gpa.allocator();
+
+ std.debug.print("=== CartesianTree Benchmark ===\n\n", .{});
+
+ const sizes = [_]usize{ 1000, 10_000, 100_000 };
+
+ inline for (sizes) |size| {
+ try benchmarkPut(allocator, size);
+ try benchmarkGet(allocator, size);
+ try benchmarkRemove(allocator, size);
+ try benchmarkIterator(allocator, size);
+ std.debug.print("\n", .{});
+ }
+}
+
+fn benchmarkPut(allocator: std.mem.Allocator, size: usize) !void {
+ var tree = ordered.CartesianTree(i32, i32).init(allocator);
+ defer tree.deinit();
+
+ var timer = try Timer.start();
+ const start = timer.lap();
+
+ var i: i32 = 0;
+ while (i < size) : (i += 1) {
+ try tree.put(i, i * 2);
+ }
+
+ const elapsed = timer.read() - start;
+ const ns_per_op = elapsed / size;
+
+ std.debug.print("Put {} items: {d:.2} ms ({d} ns/op)\n", .{
+ size,
+ @as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
+ ns_per_op,
+ });
+}
+
+fn benchmarkGet(allocator: std.mem.Allocator, size: usize) !void {
+ var tree = ordered.CartesianTree(i32, i32).init(allocator);
+ defer tree.deinit();
+
+ var i: i32 = 0;
+ while (i < size) : (i += 1) {
+ try tree.put(i, i * 2);
+ }
+
+ var timer = try Timer.start();
+ const start = timer.lap();
+
+ i = 0;
+ var found: usize = 0;
+ while (i < size) : (i += 1) {
+ if (tree.get(i)) |_| found += 1;
+ }
+
+ const elapsed = timer.read() - start;
+ const ns_per_op = elapsed / size;
+
+ std.debug.print("Get {} items: {d:.2} ms ({d} ns/op, found: {})\n", .{
+ size,
+ @as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
+ ns_per_op,
+ found,
+ });
+}
+
+fn benchmarkRemove(allocator: std.mem.Allocator, size: usize) !void {
+ var tree = ordered.CartesianTree(i32, i32).init(allocator);
+ defer tree.deinit();
+
+ var i: i32 = 0;
+ while (i < size) : (i += 1) {
+ try tree.put(i, i * 2);
+ }
+
+ var timer = try Timer.start();
+ const start = timer.lap();
+
+ i = 0;
+ while (i < size) : (i += 1) {
+ _ = tree.remove(i);
+ }
+
+ const elapsed = timer.read() - start;
+ const ns_per_op = elapsed / size;
+
+ std.debug.print("Remove {} items: {d:.2} ms ({d} ns/op)\n", .{
+ size,
+ @as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
+ ns_per_op,
+ });
+}
+
+fn benchmarkIterator(allocator: std.mem.Allocator, size: usize) !void {
+ var tree = ordered.CartesianTree(i32, i32).init(allocator);
+ defer tree.deinit();
+
+ var i: i32 = 0;
+ while (i < size) : (i += 1) {
+ try tree.put(i, i * 2);
+ }
+
+ var timer = try Timer.start();
+ const start = timer.lap();
+
+ var iter = tree.iterator(allocator);
+ defer iter.deinit();
+
+ var count: usize = 0;
+ while (iter.next()) |_| {
+ count += 1;
+ }
+
+ const elapsed = timer.read() - start;
+ const ns_per_op = elapsed / size;
+
+ std.debug.print("Iterator {} items: {d:.2} ms ({d} ns/op, count: {})\n", .{
+ size,
+ @as(f64, @floatFromInt(elapsed)) / 1_000_000.0,
+ ns_per_op,
+ count,
+ });
+}
diff --git a/build.zig b/build.zig
index 1cc40b2..ea51958 100644
--- a/build.zig
+++ b/build.zig
@@ -4,85 +4,98 @@ const fs = std.fs;
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
-
- // --- Library Setup ---
const lib_source = b.path("src/lib.zig");
-
- const lib = b.addStaticLibrary(.{
- .name = "ordered",
+ const lib_module = b.addModule("ordered", .{
.root_source_file = lib_source,
.target = target,
.optimize = optimize,
});
+ const lib = b.addLibrary(.{
+ .name = "ordered",
+ .root_module = lib_module,
+ });
b.installArtifact(lib);
-
- const lib_module = lib.root_module;
-
- // Add dependencies
- const zig_dbc_dep = b.dependency("dbc", .{});
- const zig_dbc_module = zig_dbc_dep.artifact("dbc").root_module;
- lib_module.addImport("dbc", zig_dbc_module);
-
- // --- Docs Setup ---
const docs_step = b.step("docs", "Generate API documentation");
const doc_install_path = "docs/api";
-
const gen_docs_cmd = b.addSystemCommand(&[_][]const u8{
b.graph.zig_exe,
"build-lib",
"src/lib.zig",
"-femit-docs=" ++ doc_install_path,
});
-
const mkdir_cmd = b.addSystemCommand(&[_][]const u8{
"mkdir", "-p", doc_install_path,
});
gen_docs_cmd.step.dependOn(&mkdir_cmd.step);
-
docs_step.dependOn(&gen_docs_cmd.step);
-
- // --- Test Setup ---
const lib_unit_tests = b.addTest(.{
- .root_source_file = lib_source,
- .target = target,
- .optimize = optimize,
+ .root_module = lib_module,
});
-
- // Add dbc import to tests as well
- lib_unit_tests.root_module.addImport("dbc", zig_dbc_module);
-
const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_lib_unit_tests.step);
- // --- Example Setup ---
+ // Build examples
const examples_path = "examples";
- var examples_dir = fs.cwd().openDir(examples_path, .{ .iterate = true }) catch |err| {
- if (err == error.FileNotFound) return;
- @panic("Can't open 'examples' directory");
- };
- defer examples_dir.close();
-
- var dir_iter = examples_dir.iterate();
- while (dir_iter.next() catch @panic("Failed to iterate examples")) |entry| {
- if (!std.mem.endsWith(u8, entry.name, ".zig")) continue;
-
- const exe_name = fs.path.stem(entry.name);
- const exe_path = b.fmt("{s}/{s}", .{ examples_path, entry.name });
-
- const exe = b.addExecutable(.{
- .name = exe_name,
- .root_source_file = b.path(exe_path),
- .target = target,
- .optimize = optimize,
- });
- exe.root_module.addImport("ordered", lib_module);
- b.installArtifact(exe);
+ examples_blk: {
+ var examples_dir = fs.cwd().openDir(examples_path, .{ .iterate = true }) catch |err| {
+ if (err == error.FileNotFound or err == error.NotDir) break :examples_blk;
+ @panic("Can't open 'examples' directory");
+ };
+ defer examples_dir.close();
+ var dir_iter = examples_dir.iterate();
+ while (dir_iter.next() catch @panic("Failed to iterate examples")) |entry| {
+ if (!std.mem.endsWith(u8, entry.name, ".zig")) continue;
+ const exe_name = fs.path.stem(entry.name);
+ const exe_path = b.fmt("{s}/{s}", .{ examples_path, entry.name });
+ const exe_module = b.createModule(.{
+ .root_source_file = b.path(exe_path),
+ .target = target,
+ .optimize = optimize,
+ });
+ exe_module.addImport("ordered", lib_module);
+ const exe = b.addExecutable(.{
+ .name = exe_name,
+ .root_module = exe_module,
+ });
+ b.installArtifact(exe);
+ const run_cmd = b.addRunArtifact(exe);
+ const run_step_name = b.fmt("run-{s}", .{exe_name});
+ const run_step_desc = b.fmt("Run the {s} example", .{exe_name});
+ const run_step = b.step(run_step_name, run_step_desc);
+ run_step.dependOn(&run_cmd.step);
+ }
+ }
- const run_cmd = b.addRunArtifact(exe);
- const run_step_name = b.fmt("run-{s}", .{exe_name});
- const run_step_desc = b.fmt("Run the {s} example", .{exe_name});
- const run_step = b.step(run_step_name, run_step_desc);
- run_step.dependOn(&run_cmd.step);
+ // Build benchmarks
+ const benches_path = "benches";
+ benches_blk: {
+ var benches_dir = fs.cwd().openDir(benches_path, .{ .iterate = true }) catch |err| {
+ if (err == error.FileNotFound or err == error.NotDir) break :benches_blk;
+ @panic("Can't open 'benches' directory");
+ };
+ defer benches_dir.close();
+ var dir_iter = benches_dir.iterate();
+ while (dir_iter.next() catch @panic("Failed to iterate benches")) |entry| {
+ if (!std.mem.endsWith(u8, entry.name, ".zig")) continue;
+ const bench_name = fs.path.stem(entry.name);
+ const bench_path = b.fmt("{s}/{s}", .{ benches_path, entry.name });
+ const bench_module = b.createModule(.{
+ .root_source_file = b.path(bench_path),
+ .target = target,
+ .optimize = .ReleaseFast, // Use ReleaseFast for benchmarks
+ });
+ bench_module.addImport("ordered", lib_module);
+ const bench_exe = b.addExecutable(.{
+ .name = bench_name,
+ .root_module = bench_module,
+ });
+ b.installArtifact(bench_exe);
+ const run_bench_cmd = b.addRunArtifact(bench_exe);
+ const bench_step_name = b.fmt("bench-{s}", .{bench_name});
+ const bench_step_desc = b.fmt("Run the {s} benchmark", .{bench_name});
+ const bench_step = b.step(bench_step_name, bench_step_desc);
+ bench_step.dependOn(&run_bench_cmd.step);
+ }
}
}
diff --git a/build.zig.zon b/build.zig.zon
index 6d2af56..015df48 100644
--- a/build.zig.zon
+++ b/build.zig.zon
@@ -1,14 +1,8 @@
.{
.name = .ordered,
- .version = "0.1.0-a1",
+ .version = "0.1.0-alpha.2",
.fingerprint = 0xc3121f99b0352e1b, // Changing this has security and trust implications.
- .minimum_zig_version = "0.14.1",
- .dependencies = .{
- .dbc = .{
- .url = "https://github.com/habedi/zig-dbc/archive/v0.1.0.tar.gz",
- .hash = "zig_dbc-0.1.0-VjRHfLVvAABdMwGT4jSQksMbvSUpWPsSG41qhUAmCJuk",
- },
- },
+ .minimum_zig_version = "0.15.1",
.paths = .{
"build.zig",
"build.zig.zon",
diff --git a/examples/README.md b/examples/README.md
index 59ab688..fc3efd4 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -1,7 +1,10 @@
## Ordered Examples
-| # | File | Description |
-|---|----------------------------------------------|----------------------------------------------------------------------------|
-| 1 | [e1_bounded_queue.zig](e1_bounded_queue.zig) | An example that shows contracts on a `BoundedQueue` data structure |
-| 2 | [e2_file_parser.zig](e2_file_parser.zig) | An example of using contracts to build a robust file parser |
-| 3 | [e3_linked_list.zig](e3_linked_list.zig) | An example that uses contracts to guarantee the integrity of a linked list |
+| # | File | Description |
+|---|------------------------------------------------|-----------------------------------------------------|
+| 1 | [e1_btree_map.zig](e1_btree_map.zig) | An example using the `BTreeMap` data structure |
+| 2 | [e2_sorted_set.zig](e2_sorted_set.zig) | An example using the `SortedSet` data structure |
+| 3 | [e3_red_black_tree.zig](e3_red_black_tree.zig) | An example using the `RedBlackTree` data structure |
+| 4 | [e4_skip_list.zig](e4_skip_list.zig) | An example using the `SkipList` data structure |
+| 5 | [e5_trie.zig](e5_trie.zig) | An example using the `Trie` data structure |
+| 6 | [e6_cartesian_tree.zig](e6_cartesian_tree.zig) | An example using the `CartesianTree` data structure |
diff --git a/examples/btree_map.zig b/examples/e1_btree_map.zig
similarity index 100%
rename from examples/btree_map.zig
rename to examples/e1_btree_map.zig
diff --git a/examples/sorted_set.zig b/examples/e2_sorted_set.zig
similarity index 100%
rename from examples/sorted_set.zig
rename to examples/e2_sorted_set.zig
diff --git a/examples/red_black_tree.zig b/examples/e3_red_black_tree.zig
similarity index 100%
rename from examples/red_black_tree.zig
rename to examples/e3_red_black_tree.zig
diff --git a/examples/skip_list.zig b/examples/e4_skip_list.zig
similarity index 100%
rename from examples/skip_list.zig
rename to examples/e4_skip_list.zig
diff --git a/examples/trie.zig b/examples/e5_trie.zig
similarity index 97%
rename from examples/trie.zig
rename to examples/e5_trie.zig
index 3f6843b..b086970 100644
--- a/examples/trie.zig
+++ b/examples/e5_trie.zig
@@ -28,7 +28,7 @@ pub fn main() !void {
for (keys.items) |key| {
allocator.free(key);
}
- keys.deinit();
+ keys.deinit(allocator);
}
std.debug.print("Keys with prefix 'car': ", .{});
diff --git a/examples/cartesian_tree.zig b/examples/e6_cartesian_tree.zig
similarity index 100%
rename from examples/cartesian_tree.zig
rename to examples/e6_cartesian_tree.zig
diff --git a/logo.svg b/logo.svg
index d5b2766..db99182 100644
--- a/logo.svg
+++ b/logo.svg
@@ -2,58 +2,58 @@
diff --git a/pyproject.toml b/pyproject.toml
index e1f37b7..a877ee1 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,12 +1,7 @@
[project]
name = "ordered"
version = "0.1.0"
-description = "Python environment for Ordered"
-readme = "README.md"
-license = { text = "MIT" }
-authors = [
- { name = "Hassan Abedi", email = "hassan.abedi.t@gmail.com" }
-]
+description = "The Python environment for the Ordered project"
requires-python = ">=3.10,<4.0"
dependencies = [
diff --git a/src/ordered/btree_map.zig b/src/ordered/btree_map.zig
index 7693dc7..58a4634 100644
--- a/src/ordered/btree_map.zig
+++ b/src/ordered/btree_map.zig
@@ -79,9 +79,10 @@ pub fn BTreeMap(
/// Inserts a key-value pair. If the key exists, the value is updated.
pub fn put(self: *Self, key: K, value: V) !void {
+ // Check if key exists and just update the value in place
if (self.get(key) != null) {
_ = self.remove(key);
- self.len += 1;
+ // Don't increment len here, it will be incremented below
}
var root_node = if (self.root) |r| r else {
@@ -113,22 +114,28 @@ pub fn BTreeMap(
new_sibling.is_leaf = child.is_leaf;
const t = MIN_KEYS;
- new_sibling.len = t;
+ // Calculate how many keys go to the right sibling
+ // Full node has BRANCHING_FACTOR - 1 keys
+ // Left child keeps t keys, parent gets 1, right sibling gets the rest
+ const right_keys = BRANCHING_FACTOR - 1 - t - 1;
+ new_sibling.len = right_keys;
+ // Copy the right half of keys to the new sibling
var j: u16 = 0;
- while (j < t) : (j += 1) {
+ while (j < right_keys) : (j += 1) {
new_sibling.keys[j] = child.keys[j + t + 1];
new_sibling.values[j] = child.values[j + t + 1];
}
if (!child.is_leaf) {
j = 0;
- while (j < t + 1) : (j += 1) {
+ while (j <= right_keys) : (j += 1) {
new_sibling.children[j] = child.children[j + t + 1];
}
}
child.len = t;
+ // Insert new sibling into parent
j = parent.len;
while (j > index) : (j -= 1) {
parent.children[j + 1] = parent.children[j];
@@ -400,3 +407,157 @@ test "BTreeMap: put, get, and delete" {
try str_map.put("b", 2);
try std.testing.expectEqual(2, str_map.get("b").?.*);
}
+
+test "BTreeMap: empty map operations" {
+ const allocator = std.testing.allocator;
+ var map = BTreeMap(i32, i32, i32Compare, 4).init(allocator);
+ defer map.deinit();
+
+ try std.testing.expect(map.get(42) == null);
+ try std.testing.expectEqual(@as(usize, 0), map.len);
+ try std.testing.expect(map.remove(42) == null);
+}
+
+test "BTreeMap: single element operations" {
+ const allocator = std.testing.allocator;
+ var map = BTreeMap(i32, []const u8, i32Compare, 4).init(allocator);
+ defer map.deinit();
+
+ try map.put(42, "answer");
+ try std.testing.expectEqual(@as(usize, 1), map.len);
+ try std.testing.expectEqualStrings("answer", map.get(42).?.*);
+
+ const removed = map.remove(42);
+ try std.testing.expect(removed != null);
+ try std.testing.expectEqualStrings("answer", removed.?);
+ try std.testing.expectEqual(@as(usize, 0), map.len);
+}
+
+test "BTreeMap: update existing keys" {
+ const allocator = std.testing.allocator;
+ var map = BTreeMap(i32, i32, i32Compare, 4).init(allocator);
+ defer map.deinit();
+
+ try map.put(10, 100);
+ try map.put(20, 200);
+ try map.put(10, 999); // Update
+
+ try std.testing.expectEqual(@as(usize, 2), map.len);
+ try std.testing.expectEqual(@as(i32, 999), map.get(10).?.*);
+}
+
+test "BTreeMap: sequential insertion" {
+ const allocator = std.testing.allocator;
+ var map = BTreeMap(i32, i32, i32Compare, 5).init(allocator);
+ defer map.deinit();
+
+ var i: i32 = 0;
+ while (i < 50) : (i += 1) {
+ try map.put(i, i * 2);
+ }
+
+ try std.testing.expectEqual(@as(usize, 50), map.len);
+
+ i = 0;
+ while (i < 50) : (i += 1) {
+ try std.testing.expectEqual(i * 2, map.get(i).?.*);
+ }
+}
+
+test "BTreeMap: reverse insertion" {
+ const allocator = std.testing.allocator;
+ var map = BTreeMap(i32, i32, i32Compare, 5).init(allocator);
+ defer map.deinit();
+
+ var i: i32 = 50;
+ while (i > 0) : (i -= 1) {
+ try map.put(i, i * 3);
+ }
+
+ try std.testing.expectEqual(@as(usize, 50), map.len);
+
+ i = 1;
+ while (i <= 50) : (i += 1) {
+ try std.testing.expectEqual(i * 3, map.get(i).?.*);
+ }
+}
+
+test "BTreeMap: random operations" {
+ const allocator = std.testing.allocator;
+ var map = BTreeMap(i32, i32, i32Compare, 4).init(allocator);
+ defer map.deinit();
+
+ const values = [_]i32{ 15, 3, 27, 8, 42, 1, 19, 33, 11, 25 };
+ for (values) |val| {
+ try map.put(val, val * 10);
+ }
+
+ try std.testing.expectEqual(@as(usize, 10), map.len);
+
+ for (values) |val| {
+ try std.testing.expectEqual(val * 10, map.get(val).?.*);
+ }
+
+ // Remove some
+ _ = map.remove(15);
+ _ = map.remove(27);
+ _ = map.remove(1);
+
+ try std.testing.expectEqual(@as(usize, 7), map.len);
+ try std.testing.expect(map.get(15) == null);
+ try std.testing.expect(map.get(27) == null);
+ try std.testing.expect(map.get(1) == null);
+}
+
+test "BTreeMap: remove all elements" {
+ const allocator = std.testing.allocator;
+ var map = BTreeMap(i32, i32, i32Compare, 4).init(allocator);
+ defer map.deinit();
+
+ try map.put(1, 1);
+ try map.put(2, 2);
+ try map.put(3, 3);
+ try map.put(4, 4);
+ try map.put(5, 5);
+
+ _ = map.remove(1);
+ _ = map.remove(2);
+ _ = map.remove(3);
+ _ = map.remove(4);
+ _ = map.remove(5);
+
+ try std.testing.expectEqual(@as(usize, 0), map.len);
+ try std.testing.expect(map.get(3) == null);
+}
+
+test "BTreeMap: minimum branching factor" {
+ const allocator = std.testing.allocator;
+ var map = BTreeMap(i32, i32, i32Compare, 3).init(allocator);
+ defer map.deinit();
+
+ var i: i32 = 0;
+ while (i < 20) : (i += 1) {
+ try map.put(i, i);
+ }
+
+ try std.testing.expectEqual(@as(usize, 20), map.len);
+
+ i = 0;
+ while (i < 20) : (i += 1) {
+ try std.testing.expectEqual(i, map.get(i).?.*);
+ }
+}
+
+test "BTreeMap: negative keys" {
+ const allocator = std.testing.allocator;
+ var map = BTreeMap(i32, i32, i32Compare, 4).init(allocator);
+ defer map.deinit();
+
+ try map.put(-10, 10);
+ try map.put(-5, 5);
+ try map.put(0, 0);
+ try map.put(5, -5);
+
+ try std.testing.expectEqual(@as(i32, 10), map.get(-10).?.*);
+ try std.testing.expectEqual(@as(i32, -5), map.get(5).?.*);
+}
diff --git a/src/ordered/cartesian_tree.zig b/src/ordered/cartesian_tree.zig
index 6c1c04b..03eb555 100644
--- a/src/ordered/cartesian_tree.zig
+++ b/src/ordered/cartesian_tree.zig
@@ -218,23 +218,25 @@ pub fn CartesianTree(comptime K: type, comptime V: type) type {
/// Iterator for in-order traversal
pub const Iterator = struct {
stack: std.ArrayList(*Node),
+ allocator: Allocator,
pub fn init(allocator: Allocator, root: ?*Node) Iterator {
var it = Iterator{
- .stack = std.ArrayList(*Node).init(allocator),
+ .stack = .{},
+ .allocator = allocator,
};
it.pushLeft(root);
return it;
}
pub fn deinit(self: *Iterator) void {
- self.stack.deinit();
+ self.stack.deinit(self.allocator);
}
fn pushLeft(self: *Iterator, node: ?*Node) void {
var current = node;
while (current) |n| {
- self.stack.append(n) catch return; // Handle potential allocation failure
+ self.stack.append(self.allocator, n) catch return; // Handle potential allocation failure
current = n.left;
}
}
@@ -279,62 +281,190 @@ test "CartesianTree basic operations" {
// Test get
try testing.expectEqualStrings("five", tree.get(5).?);
try testing.expectEqualStrings("three", tree.get(3).?);
- try testing.expectEqualStrings("seven", tree.get(7).?);
- try testing.expectEqualStrings("one", tree.get(1).?);
try testing.expect(tree.get(99) == null);
// Test contains
try testing.expect(tree.contains(5));
- try testing.expect(tree.contains(3));
try testing.expect(!tree.contains(99));
- // Test removal
+ // Test remove
try testing.expect(tree.remove(3));
+ try testing.expectEqual(@as(usize, 3), tree.count());
try testing.expect(!tree.contains(3));
+}
+
+test "CartesianTree: empty tree operations" {
+ var tree = CartesianTree(i32, i32).init(testing.allocator);
+ defer tree.deinit();
+
+ try testing.expect(tree.isEmpty());
+ try testing.expectEqual(@as(usize, 0), tree.count());
+ try testing.expect(tree.get(42) == null);
+ try testing.expect(!tree.contains(42));
+ try testing.expect(!tree.remove(42));
+}
+
+test "CartesianTree: single element" {
+ var tree = CartesianTree(i32, i32).init(testing.allocator);
+ defer tree.deinit();
+
+ try tree.putWithPriority(42, 100, 50);
+ try testing.expectEqual(@as(usize, 1), tree.count());
+ try testing.expectEqual(@as(i32, 100), tree.get(42).?);
+
+ const removed = tree.remove(42);
+ try testing.expect(removed);
+ try testing.expect(tree.isEmpty());
+ try testing.expect(tree.root == null);
+}
+
+test "CartesianTree: priority ordering" {
+ var tree = CartesianTree(i32, i32).init(testing.allocator);
+ defer tree.deinit();
+
+ // Higher priority should be closer to root
+ try tree.putWithPriority(10, 1, 100); // Highest priority
+ try tree.putWithPriority(5, 2, 50);
+ try tree.putWithPriority(15, 3, 75);
+
+ try testing.expectEqual(@as(u32, 100), tree.root.?.priority);
+ try testing.expectEqual(@as(i32, 10), tree.root.?.key);
+}
+
+test "CartesianTree: update existing key with different priority" {
+ var tree = CartesianTree(i32, i32).init(testing.allocator);
+ defer tree.deinit();
+
+ try tree.putWithPriority(10, 100, 50);
+ try tree.putWithPriority(10, 200, 75);
+
+ try testing.expectEqual(@as(usize, 1), tree.count());
+ try testing.expectEqual(@as(i32, 200), tree.get(10).?);
+}
+
+test "CartesianTree: random priorities with put" {
+ var tree = CartesianTree(i32, i32).init(testing.allocator);
+ defer tree.deinit();
+
+ // Using put which generates random priorities
+ try tree.put(1, 1);
+ try tree.put(2, 2);
+ try tree.put(3, 3);
+
try testing.expectEqual(@as(usize, 3), tree.count());
- try testing.expect(!tree.remove(99));
+ try testing.expectEqual(@as(i32, 1), tree.get(1).?);
+ try testing.expectEqual(@as(i32, 2), tree.get(2).?);
+ try testing.expectEqual(@as(i32, 3), tree.get(3).?);
}
-test "CartesianTree iterator" {
+test "CartesianTree: sequential keys" {
var tree = CartesianTree(i32, i32).init(testing.allocator);
defer tree.deinit();
- // Insert values with specific priorities to control structure
- try tree.putWithPriority(5, 50, 10);
- try tree.putWithPriority(3, 30, 5);
- try tree.putWithPriority(7, 70, 15);
- try tree.putWithPriority(1, 10, 3);
- try tree.putWithPriority(9, 90, 8);
-
- var it = tree.iterator(testing.allocator);
- defer it.deinit();
-
- // Should iterate in sorted key order
- const expected_keys = [_]i32{ 1, 3, 5, 7, 9 };
- const expected_values = [_]i32{ 10, 30, 50, 70, 90 };
-
- var i: usize = 0;
- while (it.next()) |entry| {
- try testing.expectEqual(expected_keys[i], entry.key);
- try testing.expectEqual(expected_values[i], entry.value);
- i += 1;
+ var i: i32 = 0;
+ while (i < 20) : (i += 1) {
+ try tree.putWithPriority(i, i * 2, @intCast(i));
+ }
+
+ try testing.expectEqual(@as(usize, 20), tree.count());
+
+ i = 0;
+ while (i < 20) : (i += 1) {
+ try testing.expectEqual(i * 2, tree.get(i).?);
}
- try testing.expectEqual(@as(usize, 5), i);
}
-test "CartesianTree heap property" {
- // Test that heap property is maintained (higher priority nodes are ancestors)
+test "CartesianTree: remove non-existent key" {
var tree = CartesianTree(i32, i32).init(testing.allocator);
defer tree.deinit();
- try tree.putWithPriority(5, 50, 100); // Root (highest priority)
- try tree.putWithPriority(3, 30, 80); // Left subtree
- try tree.putWithPriority(7, 70, 90); // Right subtree
- try tree.putWithPriority(1, 10, 60); // Left-left
- try tree.putWithPriority(9, 90, 70); // Right-right
+ try tree.putWithPriority(10, 10, 10);
+ try tree.putWithPriority(20, 20, 20);
- // Verify structure maintains heap property
- // Root should have highest priority
- try testing.expectEqual(@as(u32, 100), tree.root.?.priority);
- try testing.expectEqual(@as(i32, 5), tree.root.?.key);
+ const removed = tree.remove(15);
+ try testing.expect(!removed);
+ try testing.expectEqual(@as(usize, 2), tree.count());
+}
+
+test "CartesianTree: remove all elements" {
+ var tree = CartesianTree(i32, i32).init(testing.allocator);
+ defer tree.deinit();
+
+ try tree.putWithPriority(1, 1, 1);
+ try tree.putWithPriority(2, 2, 2);
+ try tree.putWithPriority(3, 3, 3);
+
+ try testing.expect(tree.remove(1));
+ try testing.expect(tree.remove(2));
+ try testing.expect(tree.remove(3));
+
+ try testing.expect(tree.isEmpty());
+ try testing.expect(tree.get(2) == null);
+}
+
+test "CartesianTree: negative keys" {
+ var tree = CartesianTree(i32, i32).init(testing.allocator);
+ defer tree.deinit();
+
+ try tree.putWithPriority(-10, 10, 100);
+ try tree.putWithPriority(-5, 5, 50);
+ try tree.putWithPriority(0, 0, 75);
+ try tree.putWithPriority(5, -5, 25);
+
+ try testing.expectEqual(@as(i32, 10), tree.get(-10).?);
+ try testing.expectEqual(@as(i32, -5), tree.get(5).?);
+}
+
+test "CartesianTree: iterator traversal" {
+ var tree = CartesianTree(i32, i32).init(testing.allocator);
+ defer tree.deinit();
+
+ try tree.putWithPriority(30, 30, 30);
+ try tree.putWithPriority(10, 10, 10);
+ try tree.putWithPriority(20, 20, 20);
+ try tree.putWithPriority(5, 5, 5);
+
+ var iter = tree.iterator(testing.allocator);
+ defer iter.deinit();
+
+ // Should iterate in sorted key order (BST property)
+ const expected = [_]i32{ 5, 10, 20, 30 };
+ var idx: usize = 0;
+
+ while (iter.next()) |entry| : (idx += 1) {
+ try testing.expectEqual(expected[idx], entry.key);
+ }
+ try testing.expectEqual(@as(usize, 4), idx);
+}
+
+test "CartesianTree: large dataset" {
+ var tree = CartesianTree(i32, i32).init(testing.allocator);
+ defer tree.deinit();
+
+ var i: i32 = 0;
+ while (i < 50) : (i += 1) {
+ try tree.putWithPriority(i, i * 3, @intCast(i * 2));
+ }
+
+ try testing.expectEqual(@as(usize, 50), tree.count());
+
+ i = 0;
+ while (i < 50) : (i += 1) {
+ try testing.expectEqual(i * 3, tree.get(i).?);
+ }
+}
+
+test "CartesianTree: same priorities different keys" {
+ var tree = CartesianTree(i32, i32).init(testing.allocator);
+ defer tree.deinit();
+
+ // When priorities are equal, BST property still maintained by key
+ try tree.putWithPriority(10, 1, 50);
+ try tree.putWithPriority(5, 2, 50);
+ try tree.putWithPriority(15, 3, 50);
+
+ try testing.expectEqual(@as(usize, 3), tree.count());
+ try testing.expect(tree.contains(5));
+ try testing.expect(tree.contains(10));
+ try testing.expect(tree.contains(15));
}
diff --git a/src/ordered/red_black_tree.zig b/src/ordered/red_black_tree.zig
index 4315ef1..77e9605 100644
--- a/src/ordered/red_black_tree.zig
+++ b/src/ordered/red_black_tree.zig
@@ -395,16 +395,18 @@ pub fn RedBlackTree(comptime T: type, comptime Context: type) type {
/// Iterator for in-order traversal
pub const Iterator = struct {
stack: std.ArrayList(*Node),
+ allocator: Allocator,
pub fn init(allocator: Allocator, root: ?*Node) !Iterator {
var it = Iterator{
- .stack = std.ArrayList(*Node).init(allocator),
+ .stack = .{},
+ .allocator = allocator,
};
// Initialize stack with leftmost path
var node = root;
while (node) |n| {
- try it.stack.append(n);
+ try it.stack.append(allocator, n);
node = n.left;
}
@@ -412,18 +414,18 @@ pub fn RedBlackTree(comptime T: type, comptime Context: type) type {
}
pub fn deinit(self: *Iterator) void {
- self.stack.deinit();
+ self.stack.deinit(self.allocator);
}
pub fn next(self: *Iterator) !?*Node {
if (self.stack.items.len == 0) return null;
- const node = self.stack.pop();
+ const node: *Node = self.stack.pop().?;
// Add right subtree to stack
var current = node.right;
while (current) |n| {
- try self.stack.append(n);
+ try self.stack.append(self.allocator, n);
current = n.left;
}
@@ -451,3 +453,198 @@ pub fn DefaultContext(comptime T: type) type {
pub fn RedBlackTreeManaged(comptime T: type) type {
return RedBlackTree(T, DefaultContext(T));
}
+
+test "RedBlackTree: basic operations" {
+ const allocator = std.testing.allocator;
+ var tree = RedBlackTree(i32, DefaultContext(i32)).init(allocator, .{});
+ defer tree.deinit();
+
+ try tree.insert(10);
+ try tree.insert(20);
+ try tree.insert(5);
+
+ try std.testing.expectEqual(@as(usize, 3), tree.count());
+ try std.testing.expect(tree.contains(10));
+ try std.testing.expect(tree.contains(5));
+ try std.testing.expect(!tree.contains(99));
+}
+
+test "RedBlackTree: empty tree operations" {
+ const allocator = std.testing.allocator;
+ var tree = RedBlackTree(i32, DefaultContext(i32)).init(allocator, .{});
+ defer tree.deinit();
+
+ try std.testing.expect(!tree.contains(42));
+ try std.testing.expectEqual(@as(usize, 0), tree.count());
+ try std.testing.expect(!tree.remove(42));
+}
+
+test "RedBlackTree: single element" {
+ const allocator = std.testing.allocator;
+ var tree = RedBlackTree(i32, DefaultContext(i32)).init(allocator, .{});
+ defer tree.deinit();
+
+ try tree.insert(42);
+ try std.testing.expectEqual(@as(usize, 1), tree.count());
+ try std.testing.expect(tree.contains(42));
+ try std.testing.expect(tree.root.?.color == .black);
+
+ const removed = tree.remove(42);
+ try std.testing.expect(removed);
+ try std.testing.expectEqual(@as(usize, 0), tree.count());
+ try std.testing.expect(tree.root == null);
+}
+
+test "RedBlackTree: duplicate insertions" {
+ const allocator = std.testing.allocator;
+ var tree = RedBlackTree(i32, DefaultContext(i32)).init(allocator, .{});
+ defer tree.deinit();
+
+ try tree.insert(10);
+ try tree.insert(10);
+ try tree.insert(10);
+
+ // Duplicates update existing nodes
+ try std.testing.expectEqual(@as(usize, 1), tree.count());
+}
+
+test "RedBlackTree: sequential insertion" {
+ const allocator = std.testing.allocator;
+ var tree = RedBlackTree(i32, DefaultContext(i32)).init(allocator, .{});
+ defer tree.deinit();
+
+ var i: i32 = 0;
+ while (i < 50) : (i += 1) {
+ try tree.insert(i);
+ }
+
+ try std.testing.expectEqual(@as(usize, 50), tree.count());
+ try std.testing.expect(tree.root.?.color == .black);
+
+ i = 0;
+ while (i < 50) : (i += 1) {
+ try std.testing.expect(tree.contains(i));
+ }
+}
+
+test "RedBlackTree: reverse insertion" {
+ const allocator = std.testing.allocator;
+ var tree = RedBlackTree(i32, DefaultContext(i32)).init(allocator, .{});
+ defer tree.deinit();
+
+ var i: i32 = 50;
+ while (i > 0) : (i -= 1) {
+ try tree.insert(i);
+ }
+
+ try std.testing.expectEqual(@as(usize, 50), tree.count());
+ try std.testing.expect(tree.root.?.color == .black);
+}
+
+test "RedBlackTree: remove from middle" {
+ const allocator = std.testing.allocator;
+ var tree = RedBlackTree(i32, DefaultContext(i32)).init(allocator, .{});
+ defer tree.deinit();
+
+ try tree.insert(10);
+ try tree.insert(5);
+ try tree.insert(15);
+ try tree.insert(3);
+ try tree.insert(7);
+
+ const removed = tree.remove(5);
+ try std.testing.expect(removed);
+ try std.testing.expectEqual(@as(usize, 4), tree.count());
+ try std.testing.expect(!tree.contains(5));
+ try std.testing.expect(tree.contains(3));
+ try std.testing.expect(tree.contains(7));
+}
+
+test "RedBlackTree: remove root" {
+ const allocator = std.testing.allocator;
+ var tree = RedBlackTree(i32, DefaultContext(i32)).init(allocator, .{});
+ defer tree.deinit();
+
+ try tree.insert(10);
+ try tree.insert(5);
+ try tree.insert(15);
+
+ const removed = tree.remove(10);
+ try std.testing.expect(removed);
+ try std.testing.expectEqual(@as(usize, 2), tree.count());
+ try std.testing.expect(tree.root.?.color == .black);
+}
+
+test "RedBlackTree: minimum and maximum" {
+ const allocator = std.testing.allocator;
+ var tree = RedBlackTree(i32, DefaultContext(i32)).init(allocator, .{});
+ defer tree.deinit();
+
+ try tree.insert(10);
+ try tree.insert(5);
+ try tree.insert(15);
+ try tree.insert(3);
+ try tree.insert(20);
+
+ const min = tree.minimum(null);
+ const max = tree.maximum(null);
+
+ try std.testing.expect(min != null);
+ try std.testing.expect(max != null);
+ try std.testing.expectEqual(@as(i32, 3), min.?.data);
+ try std.testing.expectEqual(@as(i32, 20), max.?.data);
+}
+
+test "RedBlackTree: iterator empty tree" {
+ const allocator = std.testing.allocator;
+ var tree = RedBlackTree(i32, DefaultContext(i32)).init(allocator, .{});
+ defer tree.deinit();
+
+ var iter = try tree.iterator();
+ defer iter.deinit();
+
+ const node = try iter.next();
+ try std.testing.expect(node == null);
+}
+
+test "RedBlackTree: clear" {
+ const allocator = std.testing.allocator;
+ var tree = RedBlackTree(i32, DefaultContext(i32)).init(allocator, .{});
+ defer tree.deinit();
+
+ try tree.insert(1);
+ try tree.insert(2);
+ try tree.insert(3);
+
+ tree.clear();
+ try std.testing.expectEqual(@as(usize, 0), tree.count());
+ try std.testing.expect(tree.root == null);
+}
+
+test "RedBlackTree: negative numbers" {
+ const allocator = std.testing.allocator;
+ var tree = RedBlackTree(i32, DefaultContext(i32)).init(allocator, .{});
+ defer tree.deinit();
+
+ try tree.insert(-10);
+ try tree.insert(-5);
+ try tree.insert(0);
+ try tree.insert(5);
+
+ try std.testing.expectEqual(@as(usize, 4), tree.count());
+ try std.testing.expect(tree.contains(-10));
+ try std.testing.expect(tree.contains(0));
+}
+
+test "RedBlackTree: find returns correct node" {
+ const allocator = std.testing.allocator;
+ var tree = RedBlackTree(i32, DefaultContext(i32)).init(allocator, .{});
+ defer tree.deinit();
+
+ try tree.insert(10);
+ try tree.insert(20);
+
+ const node = tree.find(10);
+ try std.testing.expect(node != null);
+ try std.testing.expectEqual(@as(i32, 10), node.?.data);
+}
diff --git a/src/ordered/skip_list.zig b/src/ordered/skip_list.zig
index b7142c2..8f00e89 100644
--- a/src/ordered/skip_list.zig
+++ b/src/ordered/skip_list.zig
@@ -311,21 +311,145 @@ test "SkipList: string keys" {
var list = try SkipList([]const u8, i32, strCompare, 16).init(allocator);
defer list.deinit();
- try list.put("banana", 2);
try list.put("apple", 1);
+ try list.put("banana", 2);
try list.put("cherry", 3);
- try std.testing.expectEqual(@as(i32, 1), list.get("apple").?.*);
try std.testing.expectEqual(@as(i32, 2), list.get("banana").?.*);
- try std.testing.expectEqual(@as(i32, 3), list.get("cherry").?.*);
+ try std.testing.expect(list.contains("apple"));
+ try std.testing.expect(!list.contains("date"));
+}
+
+test "SkipList: empty list operations" {
+ const allocator = std.testing.allocator;
+ var list = try SkipList(i32, i32, i32Compare, 16).init(allocator);
+ defer list.deinit();
+
+ try std.testing.expect(list.get(42) == null);
+ try std.testing.expectEqual(@as(usize, 0), list.len);
+ try std.testing.expect(list.delete(42) == null);
+ try std.testing.expect(!list.contains(42));
+}
+
+test "SkipList: single element" {
+ const allocator = std.testing.allocator;
+ var list = try SkipList(i32, i32, i32Compare, 16).init(allocator);
+ defer list.deinit();
+
+ try list.put(42, 100);
+ try std.testing.expectEqual(@as(usize, 1), list.len);
+ try std.testing.expectEqual(@as(i32, 100), list.get(42).?.*);
+
+ const deleted = list.delete(42);
+ try std.testing.expectEqual(@as(i32, 100), deleted.?);
+ try std.testing.expectEqual(@as(usize, 0), list.len);
+}
+
+test "SkipList: large dataset sequential" {
+ const allocator = std.testing.allocator;
+ var list = try SkipList(i32, i32, i32Compare, 16).init(allocator);
+ defer list.deinit();
+
+ var i: i32 = 0;
+ while (i < 100) : (i += 1) {
+ try list.put(i, i * 2);
+ }
+
+ try std.testing.expectEqual(@as(usize, 100), list.len);
+
+ i = 0;
+ while (i < 100) : (i += 1) {
+ try std.testing.expectEqual(i * 2, list.get(i).?.*);
+ }
+}
+
+test "SkipList: reverse insertion" {
+ const allocator = std.testing.allocator;
+ var list = try SkipList(i32, i32, i32Compare, 16).init(allocator);
+ defer list.deinit();
+
+ var i: i32 = 50;
+ while (i > 0) : (i -= 1) {
+ try list.put(i, i);
+ }
- // Test iteration maintains lexicographic order
+ try std.testing.expectEqual(@as(usize, 50), list.len);
+
+ // Verify iteration is sorted
var iter = list.iterator();
- const first = iter.next().?;
- try std.testing.expectEqualStrings("apple", first.key);
- const second = iter.next().?;
- try std.testing.expectEqualStrings("banana", second.key);
- const third = iter.next().?;
- try std.testing.expectEqualStrings("cherry", third.key);
- try std.testing.expect(iter.next() == null);
+ var prev: i32 = 0;
+ while (iter.next()) |entry| {
+ try std.testing.expect(entry.key > prev);
+ prev = entry.key;
+ }
+}
+
+test "SkipList: delete non-existent" {
+ const allocator = std.testing.allocator;
+ var list = try SkipList(i32, i32, i32Compare, 16).init(allocator);
+ defer list.deinit();
+
+ try list.put(10, 10);
+ try list.put(20, 20);
+
+ const deleted = list.delete(15);
+ try std.testing.expect(deleted == null);
+ try std.testing.expectEqual(@as(usize, 2), list.len);
+}
+
+test "SkipList: delete all elements" {
+ const allocator = std.testing.allocator;
+ var list = try SkipList(i32, i32, i32Compare, 16).init(allocator);
+ defer list.deinit();
+
+ try list.put(1, 1);
+ try list.put(2, 2);
+ try list.put(3, 3);
+
+ _ = list.delete(1);
+ _ = list.delete(2);
+ _ = list.delete(3);
+
+ try std.testing.expectEqual(@as(usize, 0), list.len);
+ try std.testing.expect(list.get(2) == null);
+}
+
+test "SkipList: negative keys" {
+ const allocator = std.testing.allocator;
+ var list = try SkipList(i32, i32, i32Compare, 16).init(allocator);
+ defer list.deinit();
+
+ try list.put(-10, 10);
+ try list.put(-5, 5);
+ try list.put(0, 0);
+ try list.put(5, -5);
+
+ try std.testing.expectEqual(@as(i32, 10), list.get(-10).?.*);
+ try std.testing.expectEqual(@as(i32, -5), list.get(5).?.*);
+}
+
+test "SkipList: getPtr mutation" {
+ const allocator = std.testing.allocator;
+ var list = try SkipList(i32, i32, i32Compare, 16).init(allocator);
+ defer list.deinit();
+
+ try list.put(10, 100);
+
+ const ptr = list.getPtr(10);
+ try std.testing.expect(ptr != null);
+ ptr.?.* = 999;
+
+ try std.testing.expectEqual(@as(i32, 999), list.get(10).?.*);
+}
+
+test "SkipList: minimum max level" {
+ const allocator = std.testing.allocator;
+ var list = try SkipList(i32, i32, i32Compare, 1).init(allocator);
+ defer list.deinit();
+
+ try list.put(1, 1);
+ try list.put(2, 2);
+ try list.put(3, 3);
+
+ try std.testing.expectEqual(@as(i32, 2), list.get(2).?.*);
}
diff --git a/src/ordered/sorted_set.zig b/src/ordered/sorted_set.zig
index d4abd77..4435ed7 100644
--- a/src/ordered/sorted_set.zig
+++ b/src/ordered/sorted_set.zig
@@ -16,13 +16,13 @@ pub fn SortedSet(
pub fn init(allocator: std.mem.Allocator) Self {
return .{
- .items = std.ArrayList(T).init(allocator),
+ .items = .{},
.allocator = allocator,
};
}
pub fn deinit(self: *Self) void {
- self.items.deinit();
+ self.items.deinit(self.allocator);
}
fn compareFn(key: T, item: T) std.math.Order {
@@ -32,7 +32,7 @@ pub fn SortedSet(
/// Adds a value to the vector, maintaining sort order.
pub fn add(self: *Self, value: T) !void {
const index = std.sort.lowerBound(T, self.items.items, value, compareFn);
- try self.items.insert(index, value);
+ try self.items.insert(self.allocator, index, value);
}
/// Removes an element at a given index.
@@ -73,3 +73,95 @@ test "SortedSet basic functionality" {
_ = vec.remove(1); // Remove 75
try std.testing.expectEqualSlices(i32, &.{ 50, 100 }, vec.items.items);
}
+
+test "SortedSet: empty set operations" {
+ const allocator = std.testing.allocator;
+ var vec = SortedSet(i32, i32Compare).init(allocator);
+ defer vec.deinit();
+
+ try std.testing.expect(!vec.contains(42));
+ try std.testing.expectEqual(@as(?usize, null), vec.findIndex(42));
+ try std.testing.expectEqual(@as(usize, 0), vec.items.items.len);
+}
+
+test "SortedSet: single element" {
+ const allocator = std.testing.allocator;
+ var vec = SortedSet(i32, i32Compare).init(allocator);
+ defer vec.deinit();
+
+ try vec.add(42);
+ try std.testing.expect(vec.contains(42));
+ try std.testing.expectEqual(@as(usize, 1), vec.items.items.len);
+
+ const removed = vec.remove(0);
+ try std.testing.expectEqual(@as(i32, 42), removed);
+ try std.testing.expectEqual(@as(usize, 0), vec.items.items.len);
+}
+
+test "SortedSet: duplicate values" {
+ const allocator = std.testing.allocator;
+ var vec = SortedSet(i32, i32Compare).init(allocator);
+ defer vec.deinit();
+
+ try vec.add(10);
+ try vec.add(10);
+ try vec.add(10);
+
+ // Duplicates are allowed in this implementation
+ try std.testing.expectEqual(@as(usize, 3), vec.items.items.len);
+}
+
+test "SortedSet: negative numbers" {
+ const allocator = std.testing.allocator;
+ var vec = SortedSet(i32, i32Compare).init(allocator);
+ defer vec.deinit();
+
+ try vec.add(-5);
+ try vec.add(-10);
+ try vec.add(0);
+ try vec.add(5);
+
+ try std.testing.expectEqualSlices(i32, &.{ -10, -5, 0, 5 }, vec.items.items);
+}
+
+test "SortedSet: large dataset" {
+ const allocator = std.testing.allocator;
+ var vec = SortedSet(i32, i32Compare).init(allocator);
+ defer vec.deinit();
+
+ // Insert in reverse order
+ var i: i32 = 100;
+ while (i >= 0) : (i -= 1) {
+ try vec.add(i);
+ }
+
+ // Verify sorted
+ try std.testing.expectEqual(@as(usize, 101), vec.items.items.len);
+ for (vec.items.items, 0..) |val, idx| {
+ try std.testing.expectEqual(@as(i32, @intCast(idx)), val);
+ }
+}
+
+test "SortedSet: remove boundary cases" {
+ const allocator = std.testing.allocator;
+ var vec = SortedSet(i32, i32Compare).init(allocator);
+ defer vec.deinit();
+
+ try vec.add(1);
+ try vec.add(2);
+ try vec.add(3);
+ try vec.add(4);
+ try vec.add(5);
+
+ // Remove first
+ _ = vec.remove(0);
+ try std.testing.expectEqualSlices(i32, &.{ 2, 3, 4, 5 }, vec.items.items);
+
+ // Remove last
+ _ = vec.remove(3);
+ try std.testing.expectEqualSlices(i32, &.{ 2, 3, 4 }, vec.items.items);
+
+ // Remove middle
+ _ = vec.remove(1);
+ try std.testing.expectEqualSlices(i32, &.{ 2, 4 }, vec.items.items);
+}
diff --git a/src/ordered/trie.zig b/src/ordered/trie.zig
index 38a2d7b..17e538d 100644
--- a/src/ordered/trie.zig
+++ b/src/ordered/trie.zig
@@ -154,7 +154,7 @@ pub fn Trie(comptime V: type) type {
}
pub fn keysWithPrefix(self: *const Self, allocator: std.mem.Allocator, prefix: []const u8) !std.ArrayList([]u8) {
- var results = std.ArrayList([]u8).init(allocator);
+ var results: std.ArrayList([]u8) = .{};
const prefix_node = self.findNode(prefix) orelse return results;
try self.collectKeys(allocator, prefix_node, &results, prefix);
@@ -165,7 +165,7 @@ pub fn Trie(comptime V: type) type {
fn collectKeys(self: *const Self, allocator: std.mem.Allocator, node: *const TrieNode, results: *std.ArrayList([]u8), current_key: []const u8) !void {
if (node.is_end) {
const key_copy = try allocator.dupe(u8, current_key);
- try results.append(key_copy);
+ try results.append(allocator, key_copy);
}
var iter = node.children.iterator();
@@ -194,8 +194,8 @@ pub fn Trie(comptime V: type) type {
};
fn init(allocator: std.mem.Allocator, root: *const TrieNode) !Iterator {
- var stack = std.ArrayList(IteratorFrame).init(allocator);
- try stack.append(IteratorFrame{
+ var stack: std.ArrayList(IteratorFrame) = .{};
+ try stack.append(allocator, IteratorFrame{
.node = root,
.child_iter = root.children.iterator(),
.visited_self = false,
@@ -204,13 +204,13 @@ pub fn Trie(comptime V: type) type {
return Iterator{
.stack = stack,
.allocator = allocator,
- .current_key = std.ArrayList(u8).init(allocator),
+ .current_key = .{},
};
}
fn deinit(self: *Iterator) void {
- self.stack.deinit();
- self.current_key.deinit();
+ self.stack.deinit(self.allocator);
+ self.current_key.deinit(self.allocator);
}
pub fn next(self: *Iterator) ?struct { key: []const u8, value: V } {
@@ -226,9 +226,9 @@ pub fn Trie(comptime V: type) type {
const char = entry.key_ptr.*;
const child = entry.value_ptr.*;
- self.current_key.append(char) catch return null;
+ self.current_key.append(self.allocator, char) catch return null;
- self.stack.append(IteratorFrame{
+ self.stack.append(self.allocator, IteratorFrame{
.node = child,
.child_iter = child.children.iterator(),
.visited_self = false,
@@ -252,25 +252,59 @@ pub fn Trie(comptime V: type) type {
test "Trie: basic operations" {
const allocator = std.testing.allocator;
- var trie = try Trie([]const u8).init(allocator);
+ var trie = try Trie(i32).init(allocator);
defer trie.deinit();
- try trie.put("cat", "feline");
- try trie.put("car", "vehicle");
- try trie.put("card", "playing card");
+ try trie.put("hello", 1);
+ try trie.put("world", 2);
+ try trie.put("help", 3);
try std.testing.expectEqual(@as(usize, 3), trie.len);
- try std.testing.expectEqualStrings("feline", trie.get("cat").?.*);
- try std.testing.expectEqualStrings("vehicle", trie.get("car").?.*);
- try std.testing.expect(trie.get("ca") == null);
+ try std.testing.expectEqual(@as(i32, 1), trie.get("hello").?.*);
+ try std.testing.expectEqual(@as(i32, 3), trie.get("help").?.*);
+ try std.testing.expect(trie.get("bye") == null);
+}
- const deleted = trie.delete("car");
- try std.testing.expectEqualStrings("vehicle", deleted.?);
- try std.testing.expect(trie.get("car") == null);
- try std.testing.expectEqual(@as(usize, 2), trie.len);
+test "Trie: empty trie operations" {
+ const allocator = std.testing.allocator;
+ var trie = try Trie(i32).init(allocator);
+ defer trie.deinit();
+
+ try std.testing.expect(trie.get("key") == null);
+ try std.testing.expectEqual(@as(usize, 0), trie.len);
+ try std.testing.expect(!trie.contains("key"));
+ try std.testing.expect(!trie.hasPrefix("pre"));
}
-test "Trie: prefix operations" {
+test "Trie: single character keys" {
+ const allocator = std.testing.allocator;
+ var trie = try Trie(i32).init(allocator);
+ defer trie.deinit();
+
+ try trie.put("a", 1);
+ try trie.put("b", 2);
+ try trie.put("c", 3);
+
+ try std.testing.expectEqual(@as(i32, 2), trie.get("b").?.*);
+ try std.testing.expect(trie.contains("a"));
+ try std.testing.expect(!trie.contains("d"));
+}
+
+test "Trie: empty string key" {
+ const allocator = std.testing.allocator;
+ var trie = try Trie(i32).init(allocator);
+ defer trie.deinit();
+
+ try trie.put("", 42);
+ try std.testing.expectEqual(@as(usize, 1), trie.len);
+ try std.testing.expectEqual(@as(i32, 42), trie.get("").?.*);
+
+ const deleted = trie.delete("");
+ try std.testing.expectEqual(@as(i32, 42), deleted.?);
+ try std.testing.expectEqual(@as(usize, 0), trie.len);
+}
+
+test "Trie: overlapping prefixes" {
const allocator = std.testing.allocator;
var trie = try Trie(i32).init(allocator);
defer trie.deinit();
@@ -278,18 +312,145 @@ test "Trie: prefix operations" {
try trie.put("test", 1);
try trie.put("testing", 2);
try trie.put("tester", 3);
+ try trie.put("tested", 4);
+ try std.testing.expectEqual(@as(usize, 4), trie.len);
+ try std.testing.expectEqual(@as(i32, 1), trie.get("test").?.*);
+ try std.testing.expectEqual(@as(i32, 2), trie.get("testing").?.*);
+ try std.testing.expect(trie.hasPrefix("tes"));
try std.testing.expect(trie.hasPrefix("test"));
- try std.testing.expect(trie.hasPrefix("te"));
- try std.testing.expect(!trie.hasPrefix("xyz"));
+}
- var keys = try trie.keysWithPrefix(allocator, "test");
- defer {
- for (keys.items) |key| {
- allocator.free(key);
- }
- keys.deinit();
+test "Trie: delete with shared prefixes" {
+ const allocator = std.testing.allocator;
+ var trie = try Trie(i32).init(allocator);
+ defer trie.deinit();
+
+ try trie.put("car", 1);
+ try trie.put("card", 2);
+ try trie.put("care", 3);
+
+ const deleted = trie.delete("card");
+ try std.testing.expectEqual(@as(i32, 2), deleted.?);
+ try std.testing.expectEqual(@as(usize, 2), trie.len);
+ try std.testing.expect(!trie.contains("card"));
+ try std.testing.expect(trie.contains("car"));
+ try std.testing.expect(trie.contains("care"));
+}
+
+test "Trie: delete non-existent key" {
+ const allocator = std.testing.allocator;
+ var trie = try Trie(i32).init(allocator);
+ defer trie.deinit();
+
+ try trie.put("hello", 1);
+
+ const deleted = trie.delete("world");
+ try std.testing.expect(deleted == null);
+ try std.testing.expectEqual(@as(usize, 1), trie.len);
+}
+
+test "Trie: delete prefix that is not a key" {
+ const allocator = std.testing.allocator;
+ var trie = try Trie(i32).init(allocator);
+ defer trie.deinit();
+
+ try trie.put("testing", 1);
+
+ const deleted = trie.delete("test");
+ try std.testing.expect(deleted == null);
+ try std.testing.expectEqual(@as(usize, 1), trie.len);
+ try std.testing.expect(trie.contains("testing"));
+}
+
+test "Trie: update existing key" {
+ const allocator = std.testing.allocator;
+ var trie = try Trie(i32).init(allocator);
+ defer trie.deinit();
+
+ try trie.put("key", 100);
+ try std.testing.expectEqual(@as(usize, 1), trie.len);
+
+ try trie.put("key", 200);
+ try std.testing.expectEqual(@as(usize, 1), trie.len);
+ try std.testing.expectEqual(@as(i32, 200), trie.get("key").?.*);
+}
+
+test "Trie: hasPrefix with exact match" {
+ const allocator = std.testing.allocator;
+ var trie = try Trie(i32).init(allocator);
+ defer trie.deinit();
+
+ try trie.put("hello", 1);
+
+ try std.testing.expect(trie.hasPrefix("hello"));
+ try std.testing.expect(trie.hasPrefix("hel"));
+ try std.testing.expect(trie.hasPrefix("h"));
+ try std.testing.expect(!trie.hasPrefix("helloo"));
+}
+
+test "Trie: getPtr mutation" {
+ const allocator = std.testing.allocator;
+ var trie = try Trie(i32).init(allocator);
+ defer trie.deinit();
+
+ try trie.put("key", 100);
+
+ const ptr = trie.getPtr("key");
+ try std.testing.expect(ptr != null);
+ ptr.?.* = 999;
+
+ try std.testing.expectEqual(@as(i32, 999), trie.get("key").?.*);
+}
+
+test "Trie: many keys" {
+ const allocator = std.testing.allocator;
+ var trie = try Trie(i32).init(allocator);
+ defer trie.deinit();
+
+ const keys = [_][]const u8{
+ "apple", "application", "apply", "banana", "band",
+ "can", "cancel", "cat", "dog", "door",
+ };
+
+ for (keys, 0..) |key, i| {
+ try trie.put(key, @intCast(i));
}
- try std.testing.expectEqual(@as(usize, 3), keys.items.len);
+ try std.testing.expectEqual(@as(usize, 10), trie.len);
+
+ for (keys, 0..) |key, i| {
+ try std.testing.expectEqual(@as(i32, @intCast(i)), trie.get(key).?.*);
+ }
+}
+
+test "Trie: delete all keys" {
+ const allocator = std.testing.allocator;
+ var trie = try Trie(i32).init(allocator);
+ defer trie.deinit();
+
+ try trie.put("a", 1);
+ try trie.put("b", 2);
+ try trie.put("c", 3);
+
+ _ = trie.delete("a");
+ _ = trie.delete("b");
+ _ = trie.delete("c");
+
+ try std.testing.expectEqual(@as(usize, 0), trie.len);
+ try std.testing.expect(!trie.hasPrefix("a"));
+}
+
+test "Trie: special characters" {
+ const allocator = std.testing.allocator;
+ var trie = try Trie(i32).init(allocator);
+ defer trie.deinit();
+
+ try trie.put("hello-world", 1);
+ try trie.put("test_case", 2);
+ try trie.put("foo.bar", 3);
+
+ try std.testing.expectEqual(@as(i32, 1), trie.get("hello-world").?.*);
+ try std.testing.expectEqual(@as(i32, 2), trie.get("test_case").?.*);
+ try std.testing.expectEqual(@as(i32, 3), trie.get("foo.bar").?.*);
}