Add CI workflow and update documentation for KDTree version
This commit is contained in:
parent
43c37f900f
commit
ec61d6afde
7 changed files with 119 additions and 8 deletions
42
.github/workflows/ci.yml
vendored
Normal file
42
.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, master ]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build-test:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [ '1.22.x', '1.23.x' ]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- name: Verify go.mod is tidy
|
||||
run: |
|
||||
go mod tidy
|
||||
git diff --exit-code -- go.mod go.sum
|
||||
|
||||
- name: Build
|
||||
run: go build ./...
|
||||
|
||||
- name: Test (race)
|
||||
run: go test -race ./...
|
||||
|
||||
- name: Vet
|
||||
run: go vet ./...
|
||||
|
||||
- name: Govulncheck
|
||||
uses: golang/govulncheck-action@v1
|
||||
with:
|
||||
go-version-input: ${{ matrix.go-version }}
|
||||
# Run against packages in the module
|
||||
args: ./...
|
||||
4
doc.go
Normal file
4
doc.go
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
// Package poindexter provides sorting utilities and a KDTree with simple
|
||||
// nearest-neighbour queries. It also includes helper functions to build
|
||||
// normalised, weighted KD points for 2D/3D/4D use-cases.
|
||||
package poindexter
|
||||
|
|
@ -13,13 +13,13 @@ func Version() string
|
|||
Returns the current version of the library.
|
||||
|
||||
**Returns:**
|
||||
- `string`: The version string (e.g., "0.1.0")
|
||||
- `string`: The version string (e.g., "0.2.0")
|
||||
|
||||
**Example:**
|
||||
|
||||
```go
|
||||
version := poindexter.Version()
|
||||
fmt.Println(version) // Output: 0.1.0
|
||||
fmt.Println(version) // Output: 0.2.0
|
||||
```
|
||||
|
||||
---
|
||||
|
|
|
|||
36
examples_test.go
Normal file
36
examples_test.go
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
package poindexter_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
poindexter "github.com/Snider/Poindexter"
|
||||
)
|
||||
|
||||
func ExampleNewKDTree() {
|
||||
pts := []poindexter.KDPoint[string]{
|
||||
{ID: "A", Coords: []float64{0, 0}, Value: "alpha"},
|
||||
{ID: "B", Coords: []float64{1, 0}, Value: "bravo"},
|
||||
}
|
||||
tr, _ := poindexter.NewKDTree(pts)
|
||||
p, _, _ := tr.Nearest([]float64{0.2, 0})
|
||||
fmt.Println(p.ID)
|
||||
// Output: A
|
||||
}
|
||||
|
||||
func ExampleBuild2D() {
|
||||
type rec struct{ ping, hops float64 }
|
||||
items := []rec{{ping: 20, hops: 3}, {ping: 30, hops: 2}, {ping: 15, hops: 4}}
|
||||
weights := [2]float64{1.0, 1.0}
|
||||
invert := [2]bool{false, false}
|
||||
pts, _ := poindexter.Build2D(items,
|
||||
func(r rec) string { return "" },
|
||||
func(r rec) float64 { return r.ping },
|
||||
func(r rec) float64 { return r.hops },
|
||||
weights, invert,
|
||||
)
|
||||
tr, _ := poindexter.NewKDTree(pts, poindexter.WithMetric(poindexter.ManhattanDistance{}))
|
||||
_, _, _ = tr.Nearest([]float64{0, 0})
|
||||
// Querying the origin (0,0) in normalized space tends to favor minima on each axis.
|
||||
fmt.Printf("dim=%d len=%d", tr.Dim(), tr.Len())
|
||||
// Output: dim=2 len=3
|
||||
}
|
||||
2
go.mod
2
go.mod
|
|
@ -1,3 +1,3 @@
|
|||
module github.com/Snider/Poindexter
|
||||
|
||||
go 1.24.9
|
||||
go 1.23
|
||||
|
|
|
|||
37
kdtree.go
37
kdtree.go
|
|
@ -6,6 +6,17 @@ import (
|
|||
"sort"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrEmptyPoints indicates that no points were provided to build a KDTree.
|
||||
ErrEmptyPoints = errors.New("kdtree: no points provided")
|
||||
// ErrZeroDim indicates that points or tree dimension must be at least 1.
|
||||
ErrZeroDim = errors.New("kdtree: points must have at least one dimension")
|
||||
// ErrDimMismatch indicates inconsistent dimensionality among points.
|
||||
ErrDimMismatch = errors.New("kdtree: inconsistent dimensionality in points")
|
||||
// ErrDuplicateID indicates a duplicate point ID was encountered.
|
||||
ErrDuplicateID = errors.New("kdtree: duplicate point ID")
|
||||
)
|
||||
|
||||
// KDPoint represents a point with coordinates and an attached payload/value.
|
||||
// ID should be unique within a tree to enable O(1) deletes by ID.
|
||||
// Coords must all have the same dimensionality within a given KDTree.
|
||||
|
|
@ -89,20 +100,20 @@ type KDTree[T any] struct {
|
|||
// All points must have the same dimensionality (>0).
|
||||
func NewKDTree[T any](pts []KDPoint[T], opts ...KDOption) (*KDTree[T], error) {
|
||||
if len(pts) == 0 {
|
||||
return nil, errors.New("no points provided")
|
||||
return nil, ErrEmptyPoints
|
||||
}
|
||||
dim := len(pts[0].Coords)
|
||||
if dim == 0 {
|
||||
return nil, errors.New("points must have at least one dimension")
|
||||
return nil, ErrZeroDim
|
||||
}
|
||||
idIndex := make(map[string]int, len(pts))
|
||||
for i, p := range pts {
|
||||
if len(p.Coords) != dim {
|
||||
return nil, errors.New("inconsistent dimensionality in points")
|
||||
return nil, ErrDimMismatch
|
||||
}
|
||||
if p.ID != "" {
|
||||
if _, exists := idIndex[p.ID]; exists {
|
||||
return nil, errors.New("duplicate point ID: " + p.ID)
|
||||
return nil, ErrDuplicateID
|
||||
}
|
||||
idIndex[p.ID] = i
|
||||
}
|
||||
|
|
@ -120,6 +131,24 @@ func NewKDTree[T any](pts []KDPoint[T], opts ...KDOption) (*KDTree[T], error) {
|
|||
return t, nil
|
||||
}
|
||||
|
||||
// NewKDTreeFromDim constructs an empty KDTree with the specified dimension.
|
||||
// Call Insert to add points after construction.
|
||||
func NewKDTreeFromDim[T any](dim int, opts ...KDOption) (*KDTree[T], error) {
|
||||
if dim <= 0 {
|
||||
return nil, ErrZeroDim
|
||||
}
|
||||
cfg := kdOptions{metric: EuclideanDistance{}}
|
||||
for _, o := range opts {
|
||||
o(&cfg)
|
||||
}
|
||||
return &KDTree[T]{
|
||||
points: nil,
|
||||
dim: dim,
|
||||
metric: cfg.metric,
|
||||
idIndex: make(map[string]int),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Dim returns the number of dimensions.
|
||||
func (t *KDTree[T]) Dim() int { return t.dim }
|
||||
|
||||
|
|
|
|||
2
sort.go
2
sort.go
|
|
@ -39,7 +39,7 @@ func SortBy[T any](data []T, less func(i, j int) bool) {
|
|||
}
|
||||
|
||||
// SortByKey sorts a slice by extracting a comparable key from each element.
|
||||
// The key function should return a value that implements constraints.Ordered.
|
||||
// K is restricted to int, float64, or string.
|
||||
func SortByKey[T any, K int | float64 | string](data []T, key func(T) K) {
|
||||
sort.Slice(data, func(i, j int) bool {
|
||||
return key(data[i]) < key(data[j])
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue