| .github/workflows | ||
| docs | ||
| examples | ||
| npm/poindexter-wasm | ||
| wasm | ||
| .gitignore | ||
| .golangci.yml | ||
| .goreleaser.yaml | ||
| .goreleaser.yml | ||
| AUDIT-SECRETS.md | ||
| bench_kdtree_dual_100k_test.go | ||
| bench_kdtree_dual_test.go | ||
| bench_kdtree_test.go | ||
| CHANGELOG.md | ||
| CLAUDE.md | ||
| CODE_OF_CONDUCT.md | ||
| CONTRIBUTING.md | ||
| dns_tools.go | ||
| dns_tools_test.go | ||
| doc.go | ||
| examples_test.go | ||
| fuzz_kdtree_test.go | ||
| go.mod | ||
| kdtree.go | ||
| kdtree_analytics.go | ||
| kdtree_analytics_test.go | ||
| kdtree_backend_parity_test.go | ||
| kdtree_branches_test.go | ||
| kdtree_extra_test.go | ||
| kdtree_gonum.go | ||
| kdtree_gonum_stub.go | ||
| kdtree_gonum_test.go | ||
| kdtree_helpers.go | ||
| kdtree_helpers_test.go | ||
| kdtree_morecov_test.go | ||
| kdtree_nd_noerr_test.go | ||
| kdtree_nd_test.go | ||
| kdtree_test.go | ||
| LICENSE | ||
| Makefile | ||
| mkdocs.yml | ||
| poindexter.go | ||
| poindexter_test.go | ||
| README.md | ||
| SECURITY.md | ||
| sort.go | ||
| sort_test.go | ||
Poindexter
A Go library package providing utility functions including sorting algorithms with custom comparators.
Features
- 🔢 Sorting Utilities: Sort integers, strings, and floats in ascending or descending order
- 🎯 Custom Sorting: Sort any type with custom comparison functions or key extractors
- 🔍 Binary Search: Fast search on sorted data
- 🧭 KDTree (NN Search): Build a KDTree over points with generic payloads; nearest, k-NN, and radius queries with Euclidean, Manhattan, Chebyshev, and Cosine metrics
- 📦 Generic Functions: Type-safe operations using Go generics
- ✅ Well-Tested: Comprehensive test coverage
- 📖 Documentation: Full documentation available at GitHub Pages
Installation
go get github.com/Snider/Poindexter
Quick Start
package main
import (
"fmt"
poindexter "github.com/Snider/Poindexter"
)
func main() {
// Basic sorting
numbers := []int{3, 1, 4, 1, 5, 9}
poindexter.SortInts(numbers)
fmt.Println(numbers) // [1 1 3 4 5 9]
// Custom sorting with key function
type Product struct {
Name string
Price float64
}
products := []Product{{"Apple", 1.50}, {"Banana", 0.75}, {"Cherry", 3.00}}
poindexter.SortByKey(products, func(p Product) float64 { return p.Price })
// KDTree quick demo
pts := []poindexter.KDPoint[string]{
{ID: "A", Coords: []float64{0, 0}, Value: "alpha"},
{ID: "B", Coords: []float64{1, 0}, Value: "bravo"},
{ID: "C", Coords: []float64{0, 1}, Value: "charlie"},
}
tree, _ := poindexter.NewKDTree(pts, poindexter.WithMetric(poindexter.EuclideanDistance{}))
nearest, dist, _ := tree.Nearest([]float64{0.9, 0.1})
fmt.Println(nearest.ID, nearest.Value, dist) // B bravo ~0.141...
}
Documentation
Full documentation is available at https://snider.github.io/Poindexter/
Explore runnable examples in the repository:
- examples/dht_ping_1d
- examples/kdtree_2d_ping_hop
- examples/kdtree_3d_ping_hop_geo
- examples/kdtree_4d_ping_hop_geo_score
- examples/dht_helpers (convenience wrappers for common DHT schemas)
- examples/wasm-browser (browser demo using the ESM loader)
- examples/wasm-browser-ts (TypeScript + Vite local demo)
KDTree performance and notes
- Dual backend support: Linear (always available) and an optimized KD backend enabled when building with
-tags=gonum. Linear is the default; with thegonumtag, the optimized backend becomes the default. - Complexity: Linear backend is O(n) per query. Optimized KD backend is typically sub-linear on prunable datasets and dims ≤ ~8, especially as N grows (≥10k–100k).
- Insert is O(1) amortized; delete by ID is O(1) via swap-delete; order is not preserved.
- Concurrency: the KDTree type is not safe for concurrent mutation. Protect with a mutex or share immutable snapshots for read-mostly workloads.
- See multi-dimensional examples (ping/hops/geo/score) in docs and
examples/. - Performance guide: see docs/Performance for benchmark guidance and tips: docs/perf.md • Hosted: https://snider.github.io/Poindexter/perf/
Backend selection
- Default backend is Linear. If you build with
-tags=gonum, the default becomes the optimized KD backend. - You can override per tree at construction:
// Force Linear (always available)
kdt1, _ := poindexter.NewKDTree(pts, poindexter.WithBackend(poindexter.BackendLinear))
// Force Gonum (requires build tag)
kdt2, _ := poindexter.NewKDTree(pts, poindexter.WithBackend(poindexter.BackendGonum))
- Supported metrics in the optimized backend: Euclidean (L2), Manhattan (L1), Chebyshev (L∞).
- Cosine and Weighted-Cosine currently run on the Linear backend.
- See the Performance guide for measured comparisons and when to choose which backend.
Choosing a metric (quick tips)
- Euclidean (L2): smooth trade-offs across axes; solid default for blended preferences.
- Manhattan (L1): emphasizes per-axis absolute differences; good when each unit of ping/hop matters equally.
- Chebyshev (L∞): dominated by the worst axis; useful for strict thresholds (e.g., reject high hop count regardless of ping).
- Cosine: angle-based for vector similarity; pair it with normalized/weighted features when direction matters more than magnitude.
See the multi-dimensional KDTree docs for end-to-end examples and weighting/normalization helpers: Multi-Dimensional KDTree (DHT).
Maintainer Makefile
The repository includes a maintainer-friendly Makefile that mirrors CI tasks and speeds up local workflows.
- help — list available targets
- tidy / tidy-check — run
go mod tidy, optionally verify no diffs - fmt — format code (
go fmt ./...) - vet —
go vet ./... - build —
go build ./... - examples — build all programs under
examples/(if present) - test — run unit tests
- race — run tests with the race detector
- cover — run tests with race + coverage (writes
coverage.outand prints summary) - coverhtml — render HTML coverage report to
coverage.html - coverfunc — print per-function coverage (from
coverage.out) - cover-kdtree — print coverage details filtered to
kdtree.go - fuzz — run Go fuzzing for a configurable time (default 10s) matching CI
- bench — run benchmarks with
-benchmem(writesbench.txt) - lint — run
golangci-lint(if installed) - vuln — run
govulncheck(if installed) - ci — CI-parity aggregate: tidy-check, build, vet, cover, examples, bench, lint, vuln
- release — run GoReleaser with the canonical
.goreleaser.yaml(for tagged releases) - snapshot — GoReleaser snapshot (no publish)
- docs-serve — serve MkDocs locally on 127.0.0.1:8000
- docs-build — build MkDocs site into
site/
Quick usage:
- See all targets:
make help
- Fast local cycle:
make fmt
make vet
make test
- CI-parity run (what GitHub Actions does, locally):
make ci
- Coverage summary:
make cover
- Generate HTML coverage report (writes coverage.html):
make coverhtml
- Fuzz for 10 seconds (default):
make fuzz
- Fuzz with a custom time (e.g., 30s):
make fuzz FUZZTIME=30s
- Run benchmarks (writes bench.txt):
make bench
- Build examples (if any under ./examples):
make examples
- Serve docs locally (requires mkdocs-material):
make docs-serve
Configurable variables:
FUZZTIME(default10s) — e.g.make fuzz FUZZTIME=30sBENCHOUT(defaultbench.txt),COVEROUT(defaultcoverage.out),COVERHTML(defaultcoverage.html)- Tool commands are overridable via env:
GO,GOLANGCI_LINT,GORELEASER,MKDOCS
Requirements for optional targets:
golangci-lintformake lintgolang.org/x/vuln/cmd/govulncheckformake vulngoreleaserformake release/make snapshotmkdocs+mkdocs-materialformake docs-serve/make docs-build
See the full Makefile at the repo root for authoritative target definitions.
License
This project is licensed under the European Union Public Licence v1.2 (EUPL-1.2). See LICENSE for details.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Coverage
- CI produces coverage summaries as artifacts on every push/PR:
- Default job:
coverage-summary.md(fromcoverage.out) - Gonum-tag job:
coverage-summary-gonum.md(fromcoverage-gonum.out)
- Default job:
- Locally, you can generate and inspect coverage with the Makefile:
make cover # runs tests with race + coverage and prints the total
make coverfunc # prints per-function coverage
make cover-kdtree # filters coverage to kdtree.go
make coverhtml # writes coverage.html for visual inspection
Note: CI also uploads raw coverage profiles as artifacts (coverage.out, coverage-gonum.out).