forked from Snider/Poindexter
110 lines
2.8 KiB
Go
110 lines
2.8 KiB
Go
|
|
package poindexter
|
||
|
|
|
||
|
|
import (
|
||
|
|
"testing"
|
||
|
|
)
|
||
|
|
|
||
|
|
func samplePoints() []KDPoint[string] {
|
||
|
|
return []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"},
|
||
|
|
{ID: "D", Coords: []float64{1, 1}, Value: "delta"},
|
||
|
|
{ID: "E", Coords: []float64{2, 2}, Value: "echo"},
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestKDTree_Nearest(t *testing.T) {
|
||
|
|
pts := samplePoints()
|
||
|
|
tree, err := NewKDTree(pts, WithMetric(EuclideanDistance{}))
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("NewKDTree error: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
p, dist, ok := tree.Nearest([]float64{0.9, 0.9})
|
||
|
|
if !ok {
|
||
|
|
t.Fatalf("expected a nearest neighbor")
|
||
|
|
}
|
||
|
|
if p.ID != "D" {
|
||
|
|
t.Fatalf("expected D, got %s", p.ID)
|
||
|
|
}
|
||
|
|
if dist <= 0 {
|
||
|
|
t.Fatalf("expected positive distance, got %v", dist)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestKDTree_KNearest(t *testing.T) {
|
||
|
|
pts := samplePoints()
|
||
|
|
tree, err := NewKDTree(pts, WithMetric(ManhattanDistance{}))
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("NewKDTree error: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
neighbors, dists := tree.KNearest([]float64{0.9, 0.9}, 3)
|
||
|
|
if len(neighbors) != 3 || len(dists) != 3 {
|
||
|
|
t.Fatalf("expected 3 neighbors, got %d", len(neighbors))
|
||
|
|
}
|
||
|
|
if neighbors[0].ID != "D" {
|
||
|
|
t.Fatalf("expected first neighbor D, got %s", neighbors[0].ID)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestKDTree_Radius(t *testing.T) {
|
||
|
|
pts := samplePoints()
|
||
|
|
tree, err := NewKDTree(pts, WithMetric(EuclideanDistance{}))
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("NewKDTree error: %v", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
neighbors, dists := tree.Radius([]float64{0, 0}, 1.01)
|
||
|
|
if len(neighbors) < 2 {
|
||
|
|
t.Fatalf("expected at least 2 neighbors within radius, got %d", len(neighbors))
|
||
|
|
}
|
||
|
|
// distances should be non-decreasing
|
||
|
|
for i := 1; i < len(dists); i++ {
|
||
|
|
if dists[i] < dists[i-1] {
|
||
|
|
t.Fatalf("distances not sorted: %v", dists)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestKDTree_InsertDelete(t *testing.T) {
|
||
|
|
pts := samplePoints()
|
||
|
|
tree, err := NewKDTree(pts)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("NewKDTree error: %v", err)
|
||
|
|
}
|
||
|
|
// Insert a new close point near (0,0)
|
||
|
|
ok := tree.Insert(KDPoint[string]{ID: "Z", Coords: []float64{0.05, 0.05}, Value: "zulu"})
|
||
|
|
if !ok {
|
||
|
|
t.Fatalf("insert failed")
|
||
|
|
}
|
||
|
|
p, _, found := tree.Nearest([]float64{0.04, 0.04})
|
||
|
|
if !found || p.ID != "Z" {
|
||
|
|
t.Fatalf("expected nearest to be Z after insert, got %+v", p)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Delete and verify nearest changes back
|
||
|
|
if !tree.DeleteByID("Z") {
|
||
|
|
t.Fatalf("delete failed")
|
||
|
|
}
|
||
|
|
p, _, found = tree.Nearest([]float64{0.04, 0.04})
|
||
|
|
if !found || p.ID != "A" {
|
||
|
|
t.Fatalf("expected nearest to be A after delete, got %+v", p)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestKDTree_DimAndLen(t *testing.T) {
|
||
|
|
pts := samplePoints()
|
||
|
|
tree, err := NewKDTree(pts)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("NewKDTree error: %v", err)
|
||
|
|
}
|
||
|
|
if tree.Len() != len(pts) {
|
||
|
|
t.Fatalf("Len mismatch: %d vs %d", tree.Len(), len(pts))
|
||
|
|
}
|
||
|
|
if tree.Dim() != 2 {
|
||
|
|
t.Fatalf("Dim mismatch: %d", tree.Dim())
|
||
|
|
}
|
||
|
|
}
|