diff --git a/kdtree.go b/kdtree.go index 232ad1c..efe2c61 100644 --- a/kdtree.go +++ b/kdtree.go @@ -22,7 +22,6 @@ import ( "math" "sort" - "github.com/kyroy/kdtree/kdrange" "github.com/kyroy/priority-queue" ) @@ -32,6 +31,10 @@ type Point interface { Dimensions() int // Dimension returns the value of the i-th dimension. Dimension(i int) float64 + // Distance returns the distance between two points. + Distance(p Point) float64 + // PlaneDistance returns the distance between the point and the plane X_{dim}=val. + PlaneDistance(val float64, dim int) float64 } // KDTree represents the k-d tree. @@ -138,7 +141,7 @@ func (t *KDTree) KNN(p Point, k int) []Point { // RangeSearch returns all points in the given range r. // // Returns an empty slice when input is nil or len(r) does not equal Point.Dimensions(). -func (t *KDTree) RangeSearch(r kdrange.Range) []Point { +func (t *KDTree) RangeSearch(r kdrangeRange) []Point { if t.root == nil || r == nil || len(r) != t.root.Dimensions() { return []Point{} } @@ -168,7 +171,7 @@ func knn(p Point, k int, start *node, currentAxis int, nearestPQ *pq.PriorityQue // 2. move up currentAxis = (currentAxis - 1 + p.Dimensions()) % p.Dimensions() for path, currentNode = popLast(path); currentNode != nil; path, currentNode = popLast(path) { - currentDistance := distance(p, currentNode) + currentDistance := p.Distance(currentNode.Point) checkedDistance := getKthOrLastDistance(nearestPQ, k-1) if currentDistance < checkedDistance { nearestPQ.Insert(currentNode, currentDistance) @@ -176,7 +179,7 @@ func knn(p Point, k int, start *node, currentAxis int, nearestPQ *pq.PriorityQue } // check other side of plane - if planeDistance(p, currentNode.Dimension(currentAxis), currentAxis) < checkedDistance { + if p.PlaneDistance(currentNode.Dimension(currentAxis), currentAxis) < checkedDistance { var next *node if p.Dimension(currentAxis) < currentNode.Dimension(currentAxis) { next = currentNode.Right @@ -189,18 +192,6 @@ func knn(p Point, k int, start *node, currentAxis int, nearestPQ *pq.PriorityQue } } -func distance(p1, p2 Point) float64 { - sum := 0. - for i := 0; i < p1.Dimensions(); i++ { - sum += math.Pow(p1.Dimension(i)-p2.Dimension(i), 2.0) - } - return math.Sqrt(sum) -} - -func planeDistance(p Point, planePosition float64, dim int) float64 { - return math.Abs(planePosition - p.Dimension(dim)) -} - func popLast(arr []*node) ([]*node, *node) { l := len(arr) - 1 if l < 0 { @@ -368,7 +359,7 @@ func (n *node) FindLargest(axis int, largest *node) *node { return largest } -func (n *node) RangeSearch(r kdrange.Range, axis int) []Point { +func (n *node) RangeSearch(r kdrangeRange, axis int) []Point { points := []Point{} for dim, limit := range r { diff --git a/kdtree_test.go b/kdtree_test.go index d1e3fcc..d50703f 100644 --- a/kdtree_test.go +++ b/kdtree_test.go @@ -14,56 +14,56 @@ * limitations under the License. */ -package kdtree_test +package kdtree import ( - "math" "math/rand" "testing" "time" "github.com/jupp0r/go-priority-queue" - "github.com/kyroy/kdtree" - "github.com/kyroy/kdtree/kdrange" - . "github.com/kyroy/kdtree/points" "github.com/stretchr/testify/assert" ) func TestNew(t *testing.T) { tests := []struct { name string - input []kdtree.Point - output []kdtree.Point + input []Point + output []Point }{ { name: "nil", input: nil, - output: []kdtree.Point{}, + output: []Point{}, }, { name: "empty", - input: []kdtree.Point{}, - output: []kdtree.Point{}, + input: []Point{}, + output: []Point{}, }, { name: "1", - input: []kdtree.Point{&Point2D{X: 1., Y: 2.}}, - output: []kdtree.Point{&Point2D{X: 1., Y: 2.}}, + input: []Point{&SamplePoint2D{X: 1., Y: 2.}}, + output: []Point{&SamplePoint2D{X: 1., Y: 2.}}, }, { name: "2 equal", - input: []kdtree.Point{&Point2D{X: 1., Y: 2.}, &Point2D{X: 1., Y: 2.}}, - output: []kdtree.Point{&Point2D{X: 1., Y: 2.}, &Point2D{X: 1., Y: 2.}}, + input: []Point{&SamplePoint2D{X: 1., Y: 2.}, &SamplePoint2D{X: 1., Y: 2.}}, + output: []Point{&SamplePoint2D{X: 1., Y: 2.}, &SamplePoint2D{X: 1., Y: 2.}}, }, { - name: "sort 1 dim", - input: []kdtree.Point{&Point2D{X: 1.1, Y: 1.2}, &Point2D{X: 1.3, Y: 1.0}, &Point2D{X: 0.9, Y: 1.3}}, - output: []kdtree.Point{&Point2D{X: 0.9, Y: 1.3}, &Point2D{X: 1.1, Y: 1.2}, &Point2D{X: 1.3, Y: 1.0}}, + name: "sort 1 dim", + input: []Point{&SamplePoint2D{X: 1.1, Y: 1.2}, + &SamplePoint2D{X: 1.3, Y: 1.0}, + &SamplePoint2D{X: 0.9, Y: 1.3}}, + output: []Point{&SamplePoint2D{X: 0.9, Y: 1.3}, + &SamplePoint2D{X: 1.1, Y: 1.2}, + &SamplePoint2D{X: 1.3, Y: 1.0}}, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - tree := kdtree.New(test.input) + tree := New(test.input) assert.Equal(t, test.output, tree.Points()) }) @@ -73,13 +73,18 @@ func TestNew(t *testing.T) { func TestKDTree_String(t *testing.T) { tests := []struct { name string - tree *kdtree.KDTree + tree *KDTree expected string }{ - {name: "empty", tree: &kdtree.KDTree{}, expected: "[]"}, - {name: "1 elem", tree: kdtree.New([]kdtree.Point{&Point2D{X: 2, Y: 3}}), expected: "[{2.00 3.00}]"}, - {name: "2 elem", tree: kdtree.New([]kdtree.Point{&Point2D{X: 2, Y: 3}, &Point2D{X: 3.4, Y: 1}}), expected: "[[{2.00 3.00} {3.40 1.00} ]]"}, - {name: "3 elem", tree: kdtree.New([]kdtree.Point{&Point2D{X: 2, Y: 3}, &Point2D{X: 1.4, Y: 7.1}, &Point2D{X: 3.4, Y: 1}}), expected: "[[{1.40 7.10} {2.00 3.00} {3.40 1.00}]]"}, + {name: "empty", tree: &KDTree{}, expected: "[]"}, + {name: "1 elem", tree: New([]Point{&SamplePoint2D{X: 2, Y: 3}}), expected: "[{2.00 3.00}]"}, + {name: "2 elem", tree: New([]Point{&SamplePoint2D{X: 2, Y: 3}, + &SamplePoint2D{X: 3.4, Y: 1}}), + expected: "[[{2.00 3.00} {3.40 1.00} ]]"}, + {name: "3 elem", tree: New([]Point{&SamplePoint2D{X: 2, Y: 3}, + &SamplePoint2D{X: 1.4, Y: 7.1}, + &SamplePoint2D{X: 3.4, Y: 1}}), + expected: "[[{1.40 7.10} {2.00 3.00} {3.40 1.00}]]"}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { @@ -91,26 +96,26 @@ func TestKDTree_String(t *testing.T) { func TestKDTree_Insert(t *testing.T) { tests := []struct { name string - treeInput *kdtree.KDTree - input []kdtree.Point - output []kdtree.Point + treeInput *KDTree + input []Point + output []Point }{ { name: "empty tree", - input: []kdtree.Point{&Point2D{X: 1., Y: 2.}}, - output: []kdtree.Point{&Point2D{X: 1., Y: 2.}}, + input: []Point{&SamplePoint2D{X: 1., Y: 2.}}, + output: []Point{&SamplePoint2D{X: 1., Y: 2.}}, }, { name: "1 dim", - treeInput: kdtree.New([]kdtree.Point{&Point2D{X: 1., Y: 2.}}), - input: []kdtree.Point{&Point2D{X: 0.9, Y: 2.1}}, - output: []kdtree.Point{&Point2D{X: 0.9, Y: 2.1}, &Point2D{X: 1., Y: 2.}}, + treeInput: New([]Point{&SamplePoint2D{X: 1., Y: 2.}}), + input: []Point{&SamplePoint2D{X: 0.9, Y: 2.1}}, + output: []Point{&SamplePoint2D{X: 0.9, Y: 2.1}, &SamplePoint2D{X: 1., Y: 2.}}, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if test.treeInput == nil { - test.treeInput = kdtree.New(nil) + test.treeInput = New(nil) } for _, elem := range test.input { test.treeInput.Insert(elem) @@ -123,7 +128,7 @@ func TestKDTree_Insert(t *testing.T) { func TestKDTree_InsertWithGenerator(t *testing.T) { tests := []struct { name string - input []kdtree.Point + input []Point }{ {name: "p:10,k:5", input: generateTestCaseData(10)}, {name: "p:100,k:5", input: generateTestCaseData(100)}, @@ -131,7 +136,7 @@ func TestKDTree_InsertWithGenerator(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - tree := kdtree.New(nil) + tree := New(nil) for _, elem := range test.input { tree.Insert(elem) _ = tree.Points() @@ -145,39 +150,43 @@ func TestKDTree_InsertWithGenerator(t *testing.T) { func TestKDTree_Remove(t *testing.T) { tests := []struct { name string - treeInput *kdtree.KDTree - preRemove []kdtree.Point - input kdtree.Point + treeInput *KDTree + preRemove []Point + input Point treeOutput string - output kdtree.Point + output Point }{ { name: "empty tree", - treeInput: kdtree.New([]kdtree.Point{}), - input: &Point2D{}, + treeInput: New([]Point{}), + input: &SamplePoint2D{}, treeOutput: "[]", output: nil, }, { name: "nil input", - treeInput: kdtree.New([]kdtree.Point{&Point2D{X: 1., Y: 2.}}), + treeInput: New([]Point{&SamplePoint2D{X: 1., Y: 2.}}), input: nil, treeOutput: "[{1.00 2.00}]", output: nil, }, { name: "remove root", - treeInput: kdtree.New([]kdtree.Point{&Point2D{X: 1., Y: 2.}}), - input: &Point2D{X: 1., Y: 2.}, + treeInput: New([]Point{&SamplePoint2D{X: 1., Y: 2.}}), + input: &SamplePoint2D{X: 1., Y: 2.}, treeOutput: "[]", - output: &Point2D{X: 1., Y: 2.}, + output: &SamplePoint2D{X: 1., Y: 2.}, }, { - name: "remove root with children", - treeInput: kdtree.New([]kdtree.Point{&Point2D{X: 1., Y: 2.}, &Point2D{X: 1.2, Y: 2.2}, &Point2D{X: 1.3, Y: 2.3}, &Point2D{X: 1.1, Y: 2.1}, &Point2D{X: -1.3, Y: -2.2}}), - input: &Point2D{X: 1.1, Y: 2.1}, + name: "remove root with children", + treeInput: New([]Point{&SamplePoint2D{X: 1., Y: 2.}, + &SamplePoint2D{X: 1.2, Y: 2.2}, + &SamplePoint2D{X: 1.3, Y: 2.3}, + &SamplePoint2D{X: 1.1, Y: 2.1}, + &SamplePoint2D{X: -1.3, Y: -2.2}}), + input: &SamplePoint2D{X: 1.1, Y: 2.1}, treeOutput: "[[{-1.30 -2.20} {1.00 2.00} [{1.20 2.20} {1.30 2.30} ]]]", - output: &Point2D{X: 1.1, Y: 2.1}, + output: &SamplePoint2D{X: 1.1, Y: 2.1}, }, // x(5,4) // y(3,6) (7, 7) @@ -185,77 +194,208 @@ func TestKDTree_Remove(t *testing.T) { // y(1, 3) (4, 1) (1,8) nil (7, 4) (8, 5) (6, 8) nil // [[[[{1.00 3.00} {2.00 2.00} {4.00 1.00}] {3.00 6.00} [{1.00 8.00} {2.00 10.00} ]] {5.00 4.00} [[{7.00 4.00} {8.00 2.00} {8.00 5.00}] {7.00 7.00} [{6.00 8.00} {9.00 9.00} ]]]] { - name: "not existing", - treeInput: kdtree.New([]kdtree.Point{&Point2D{X: 1, Y: 3}, &Point2D{X: 1, Y: 8}, &Point2D{X: 2, Y: 2}, &Point2D{X: 2, Y: 10}, &Point2D{X: 3, Y: 6}, &Point2D{X: 4, Y: 1}, &Point2D{X: 5, Y: 4}, &Point2D{X: 6, Y: 8}, &Point2D{X: 7, Y: 4}, &Point2D{X: 7, Y: 7}, &Point2D{X: 8, Y: 2}, &Point2D{X: 8, Y: 5}, &Point2D{X: 9, Y: 9}}), - input: &Point2D{X: 1., Y: 1.}, + name: "not existing", + treeInput: New( + []Point{ + &SamplePoint2D{X: 1, Y: 3}, + &SamplePoint2D{X: 1, Y: 8}, + &SamplePoint2D{X: 2, Y: 2}, + &SamplePoint2D{X: 2, Y: 10}, + &SamplePoint2D{X: 3, Y: 6}, + &SamplePoint2D{X: 4, Y: 1}, + &SamplePoint2D{X: 5, Y: 4}, + &SamplePoint2D{X: 6, Y: 8}, + &SamplePoint2D{X: 7, Y: 4}, + &SamplePoint2D{X: 7, Y: 7}, + &SamplePoint2D{X: 8, Y: 2}, + &SamplePoint2D{X: 8, Y: 5}, + &SamplePoint2D{X: 9, Y: 9}}), + input: &SamplePoint2D{X: 1., Y: 1.}, treeOutput: "[[[[{1.00 3.00} {2.00 2.00} {4.00 1.00}] {3.00 6.00} [{1.00 8.00} {2.00 10.00} ]] {5.00 4.00} [[{7.00 4.00} {8.00 2.00} {8.00 5.00}] {7.00 7.00} [{6.00 8.00} {9.00 9.00} ]]]]", output: nil, }, { - name: "remove leaf", - treeInput: kdtree.New([]kdtree.Point{&Point2D{X: 1, Y: 3}, &Point2D{X: 1, Y: 8}, &Point2D{X: 2, Y: 2}, &Point2D{X: 2, Y: 10}, &Point2D{X: 3, Y: 6}, &Point2D{X: 4, Y: 1}, &Point2D{X: 5, Y: 4}, &Point2D{X: 6, Y: 8}, &Point2D{X: 7, Y: 4}, &Point2D{X: 7, Y: 7}, &Point2D{X: 8, Y: 2}, &Point2D{X: 8, Y: 5}, &Point2D{X: 9, Y: 9}}), - input: &Point2D{X: 8., Y: 5.}, + name: "remove leaf", + treeInput: New( + []Point{ + &SamplePoint2D{X: 1, Y: 3}, + &SamplePoint2D{X: 1, Y: 8}, + &SamplePoint2D{X: 2, Y: 2}, + &SamplePoint2D{X: 2, Y: 10}, + &SamplePoint2D{X: 3, Y: 6}, + &SamplePoint2D{X: 4, Y: 1}, + &SamplePoint2D{X: 5, Y: 4}, + &SamplePoint2D{X: 6, Y: 8}, + &SamplePoint2D{X: 7, Y: 4}, + &SamplePoint2D{X: 7, Y: 7}, + &SamplePoint2D{X: 8, Y: 2}, + &SamplePoint2D{X: 8, Y: 5}, + &SamplePoint2D{X: 9, Y: 9}}), + input: &SamplePoint2D{X: 8., Y: 5.}, treeOutput: "[[[[{1.00 3.00} {2.00 2.00} {4.00 1.00}] {3.00 6.00} [{1.00 8.00} {2.00 10.00} ]] {5.00 4.00} [[{7.00 4.00} {8.00 2.00} ] {7.00 7.00} [{6.00 8.00} {9.00 9.00} ]]]]", - output: &Point2D{X: 8., Y: 5.}, - }, - { - name: "remove leaf", - treeInput: kdtree.New([]kdtree.Point{&Point2D{X: 1, Y: 3}, &Point2D{X: 1, Y: 8}, &Point2D{X: 2, Y: 2}, &Point2D{X: 2, Y: 10}, &Point2D{X: 3, Y: 6}, &Point2D{X: 4, Y: 1}, &Point2D{X: 5, Y: 4}, &Point2D{X: 6, Y: 8}, &Point2D{X: 7, Y: 4}, &Point2D{X: 7, Y: 7}, &Point2D{X: 8, Y: 2}, &Point2D{X: 8, Y: 5}, &Point2D{X: 9, Y: 9}}), - input: &Point2D{X: 6., Y: 8.}, + output: &SamplePoint2D{X: 8., Y: 5.}, + }, + { + name: "remove leaf", + treeInput: New([]Point{&SamplePoint2D{X: 1, Y: 3}, + &SamplePoint2D{X: 1, Y: 8}, + &SamplePoint2D{X: 2, Y: 2}, + &SamplePoint2D{X: 2, Y: 10}, + &SamplePoint2D{X: 3, Y: 6}, + &SamplePoint2D{X: 4, Y: 1}, + &SamplePoint2D{X: 5, Y: 4}, + &SamplePoint2D{X: 6, Y: 8}, + &SamplePoint2D{X: 7, Y: 4}, + &SamplePoint2D{X: 7, Y: 7}, + &SamplePoint2D{X: 8, Y: 2}, + &SamplePoint2D{X: 8, Y: 5}, + &SamplePoint2D{X: 9, Y: 9}}), + input: &SamplePoint2D{X: 6., Y: 8.}, treeOutput: "[[[[{1.00 3.00} {2.00 2.00} {4.00 1.00}] {3.00 6.00} [{1.00 8.00} {2.00 10.00} ]] {5.00 4.00} [[{7.00 4.00} {8.00 2.00} {8.00 5.00}] {7.00 7.00} {9.00 9.00}]]]", - output: &Point2D{X: 6., Y: 8.}, - }, - { - name: "remove with 1 replace, right child nil", - treeInput: kdtree.New([]kdtree.Point{&Point2D{X: 1, Y: 3}, &Point2D{X: 1, Y: 8}, &Point2D{X: 2, Y: 2}, &Point2D{X: 2, Y: 10}, &Point2D{X: 3, Y: 6}, &Point2D{X: 4, Y: 1}, &Point2D{X: 5, Y: 4}, &Point2D{X: 6, Y: 8}, &Point2D{X: 7, Y: 4}, &Point2D{X: 7, Y: 7}, &Point2D{X: 8, Y: 2}, &Point2D{X: 8, Y: 5}, &Point2D{X: 9, Y: 9}}), - input: &Point2D{X: 9., Y: 9.}, + output: &SamplePoint2D{X: 6., Y: 8.}, + }, + { + name: "remove with 1 replace, right child nil", + treeInput: New([]Point{&SamplePoint2D{X: 1, Y: 3}, + &SamplePoint2D{X: 1, Y: 8}, + &SamplePoint2D{X: 2, Y: 2}, + &SamplePoint2D{X: 2, Y: 10}, + &SamplePoint2D{X: 3, Y: 6}, + &SamplePoint2D{X: 4, Y: 1}, + &SamplePoint2D{X: 5, Y: 4}, + &SamplePoint2D{X: 6, Y: 8}, + &SamplePoint2D{X: 7, Y: 4}, + &SamplePoint2D{X: 7, Y: 7}, + &SamplePoint2D{X: 8, Y: 2}, + &SamplePoint2D{X: 8, Y: 5}, + &SamplePoint2D{X: 9, Y: 9}}), + input: &SamplePoint2D{X: 9., Y: 9.}, treeOutput: "[[[[{1.00 3.00} {2.00 2.00} {4.00 1.00}] {3.00 6.00} [{1.00 8.00} {2.00 10.00} ]] {5.00 4.00} [[{7.00 4.00} {8.00 2.00} {8.00 5.00}] {7.00 7.00} {6.00 8.00}]]]", - output: &Point2D{X: 9., Y: 9.}, - }, - { - name: "remove with 1 replace, left child nil", - treeInput: kdtree.New([]kdtree.Point{&Point2D{X: 1, Y: 3}, &Point2D{X: 1, Y: 8}, &Point2D{X: 2, Y: 2}, &Point2D{X: 2, Y: 10}, &Point2D{X: 3, Y: 6}, &Point2D{X: 4, Y: 1}, &Point2D{X: 5, Y: 4}, &Point2D{X: 6, Y: 8}, &Point2D{X: 7, Y: 4}, &Point2D{X: 7, Y: 7}, &Point2D{X: 8, Y: 2}, &Point2D{X: 8, Y: 5}, &Point2D{X: 9, Y: 9}}), - preRemove: []kdtree.Point{&Point2D{X: 1, Y: 3}}, - input: &Point2D{X: 2., Y: 2.}, + output: &SamplePoint2D{X: 9., Y: 9.}, + }, + { + name: "remove with 1 replace, left child nil", + treeInput: New([]Point{&SamplePoint2D{X: 1, Y: 3}, + &SamplePoint2D{X: 1, Y: 8}, + &SamplePoint2D{X: 2, Y: 2}, + &SamplePoint2D{X: 2, Y: 10}, + &SamplePoint2D{X: 3, Y: 6}, + &SamplePoint2D{X: 4, Y: 1}, + &SamplePoint2D{X: 5, Y: 4}, + &SamplePoint2D{X: 6, Y: 8}, + &SamplePoint2D{X: 7, Y: 4}, + &SamplePoint2D{X: 7, Y: 7}, + &SamplePoint2D{X: 8, Y: 2}, + &SamplePoint2D{X: 8, Y: 5}, + &SamplePoint2D{X: 9, Y: 9}}), + preRemove: []Point{&SamplePoint2D{X: 1, Y: 3}}, + input: &SamplePoint2D{X: 2., Y: 2.}, treeOutput: "[[[{4.00 1.00} {3.00 6.00} [{1.00 8.00} {2.00 10.00} ]] {5.00 4.00} [[{7.00 4.00} {8.00 2.00} {8.00 5.00}] {7.00 7.00} [{6.00 8.00} {9.00 9.00} ]]]]", - output: &Point2D{X: 2., Y: 2.}, - }, - { - name: "remove with 1 replace", - treeInput: kdtree.New([]kdtree.Point{&Point2D{X: 1, Y: 3}, &Point2D{X: 1, Y: 8}, &Point2D{X: 2, Y: 2}, &Point2D{X: 2, Y: 10}, &Point2D{X: 3, Y: 6}, &Point2D{X: 4, Y: 1}, &Point2D{X: 5, Y: 4}, &Point2D{X: 6, Y: 8}, &Point2D{X: 7, Y: 4}, &Point2D{X: 7, Y: 7}, &Point2D{X: 8, Y: 2}, &Point2D{X: 8, Y: 5}, &Point2D{X: 9, Y: 9}}), - input: &Point2D{X: 8., Y: 2.}, + output: &SamplePoint2D{X: 2., Y: 2.}, + }, + { + name: "remove with 1 replace", + treeInput: New([]Point{&SamplePoint2D{X: 1, Y: 3}, + &SamplePoint2D{X: 1, Y: 8}, + &SamplePoint2D{X: 2, Y: 2}, + &SamplePoint2D{X: 2, Y: 10}, + &SamplePoint2D{X: 3, Y: 6}, + &SamplePoint2D{X: 4, Y: 1}, + &SamplePoint2D{X: 5, Y: 4}, + &SamplePoint2D{X: 6, Y: 8}, + &SamplePoint2D{X: 7, Y: 4}, + &SamplePoint2D{X: 7, Y: 7}, + &SamplePoint2D{X: 8, Y: 2}, + &SamplePoint2D{X: 8, Y: 5}, + &SamplePoint2D{X: 9, Y: 9}}), + input: &SamplePoint2D{X: 8., Y: 2.}, treeOutput: "[[[[{1.00 3.00} {2.00 2.00} {4.00 1.00}] {3.00 6.00} [{1.00 8.00} {2.00 10.00} ]] {5.00 4.00} [[ {7.00 4.00} {8.00 5.00}] {7.00 7.00} [{6.00 8.00} {9.00 9.00} ]]]]", - output: &Point2D{X: 8., Y: 2.}, - }, - { - name: "remove with 1 replace, deep", - treeInput: kdtree.New([]kdtree.Point{&Point2D{X: 1, Y: 3}, &Point2D{X: 1, Y: 8}, &Point2D{X: 2, Y: 2}, &Point2D{X: 2, Y: 10}, &Point2D{X: 3, Y: 6}, &Point2D{X: 4, Y: 1}, &Point2D{X: 5, Y: 4}, &Point2D{X: 6, Y: 8}, &Point2D{X: 7, Y: 4}, &Point2D{X: 7, Y: 7}, &Point2D{X: 8, Y: 2}, &Point2D{X: 8, Y: 5}, &Point2D{X: 9, Y: 9}}), - input: &Point2D{X: 3., Y: 6.}, + output: &SamplePoint2D{X: 8., Y: 2.}, + }, + { + name: "remove with 1 replace, deep", + treeInput: New([]Point{&SamplePoint2D{X: 1, Y: 3}, + &SamplePoint2D{X: 1, Y: 8}, + &SamplePoint2D{X: 2, Y: 2}, + &SamplePoint2D{X: 2, Y: 10}, + &SamplePoint2D{X: 3, Y: 6}, + &SamplePoint2D{X: 4, Y: 1}, + &SamplePoint2D{X: 5, Y: 4}, + &SamplePoint2D{X: 6, Y: 8}, + &SamplePoint2D{X: 7, Y: 4}, + &SamplePoint2D{X: 7, Y: 7}, + &SamplePoint2D{X: 8, Y: 2}, + &SamplePoint2D{X: 8, Y: 5}, + &SamplePoint2D{X: 9, Y: 9}}), + input: &SamplePoint2D{X: 3., Y: 6.}, treeOutput: "[[[[ {2.00 2.00} {4.00 1.00}] {1.00 3.00} [{1.00 8.00} {2.00 10.00} ]] {5.00 4.00} [[{7.00 4.00} {8.00 2.00} {8.00 5.00}] {7.00 7.00} [{6.00 8.00} {9.00 9.00} ]]]]", - output: &Point2D{X: 3., Y: 6.}, - }, - { - name: "remove with 1 replace, deep", - treeInput: kdtree.New([]kdtree.Point{&Point2D{X: 1, Y: 3}, &Point2D{X: 1, Y: 8}, &Point2D{X: 2, Y: 2}, &Point2D{X: 2, Y: 10}, &Point2D{X: 3, Y: 6}, &Point2D{X: 4, Y: 1}, &Point2D{X: 5, Y: 4}, &Point2D{X: 6, Y: 8}, &Point2D{X: 7, Y: 4}, &Point2D{X: 7, Y: 7}, &Point2D{X: 8, Y: 2}, &Point2D{X: 8, Y: 5}, &Point2D{X: 9, Y: 9}}), - input: &Point2D{X: 7., Y: 7.}, + output: &SamplePoint2D{X: 3., Y: 6.}, + }, + { + name: "remove with 1 replace, deep", + treeInput: New([]Point{&SamplePoint2D{X: 1, Y: 3}, + &SamplePoint2D{X: 1, Y: 8}, + &SamplePoint2D{X: 2, Y: 2}, + &SamplePoint2D{X: 2, Y: 10}, + &SamplePoint2D{X: 3, Y: 6}, + &SamplePoint2D{X: 4, Y: 1}, + &SamplePoint2D{X: 5, Y: 4}, + &SamplePoint2D{X: 6, Y: 8}, + &SamplePoint2D{X: 7, Y: 4}, + &SamplePoint2D{X: 7, Y: 7}, + &SamplePoint2D{X: 8, Y: 2}, + &SamplePoint2D{X: 8, Y: 5}, + &SamplePoint2D{X: 9, Y: 9}}), + input: &SamplePoint2D{X: 7., Y: 7.}, treeOutput: "[[[[{1.00 3.00} {2.00 2.00} {4.00 1.00}] {3.00 6.00} [{1.00 8.00} {2.00 10.00} ]] {5.00 4.00} [[{7.00 4.00} {8.00 2.00} ] {8.00 5.00} [{6.00 8.00} {9.00 9.00} ]]]]", - output: &Point2D{X: 7., Y: 7.}, - }, - { - name: "remove with left nil", - treeInput: kdtree.New([]kdtree.Point{&Point2D{X: 1, Y: 3}, &Point2D{X: 1, Y: 8}, &Point2D{X: 2, Y: 2}, &Point2D{X: 2, Y: 10}, &Point2D{X: 3, Y: 6}, &Point2D{X: 4, Y: 1}, &Point2D{X: 5, Y: 4}, &Point2D{X: 6, Y: 8}, &Point2D{X: 7, Y: 4}, &Point2D{X: 7, Y: 7}, &Point2D{X: 8, Y: 2}, &Point2D{X: 8, Y: 5}, &Point2D{X: 9, Y: 9}}), - preRemove: []kdtree.Point{&Point2D{X: 4, Y: 1}, &Point2D{X: 1, Y: 3}, &Point2D{X: 2, Y: 2}, &Point2D{X: 1, Y: 8}, &Point2D{X: 2, Y: 10}, &Point2D{X: 3, Y: 6}}, - input: &Point2D{X: 5., Y: 4.}, + output: &SamplePoint2D{X: 7., Y: 7.}, + }, + { + name: "remove with left nil", + treeInput: New([]Point{&SamplePoint2D{X: 1, Y: 3}, + &SamplePoint2D{X: 1, Y: 8}, + &SamplePoint2D{X: 2, Y: 2}, + &SamplePoint2D{X: 2, Y: 10}, + &SamplePoint2D{X: 3, Y: 6}, + &SamplePoint2D{X: 4, Y: 1}, + &SamplePoint2D{X: 5, Y: 4}, + &SamplePoint2D{X: 6, Y: 8}, + &SamplePoint2D{X: 7, Y: 4}, + &SamplePoint2D{X: 7, Y: 7}, + &SamplePoint2D{X: 8, Y: 2}, + &SamplePoint2D{X: 8, Y: 5}, + &SamplePoint2D{X: 9, Y: 9}}), + preRemove: []Point{&SamplePoint2D{X: 4, Y: 1}, + &SamplePoint2D{X: 1, Y: 3}, + &SamplePoint2D{X: 2, Y: 2}, + &SamplePoint2D{X: 1, Y: 8}, + &SamplePoint2D{X: 2, Y: 10}, + &SamplePoint2D{X: 3, Y: 6}}, + input: &SamplePoint2D{X: 5., Y: 4.}, treeOutput: "[[ {6.00 8.00} [[{7.00 4.00} {8.00 2.00} {8.00 5.00}] {7.00 7.00} {9.00 9.00}]]]", - output: &Point2D{X: 5., Y: 4.}, - }, - { - name: "remove with sub left nil", - treeInput: kdtree.New([]kdtree.Point{&Point2D{X: 1, Y: 3}, &Point2D{X: 1, Y: 8}, &Point2D{X: 2, Y: 2}, &Point2D{X: 2, Y: 10}, &Point2D{X: 3, Y: 6}, &Point2D{X: 4, Y: 1}, &Point2D{X: 5, Y: 4}, &Point2D{X: 6, Y: 8}, &Point2D{X: 7, Y: 4}, &Point2D{X: 7, Y: 7}, &Point2D{X: 8, Y: 2}, &Point2D{X: 8, Y: 5}, &Point2D{X: 9, Y: 9}}), - preRemove: []kdtree.Point{&Point2D{X: 4, Y: 1}, &Point2D{X: 1, Y: 3}, &Point2D{X: 2, Y: 2}}, - input: &Point2D{X: 5., Y: 4.}, + output: &SamplePoint2D{X: 5., Y: 4.}, + }, + { + name: "remove with sub left nil", + treeInput: New([]Point{&SamplePoint2D{X: 1, Y: 3}, + &SamplePoint2D{X: 1, Y: 8}, + &SamplePoint2D{X: 2, Y: 2}, + &SamplePoint2D{X: 2, Y: 10}, + &SamplePoint2D{X: 3, Y: 6}, + &SamplePoint2D{X: 4, Y: 1}, + &SamplePoint2D{X: 5, Y: 4}, + &SamplePoint2D{X: 6, Y: 8}, + &SamplePoint2D{X: 7, Y: 4}, + &SamplePoint2D{X: 7, Y: 7}, + &SamplePoint2D{X: 8, Y: 2}, + &SamplePoint2D{X: 8, Y: 5}, + &SamplePoint2D{X: 9, Y: 9}}), + preRemove: []Point{&SamplePoint2D{X: 4, Y: 1}, + &SamplePoint2D{X: 1, Y: 3}, + &SamplePoint2D{X: 2, Y: 2}}, + input: &SamplePoint2D{X: 5., Y: 4.}, treeOutput: "[[[ {1.00 8.00} {2.00 10.00}] {3.00 6.00} [[{7.00 4.00} {8.00 2.00} {8.00 5.00}] {7.00 7.00} [{6.00 8.00} {9.00 9.00} ]]]]", - output: &Point2D{X: 5., Y: 4.}, + output: &SamplePoint2D{X: 5., Y: 4.}, }, // x (4,1) // y (1,3) (5,4) @@ -264,18 +404,78 @@ func TestKDTree_Remove(t *testing.T) { // x (2,1) (1,3) (3,1) (3,3) (1,8) (2,10) (4,4) (3,9) (6,2) (6,4) (8,2) (7,4) (6,5) (6,8) (9,6) (9,9) // [[[[[{2.00 1.00} {2.00 2.00} {1.00 3.00}] {3.00 1.00} [{3.00 1.00} {4.00 2.00} {3.00 3.00}]] {1.00 3.00} [[{1.00 8.00} {2.00 8.00} {2.00 10.00}] {3.00 6.00} [{4.00 4.00} {3.00 8.00} {3.00 9.00}]]] {4.00 1.00} [[[{6.00 2.00} {5.00 3.00} {6.00 4.00}] {7.00 3.00} [{8.00 2.00} {9.00 2.00} {7.00 4.00}]] {5.00 4.00} [[{6.00 5.00} {7.00 7.00} {6.00 8.00}] {8.00 5.00} [{9.00 6.00} {9.00 8.00} {9.00 9.00}]]]]] { - name: "remove (3,1) with 2 replace", - treeInput: kdtree.New([]kdtree.Point{&Point2D{X: 1, Y: 3}, &Point2D{X: 1, Y: 8}, &Point2D{X: 2, Y: 2}, &Point2D{X: 2, Y: 10}, &Point2D{X: 3, Y: 6}, &Point2D{X: 4, Y: 1}, &Point2D{X: 5, Y: 4}, &Point2D{X: 6, Y: 8}, &Point2D{X: 7, Y: 4}, &Point2D{X: 7, Y: 7}, &Point2D{X: 8, Y: 2}, &Point2D{X: 8, Y: 5}, &Point2D{X: 9, Y: 9}, &Point2D{X: 3, Y: 1}, &Point2D{X: 4, Y: 2}, &Point2D{X: 9, Y: 2}, &Point2D{X: 6, Y: 5}, &Point2D{X: 3, Y: 8}, &Point2D{X: 6, Y: 2}, &Point2D{X: 1, Y: 3}, &Point2D{X: 3, Y: 3}, &Point2D{X: 6, Y: 4}, &Point2D{X: 9, Y: 8}, &Point2D{X: 2, Y: 1}, &Point2D{X: 2, Y: 8}, &Point2D{X: 3, Y: 1}, &Point2D{X: 7, Y: 3}, &Point2D{X: 3, Y: 9}, &Point2D{X: 4, Y: 4}, &Point2D{X: 5, Y: 3}, &Point2D{X: 9, Y: 6}}), - input: &Point2D{X: 3., Y: 1.}, + name: "remove (3,1) with 2 replace", + treeInput: New([]Point{&SamplePoint2D{X: 1, Y: 3}, + &SamplePoint2D{X: 1, Y: 8}, + &SamplePoint2D{X: 2, Y: 2}, + &SamplePoint2D{X: 2, Y: 10}, + &SamplePoint2D{X: 3, Y: 6}, + &SamplePoint2D{X: 4, Y: 1}, + &SamplePoint2D{X: 5, Y: 4}, + &SamplePoint2D{X: 6, Y: 8}, + &SamplePoint2D{X: 7, Y: 4}, + &SamplePoint2D{X: 7, Y: 7}, + &SamplePoint2D{X: 8, Y: 2}, + &SamplePoint2D{X: 8, Y: 5}, + &SamplePoint2D{X: 9, Y: 9}, + &SamplePoint2D{X: 3, Y: 1}, + &SamplePoint2D{X: 4, Y: 2}, + &SamplePoint2D{X: 9, Y: 2}, + &SamplePoint2D{X: 6, Y: 5}, + &SamplePoint2D{X: 3, Y: 8}, + &SamplePoint2D{X: 6, Y: 2}, + &SamplePoint2D{X: 1, Y: 3}, + &SamplePoint2D{X: 3, Y: 3}, + &SamplePoint2D{X: 6, Y: 4}, + &SamplePoint2D{X: 9, Y: 8}, + &SamplePoint2D{X: 2, Y: 1}, + &SamplePoint2D{X: 2, Y: 8}, + &SamplePoint2D{X: 3, Y: 1}, + &SamplePoint2D{X: 7, Y: 3}, + &SamplePoint2D{X: 3, Y: 9}, + &SamplePoint2D{X: 4, Y: 4}, + &SamplePoint2D{X: 5, Y: 3}, + &SamplePoint2D{X: 9, Y: 6}}), + input: &SamplePoint2D{X: 3., Y: 1.}, treeOutput: "[[[[[ {2.00 1.00} {1.00 3.00}] {2.00 2.00} [{3.00 1.00} {4.00 2.00} {3.00 3.00}]] {1.00 3.00} [[{1.00 8.00} {2.00 8.00} {2.00 10.00}] {3.00 6.00} [{4.00 4.00} {3.00 8.00} {3.00 9.00}]]] {4.00 1.00} [[[{6.00 2.00} {5.00 3.00} {6.00 4.00}] {7.00 3.00} [{8.00 2.00} {9.00 2.00} {7.00 4.00}]] {5.00 4.00} [[{6.00 5.00} {7.00 7.00} {6.00 8.00}] {8.00 5.00} [{9.00 6.00} {9.00 8.00} {9.00 9.00}]]]]]", - output: &Point2D{X: 3., Y: 1.}, - }, - { - name: "remove (5,4) with 1 replace, deep 3", - treeInput: kdtree.New([]kdtree.Point{&Point2D{X: 1, Y: 3}, &Point2D{X: 1, Y: 8}, &Point2D{X: 2, Y: 2}, &Point2D{X: 2, Y: 10}, &Point2D{X: 3, Y: 6}, &Point2D{X: 4, Y: 1}, &Point2D{X: 5, Y: 4}, &Point2D{X: 6, Y: 8}, &Point2D{X: 7, Y: 4}, &Point2D{X: 7, Y: 7}, &Point2D{X: 8, Y: 2}, &Point2D{X: 8, Y: 5}, &Point2D{X: 9, Y: 9}, &Point2D{X: 3, Y: 1}, &Point2D{X: 4, Y: 2}, &Point2D{X: 9, Y: 2}, &Point2D{X: 6, Y: 5}, &Point2D{X: 3, Y: 8}, &Point2D{X: 6, Y: 2}, &Point2D{X: 1, Y: 3}, &Point2D{X: 3, Y: 3}, &Point2D{X: 6, Y: 4}, &Point2D{X: 9, Y: 8}, &Point2D{X: 2, Y: 1}, &Point2D{X: 2, Y: 8}, &Point2D{X: 3, Y: 1}, &Point2D{X: 7, Y: 3}, &Point2D{X: 3, Y: 9}, &Point2D{X: 4, Y: 4}, &Point2D{X: 5, Y: 3}, &Point2D{X: 9, Y: 6}}), - input: &Point2D{X: 5., Y: 4.}, + output: &SamplePoint2D{X: 3., Y: 1.}, + }, + { + name: "remove (5,4) with 1 replace, deep 3", + treeInput: New([]Point{&SamplePoint2D{X: 1, Y: 3}, + &SamplePoint2D{X: 1, Y: 8}, + &SamplePoint2D{X: 2, Y: 2}, + &SamplePoint2D{X: 2, Y: 10}, + &SamplePoint2D{X: 3, Y: 6}, + &SamplePoint2D{X: 4, Y: 1}, + &SamplePoint2D{X: 5, Y: 4}, + &SamplePoint2D{X: 6, Y: 8}, + &SamplePoint2D{X: 7, Y: 4}, + &SamplePoint2D{X: 7, Y: 7}, + &SamplePoint2D{X: 8, Y: 2}, + &SamplePoint2D{X: 8, Y: 5}, + &SamplePoint2D{X: 9, Y: 9}, + &SamplePoint2D{X: 3, Y: 1}, + &SamplePoint2D{X: 4, Y: 2}, + &SamplePoint2D{X: 9, Y: 2}, + &SamplePoint2D{X: 6, Y: 5}, + &SamplePoint2D{X: 3, Y: 8}, + &SamplePoint2D{X: 6, Y: 2}, + &SamplePoint2D{X: 1, Y: 3}, + &SamplePoint2D{X: 3, Y: 3}, + &SamplePoint2D{X: 6, Y: 4}, + &SamplePoint2D{X: 9, Y: 8}, + &SamplePoint2D{X: 2, Y: 1}, + &SamplePoint2D{X: 2, Y: 8}, + &SamplePoint2D{X: 3, Y: 1}, + &SamplePoint2D{X: 7, Y: 3}, + &SamplePoint2D{X: 3, Y: 9}, + &SamplePoint2D{X: 4, Y: 4}, + &SamplePoint2D{X: 5, Y: 3}, + &SamplePoint2D{X: 9, Y: 6}}), + input: &SamplePoint2D{X: 5., Y: 4.}, treeOutput: "[[[[[{2.00 1.00} {2.00 2.00} {1.00 3.00}] {3.00 1.00} [{3.00 1.00} {4.00 2.00} {3.00 3.00}]] {1.00 3.00} [[{1.00 8.00} {2.00 8.00} {2.00 10.00}] {3.00 6.00} [{4.00 4.00} {3.00 8.00} {3.00 9.00}]]] {4.00 1.00} [[[{6.00 2.00} {5.00 3.00} ] {7.00 3.00} [{8.00 2.00} {9.00 2.00} {7.00 4.00}]] {6.00 4.00} [[{6.00 5.00} {7.00 7.00} {6.00 8.00}] {8.00 5.00} [{9.00 6.00} {9.00 8.00} {9.00 9.00}]]]]]", - output: &Point2D{X: 5., Y: 4.}, + output: &SamplePoint2D{X: 5., Y: 4.}, }, } for _, test := range tests { @@ -286,7 +486,7 @@ func TestKDTree_Remove(t *testing.T) { } } o := test.treeInput.Remove(test.input) - if c, ok := o.(kdtree.Point); ok { + if c, ok := o.(Point); ok { assertPointsEqual(t, test.output, c) } else { assert.Equal(t, test.output, o) @@ -299,13 +499,13 @@ func TestKDTree_Remove(t *testing.T) { func TestKDTree_Balance(t *testing.T) { tests := []struct { name string - treeInput *kdtree.KDTree - preRemove []kdtree.Point + treeInput *KDTree + preRemove []Point treeOutput string }{ { name: "empty tree", - treeInput: kdtree.New([]kdtree.Point{}), + treeInput: New([]Point{}), treeOutput: "[]", }, } @@ -325,7 +525,7 @@ func TestKDTree_Balance(t *testing.T) { func TestKDTree_BalanceNoNilNode(t *testing.T) { tests := []struct { name string - input []kdtree.Point + input []Point add int remove int }{ @@ -344,7 +544,7 @@ func TestKDTree_BalanceNoNilNode(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - tree := kdtree.New(test.input) + tree := New(test.input) for i := 0; i < test.remove; i++ { tree.Remove(test.input[i]) } @@ -361,43 +561,61 @@ func TestKDTree_BalanceNoNilNode(t *testing.T) { func TestKDTree_KNN(t *testing.T) { tests := []struct { name string - target kdtree.Point + target Point k int - input []kdtree.Point - output []kdtree.Point + input []Point + output []Point }{ { name: "nil", target: nil, k: 3, - input: []kdtree.Point{&Point2D{X: 1., Y: 2.}}, - output: []kdtree.Point{}, + input: []Point{&SamplePoint2D{X: 1., Y: 2.}}, + output: []Point{}, }, { name: "empty", - target: &Point2D{X: 1., Y: 2.}, + target: &SamplePoint2D{X: 1., Y: 2.}, k: 3, - input: []kdtree.Point{}, - output: []kdtree.Point{}, + input: []Point{}, + output: []Point{}, }, { name: "k >> points", - target: &Point2D{X: 1., Y: 2.}, + target: &SamplePoint2D{X: 1., Y: 2.}, k: 10, - input: []kdtree.Point{&Point2D{X: 1., Y: 2.}, &Point2D{X: 0.9, Y: 2.1}, &Point2D{X: 1.1, Y: 1.9}}, - output: []kdtree.Point{&Point2D{X: 1., Y: 2.}, &Point2D{X: 0.9, Y: 2.1}, &Point2D{X: 1.1, Y: 1.9}}, + input: []Point{&SamplePoint2D{X: 1., Y: 2.}, + &SamplePoint2D{X: 0.9, Y: 2.1}, + &SamplePoint2D{X: 1.1, Y: 1.9}}, + output: []Point{&SamplePoint2D{X: 1., Y: 2.}, + &SamplePoint2D{X: 0.9, Y: 2.1}, + &SamplePoint2D{X: 1.1, Y: 1.9}}, }, { name: "small 2D example", - target: &Point2D{X: 9, Y: 4}, + target: &SamplePoint2D{X: 9, Y: 4}, k: 3, - input: []kdtree.Point{&Point2D{X: 1, Y: 3}, &Point2D{X: 1, Y: 8}, &Point2D{X: 2, Y: 2}, &Point2D{X: 2, Y: 10}, &Point2D{X: 3, Y: 6}, &Point2D{X: 4, Y: 1}, &Point2D{X: 5, Y: 4}, &Point2D{X: 6, Y: 8}, &Point2D{X: 7, Y: 4}, &Point2D{X: 7, Y: 7}, &Point2D{X: 8, Y: 2}, &Point2D{X: 8, Y: 5}, &Point2D{X: 9, Y: 9}}, - output: []kdtree.Point{&Point2D{X: 8, Y: 5}, &Point2D{X: 7, Y: 4}, &Point2D{X: 8, Y: 2}}, + input: []Point{&SamplePoint2D{X: 1, Y: 3}, + &SamplePoint2D{X: 1, Y: 8}, + &SamplePoint2D{X: 2, Y: 2}, + &SamplePoint2D{X: 2, Y: 10}, + &SamplePoint2D{X: 3, Y: 6}, + &SamplePoint2D{X: 4, Y: 1}, + &SamplePoint2D{X: 5, Y: 4}, + &SamplePoint2D{X: 6, Y: 8}, + &SamplePoint2D{X: 7, Y: 4}, + &SamplePoint2D{X: 7, Y: 7}, + &SamplePoint2D{X: 8, Y: 2}, + &SamplePoint2D{X: 8, Y: 5}, + &SamplePoint2D{X: 9, Y: 9}}, + output: []Point{&SamplePoint2D{X: 8, Y: 5}, + &SamplePoint2D{X: 7, Y: 4}, + &SamplePoint2D{X: 8, Y: 2}}, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - tree := kdtree.New(test.input) + tree := New(test.input) assert.Equal(t, test.output, tree.KNN(test.target, test.k)) }) } @@ -406,21 +624,21 @@ func TestKDTree_KNN(t *testing.T) { func TestKDTree_KNNWithGenerator(t *testing.T) { tests := []struct { name string - target kdtree.Point + target Point k int - input []kdtree.Point + input []Point }{ - {name: "p:100,k:5", target: &Point2D{}, k: 5, input: generateTestCaseData(100)}, - {name: "p:1000,k:5", target: &Point2D{}, k: 5, input: generateTestCaseData(1000)}, - {name: "p:10000,k:5", target: &Point2D{}, k: 5, input: generateTestCaseData(10000)}, - {name: "p:100000,k:5", target: &Point2D{}, k: 5, input: generateTestCaseData(100000)}, - {name: "p:1000000,k:10", target: &Point2D{}, k: 10, input: generateTestCaseData(1000000)}, - {name: "p:1000000,k:20", target: &Point2D{}, k: 20, input: generateTestCaseData(1000000)}, - {name: "p:1000000,k:30", target: &Point2D{}, k: 30, input: generateTestCaseData(1000000)}, + {name: "p:100,k:5", target: &SamplePoint2D{}, k: 5, input: generateTestCaseData(100)}, + {name: "p:1000,k:5", target: &SamplePoint2D{}, k: 5, input: generateTestCaseData(1000)}, + {name: "p:10000,k:5", target: &SamplePoint2D{}, k: 5, input: generateTestCaseData(10000)}, + {name: "p:100000,k:5", target: &SamplePoint2D{}, k: 5, input: generateTestCaseData(100000)}, + {name: "p:1000000,k:10", target: &SamplePoint2D{}, k: 10, input: generateTestCaseData(1000000)}, + {name: "p:1000000,k:20", target: &SamplePoint2D{}, k: 20, input: generateTestCaseData(1000000)}, + {name: "p:1000000,k:30", target: &SamplePoint2D{}, k: 30, input: generateTestCaseData(1000000)}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - tree := kdtree.New(test.input) + tree := New(test.input) assert.Equal(t, prioQueueKNN(test.input, test.target, test.k), tree.KNN(test.target, test.k)) }) } @@ -429,45 +647,165 @@ func TestKDTree_KNNWithGenerator(t *testing.T) { func TestKDTree_RangeSearch(t *testing.T) { tests := []struct { name string - tree *kdtree.KDTree - input kdrange.Range - expected []kdtree.Point + tree *KDTree + input kdrangeRange + expected []Point }{ { name: "nil", - tree: kdtree.New(generateTestCaseData(5)), + tree: New(generateTestCaseData(5)), input: nil, - expected: []kdtree.Point{}, + expected: []Point{}, }, { name: "wrong dim", - tree: kdtree.New(generateTestCaseData(5)), - input: kdrange.New(), - expected: []kdtree.Point{}, - }, - { - name: "out of range x (lower)", - tree: kdtree.New([]kdtree.Point{&Point2D{X: 1, Y: 3}, &Point2D{X: 1, Y: 8}, &Point2D{X: 2, Y: 2}, &Point2D{X: 2, Y: 10}, &Point2D{X: 3, Y: 6}, &Point2D{X: 4, Y: 1}, &Point2D{X: 5, Y: 4}, &Point2D{X: 6, Y: 8}, &Point2D{X: 7, Y: 4}, &Point2D{X: 7, Y: 7}, &Point2D{X: 8, Y: 2}, &Point2D{X: 8, Y: 5}, &Point2D{X: 9, Y: 9}, &Point2D{X: 3, Y: 1}, &Point2D{X: 4, Y: 2}, &Point2D{X: 9, Y: 2}, &Point2D{X: 6, Y: 5}, &Point2D{X: 3, Y: 8}, &Point2D{X: 6, Y: 2}, &Point2D{X: 1, Y: 3}, &Point2D{X: 3, Y: 3}, &Point2D{X: 6, Y: 4}, &Point2D{X: 9, Y: 8}, &Point2D{X: 2, Y: 1}, &Point2D{X: 2, Y: 8}, &Point2D{X: 3, Y: 1}, &Point2D{X: 7, Y: 3}, &Point2D{X: 3, Y: 9}, &Point2D{X: 4, Y: 4}, &Point2D{X: 5, Y: 3}, &Point2D{X: 9, Y: 6}}), - input: kdrange.New(-2, -1, 2, 10), - expected: []kdtree.Point{}, - }, - { - name: "out of range y (lower)", - tree: kdtree.New([]kdtree.Point{&Point2D{X: 1, Y: 3}, &Point2D{X: 1, Y: 8}, &Point2D{X: 2, Y: 2}, &Point2D{X: 2, Y: 10}, &Point2D{X: 3, Y: 6}, &Point2D{X: 4, Y: 1}, &Point2D{X: 5, Y: 4}, &Point2D{X: 6, Y: 8}, &Point2D{X: 7, Y: 4}, &Point2D{X: 7, Y: 7}, &Point2D{X: 8, Y: 2}, &Point2D{X: 8, Y: 5}, &Point2D{X: 9, Y: 9}, &Point2D{X: 3, Y: 1}, &Point2D{X: 4, Y: 2}, &Point2D{X: 9, Y: 2}, &Point2D{X: 6, Y: 5}, &Point2D{X: 3, Y: 8}, &Point2D{X: 6, Y: 2}, &Point2D{X: 1, Y: 3}, &Point2D{X: 3, Y: 3}, &Point2D{X: 6, Y: 4}, &Point2D{X: 9, Y: 8}, &Point2D{X: 2, Y: 1}, &Point2D{X: 2, Y: 8}, &Point2D{X: 3, Y: 1}, &Point2D{X: 7, Y: 3}, &Point2D{X: 3, Y: 9}, &Point2D{X: 4, Y: 4}, &Point2D{X: 5, Y: 3}, &Point2D{X: 9, Y: 6}}), - input: kdrange.New(2, 10, -2, -1), - expected: []kdtree.Point{}, - }, - { - name: "out of range x (higher)", - tree: kdtree.New([]kdtree.Point{&Point2D{X: 1, Y: 3}, &Point2D{X: 1, Y: 8}, &Point2D{X: 2, Y: 2}, &Point2D{X: 2, Y: 10}, &Point2D{X: 3, Y: 6}, &Point2D{X: 4, Y: 1}, &Point2D{X: 5, Y: 4}, &Point2D{X: 6, Y: 8}, &Point2D{X: 7, Y: 4}, &Point2D{X: 7, Y: 7}, &Point2D{X: 8, Y: 2}, &Point2D{X: 8, Y: 5}, &Point2D{X: 9, Y: 9}, &Point2D{X: 3, Y: 1}, &Point2D{X: 4, Y: 2}, &Point2D{X: 9, Y: 2}, &Point2D{X: 6, Y: 5}, &Point2D{X: 3, Y: 8}, &Point2D{X: 6, Y: 2}, &Point2D{X: 1, Y: 3}, &Point2D{X: 3, Y: 3}, &Point2D{X: 6, Y: 4}, &Point2D{X: 9, Y: 8}, &Point2D{X: 2, Y: 1}, &Point2D{X: 2, Y: 8}, &Point2D{X: 3, Y: 1}, &Point2D{X: 7, Y: 3}, &Point2D{X: 3, Y: 9}, &Point2D{X: 4, Y: 4}, &Point2D{X: 5, Y: 3}, &Point2D{X: 9, Y: 6}}), - input: kdrange.New(20, 30, 2, 10), - expected: []kdtree.Point{}, - }, - { - name: "out of range y (higher)", - tree: kdtree.New([]kdtree.Point{&Point2D{X: 1, Y: 3}, &Point2D{X: 1, Y: 8}, &Point2D{X: 2, Y: 2}, &Point2D{X: 2, Y: 10}, &Point2D{X: 3, Y: 6}, &Point2D{X: 4, Y: 1}, &Point2D{X: 5, Y: 4}, &Point2D{X: 6, Y: 8}, &Point2D{X: 7, Y: 4}, &Point2D{X: 7, Y: 7}, &Point2D{X: 8, Y: 2}, &Point2D{X: 8, Y: 5}, &Point2D{X: 9, Y: 9}, &Point2D{X: 3, Y: 1}, &Point2D{X: 4, Y: 2}, &Point2D{X: 9, Y: 2}, &Point2D{X: 6, Y: 5}, &Point2D{X: 3, Y: 8}, &Point2D{X: 6, Y: 2}, &Point2D{X: 1, Y: 3}, &Point2D{X: 3, Y: 3}, &Point2D{X: 6, Y: 4}, &Point2D{X: 9, Y: 8}, &Point2D{X: 2, Y: 1}, &Point2D{X: 2, Y: 8}, &Point2D{X: 3, Y: 1}, &Point2D{X: 7, Y: 3}, &Point2D{X: 3, Y: 9}, &Point2D{X: 4, Y: 4}, &Point2D{X: 5, Y: 3}, &Point2D{X: 9, Y: 6}}), - input: kdrange.New(2, 10, 20, 30), - expected: []kdtree.Point{}, + tree: New(generateTestCaseData(5)), + input: NewKDRange(), + expected: []Point{}, + }, + { + name: "out of range x (lower)", + tree: New([]Point{&SamplePoint2D{X: 1, Y: 3}, + &SamplePoint2D{X: 1, Y: 8}, + &SamplePoint2D{X: 2, Y: 2}, + &SamplePoint2D{X: 2, Y: 10}, + &SamplePoint2D{X: 3, Y: 6}, + &SamplePoint2D{X: 4, Y: 1}, + &SamplePoint2D{X: 5, Y: 4}, + &SamplePoint2D{X: 6, Y: 8}, + &SamplePoint2D{X: 7, Y: 4}, + &SamplePoint2D{X: 7, Y: 7}, + &SamplePoint2D{X: 8, Y: 2}, + &SamplePoint2D{X: 8, Y: 5}, + &SamplePoint2D{X: 9, Y: 9}, + &SamplePoint2D{X: 3, Y: 1}, + &SamplePoint2D{X: 4, Y: 2}, + &SamplePoint2D{X: 9, Y: 2}, + &SamplePoint2D{X: 6, Y: 5}, + &SamplePoint2D{X: 3, Y: 8}, + &SamplePoint2D{X: 6, Y: 2}, + &SamplePoint2D{X: 1, Y: 3}, + &SamplePoint2D{X: 3, Y: 3}, + &SamplePoint2D{X: 6, Y: 4}, + &SamplePoint2D{X: 9, Y: 8}, + &SamplePoint2D{X: 2, Y: 1}, + &SamplePoint2D{X: 2, Y: 8}, + &SamplePoint2D{X: 3, Y: 1}, + &SamplePoint2D{X: 7, Y: 3}, + &SamplePoint2D{X: 3, Y: 9}, + &SamplePoint2D{X: 4, Y: 4}, + &SamplePoint2D{X: 5, Y: 3}, + &SamplePoint2D{X: 9, Y: 6}}), + input: NewKDRange(-2, -1, 2, 10), + expected: []Point{}, + }, + { + name: "out of range y (lower)", + tree: New([]Point{&SamplePoint2D{X: 1, Y: 3}, + &SamplePoint2D{X: 1, Y: 8}, + &SamplePoint2D{X: 2, Y: 2}, + &SamplePoint2D{X: 2, Y: 10}, + &SamplePoint2D{X: 3, Y: 6}, + &SamplePoint2D{X: 4, Y: 1}, + &SamplePoint2D{X: 5, Y: 4}, + &SamplePoint2D{X: 6, Y: 8}, + &SamplePoint2D{X: 7, Y: 4}, + &SamplePoint2D{X: 7, Y: 7}, + &SamplePoint2D{X: 8, Y: 2}, + &SamplePoint2D{X: 8, Y: 5}, + &SamplePoint2D{X: 9, Y: 9}, + &SamplePoint2D{X: 3, Y: 1}, + &SamplePoint2D{X: 4, Y: 2}, + &SamplePoint2D{X: 9, Y: 2}, + &SamplePoint2D{X: 6, Y: 5}, + &SamplePoint2D{X: 3, Y: 8}, + &SamplePoint2D{X: 6, Y: 2}, + &SamplePoint2D{X: 1, Y: 3}, + &SamplePoint2D{X: 3, Y: 3}, + &SamplePoint2D{X: 6, Y: 4}, + &SamplePoint2D{X: 9, Y: 8}, + &SamplePoint2D{X: 2, Y: 1}, + &SamplePoint2D{X: 2, Y: 8}, + &SamplePoint2D{X: 3, Y: 1}, + &SamplePoint2D{X: 7, Y: 3}, + &SamplePoint2D{X: 3, Y: 9}, + &SamplePoint2D{X: 4, Y: 4}, + &SamplePoint2D{X: 5, Y: 3}, + &SamplePoint2D{X: 9, Y: 6}}), + input: NewKDRange(2, 10, -2, -1), + expected: []Point{}, + }, + { + name: "out of range x (higher)", + tree: New([]Point{&SamplePoint2D{X: 1, Y: 3}, + &SamplePoint2D{X: 1, Y: 8}, + &SamplePoint2D{X: 2, Y: 2}, + &SamplePoint2D{X: 2, Y: 10}, + &SamplePoint2D{X: 3, Y: 6}, + &SamplePoint2D{X: 4, Y: 1}, + &SamplePoint2D{X: 5, Y: 4}, + &SamplePoint2D{X: 6, Y: 8}, + &SamplePoint2D{X: 7, Y: 4}, + &SamplePoint2D{X: 7, Y: 7}, + &SamplePoint2D{X: 8, Y: 2}, + &SamplePoint2D{X: 8, Y: 5}, + &SamplePoint2D{X: 9, Y: 9}, + &SamplePoint2D{X: 3, Y: 1}, + &SamplePoint2D{X: 4, Y: 2}, + &SamplePoint2D{X: 9, Y: 2}, + &SamplePoint2D{X: 6, Y: 5}, + &SamplePoint2D{X: 3, Y: 8}, + &SamplePoint2D{X: 6, Y: 2}, + &SamplePoint2D{X: 1, Y: 3}, + &SamplePoint2D{X: 3, Y: 3}, + &SamplePoint2D{X: 6, Y: 4}, + &SamplePoint2D{X: 9, Y: 8}, + &SamplePoint2D{X: 2, Y: 1}, + &SamplePoint2D{X: 2, Y: 8}, + &SamplePoint2D{X: 3, Y: 1}, + &SamplePoint2D{X: 7, Y: 3}, + &SamplePoint2D{X: 3, Y: 9}, + &SamplePoint2D{X: 4, Y: 4}, + &SamplePoint2D{X: 5, Y: 3}, + &SamplePoint2D{X: 9, Y: 6}}), + input: NewKDRange(20, 30, 2, 10), + expected: []Point{}, + }, + { + name: "out of range y (higher)", + tree: New([]Point{&SamplePoint2D{X: 1, Y: 3}, + &SamplePoint2D{X: 1, Y: 8}, + &SamplePoint2D{X: 2, Y: 2}, + &SamplePoint2D{X: 2, Y: 10}, + &SamplePoint2D{X: 3, Y: 6}, + &SamplePoint2D{X: 4, Y: 1}, + &SamplePoint2D{X: 5, Y: 4}, + &SamplePoint2D{X: 6, Y: 8}, + &SamplePoint2D{X: 7, Y: 4}, + &SamplePoint2D{X: 7, Y: 7}, + &SamplePoint2D{X: 8, Y: 2}, + &SamplePoint2D{X: 8, Y: 5}, + &SamplePoint2D{X: 9, Y: 9}, + &SamplePoint2D{X: 3, Y: 1}, + &SamplePoint2D{X: 4, Y: 2}, + &SamplePoint2D{X: 9, Y: 2}, + &SamplePoint2D{X: 6, Y: 5}, + &SamplePoint2D{X: 3, Y: 8}, + &SamplePoint2D{X: 6, Y: 2}, + &SamplePoint2D{X: 1, Y: 3}, + &SamplePoint2D{X: 3, Y: 3}, + &SamplePoint2D{X: 6, Y: 4}, + &SamplePoint2D{X: 9, Y: 8}, + &SamplePoint2D{X: 2, Y: 1}, + &SamplePoint2D{X: 2, Y: 8}, + &SamplePoint2D{X: 3, Y: 1}, + &SamplePoint2D{X: 7, Y: 3}, + &SamplePoint2D{X: 3, Y: 9}, + &SamplePoint2D{X: 4, Y: 4}, + &SamplePoint2D{X: 5, Y: 3}, + &SamplePoint2D{X: 9, Y: 6}}), + input: NewKDRange(2, 10, 20, 30), + expected: []Point{}, }, } for _, test := range tests { @@ -480,18 +818,18 @@ func TestKDTree_RangeSearch(t *testing.T) { func TestKDTree_RangeSearchWithGenerator(t *testing.T) { tests := []struct { name string - input []kdtree.Point - r kdrange.Range + input []Point + r kdrangeRange }{ - {name: "nodes: 100 range: -100 50 -50 100", input: generateTestCaseData(100), r: kdrange.New(-100, 50, -50, 100)}, - {name: "nodes: 1000 range: -100 50 -50 100", input: generateTestCaseData(1000), r: kdrange.New(-100, 50, -50, 100)}, - {name: "nodes: 10000 range: -100 50 -50 100", input: generateTestCaseData(10000), r: kdrange.New(-100, 50, -50, 100)}, - {name: "nodes: 100000 range: -500 250 -250 500", input: generateTestCaseData(100000), r: kdrange.New(-500, 250, -250, 500)}, - {name: "nodes: 1000000 range: -500 250 -250 500", input: generateTestCaseData(1000000), r: kdrange.New(-500, 250, -250, 500)}, + {name: "nodes: 100 range: -100 50 -50 100", input: generateTestCaseData(100), r: NewKDRange(-100, 50, -50, 100)}, + {name: "nodes: 1000 range: -100 50 -50 100", input: generateTestCaseData(1000), r: NewKDRange(-100, 50, -50, 100)}, + {name: "nodes: 10000 range: -100 50 -50 100", input: generateTestCaseData(10000), r: NewKDRange(-100, 50, -50, 100)}, + {name: "nodes: 100000 range: -500 250 -250 500", input: generateTestCaseData(100000), r: NewKDRange(-500, 250, -250, 500)}, + {name: "nodes: 1000000 range: -500 250 -250 500", input: generateTestCaseData(1000000), r: NewKDRange(-500, 250, -250, 500)}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - tree := kdtree.New(test.input) + tree := New(test.input) assert.ElementsMatch(t, filterRangeSearch(test.input, test.r), tree.RangeSearch(test.r)) }) } @@ -499,13 +837,13 @@ func TestKDTree_RangeSearchWithGenerator(t *testing.T) { // benchmarks -var resultTree *kdtree.KDTree -var resultPoints []kdtree.Point +var resultTree *KDTree +var resultPoints []Point func BenchmarkNew(b *testing.B) { benchmarks := []struct { name string - input []kdtree.Point + input []Point }{ {name: "100", input: generateTestCaseData(100)}, {name: "1000", input: generateTestCaseData(1000)}, @@ -514,9 +852,9 @@ func BenchmarkNew(b *testing.B) { } for _, bm := range benchmarks { b.Run(bm.name, func(b *testing.B) { - var t *kdtree.KDTree + var t *KDTree for i := 0; i < b.N; i++ { - t = kdtree.New(bm.input) + t = New(bm.input) } resultTree = t }) @@ -526,18 +864,18 @@ func BenchmarkNew(b *testing.B) { func BenchmarkKNN(b *testing.B) { benchmarks := []struct { name string - target kdtree.Point + target Point k int - input []kdtree.Point + input []Point }{ - {name: "p:100,k:5", target: &Point2D{}, k: 5, input: generateTestCaseData(100)}, - {name: "p:1000,k:5", target: &Point2D{}, k: 5, input: generateTestCaseData(1000)}, - {name: "p:10000,k:5", target: &Point2D{}, k: 5, input: generateTestCaseData(10000)}, - {name: "p:100000,k:5", target: &Point2D{}, k: 5, input: generateTestCaseData(100000)}, + {name: "p:100,k:5", target: &SamplePoint2D{}, k: 5, input: generateTestCaseData(100)}, + {name: "p:1000,k:5", target: &SamplePoint2D{}, k: 5, input: generateTestCaseData(1000)}, + {name: "p:10000,k:5", target: &SamplePoint2D{}, k: 5, input: generateTestCaseData(10000)}, + {name: "p:100000,k:5", target: &SamplePoint2D{}, k: 5, input: generateTestCaseData(100000)}, } for _, bm := range benchmarks { - var res []kdtree.Point - tree := kdtree.New(bm.input) + var res []Point + tree := New(bm.input) b.Run(bm.name, func(b *testing.B) { for i := 0; i < b.N; i++ { res = tree.KNN(bm.target, bm.k) @@ -549,25 +887,26 @@ func BenchmarkKNN(b *testing.B) { // helpers -func generateTestCaseData(size int) []kdtree.Point { +func generateTestCaseData(size int) []Point { r := rand.New(rand.NewSource(time.Now().UnixNano())) - var points []kdtree.Point + var pointsSlice []Point for i := 0; i < size; i++ { - points = append(points, &Point2D{X: r.Float64()*3000 - 1500, Y: r.Float64()*3000 - 1500}) + pointsSlice = append(pointsSlice, + &SamplePoint2D{X: r.Float64()*3000 - 1500, Y: r.Float64()*3000 - 1500}) } - return points + return pointsSlice } -func prioQueueKNN(points []kdtree.Point, p kdtree.Point, k int) []kdtree.Point { - knn := make([]kdtree.Point, 0, k) +func prioQueueKNN(points []Point, p Point, k int) []Point { + knn := make([]Point, 0, k) if p == nil { return knn } nnPQ := pq.New() for _, point := range points { - nnPQ.Insert(point, distance(p, point)) + nnPQ.Insert(point, p.Distance(point)) } for i := 0; i < k; i++ { @@ -575,13 +914,13 @@ func prioQueueKNN(points []kdtree.Point, p kdtree.Point, k int) []kdtree.Point { if err != nil { break } - knn = append(knn, point.(kdtree.Point)) + knn = append(knn, point.(Point)) } return knn } -func filterRangeSearch(points []kdtree.Point, r kdrange.Range) []kdtree.Point { - result := make([]kdtree.Point, 0) +func filterRangeSearch(points []Point, r kdrangeRange) []Point { + result := make([]Point, 0) pointLoop: for _, point := range points { @@ -596,15 +935,7 @@ pointLoop: return result } -func distance(p1, p2 kdtree.Point) float64 { - sum := 0. - for i := 0; i < p1.Dimensions(); i++ { - sum += math.Pow(p1.Dimension(i)-p2.Dimension(i), 2.0) - } - return math.Sqrt(sum) -} - -func assertPointsEqual(t *testing.T, p1 kdtree.Point, p2 kdtree.Point) { +func assertPointsEqual(t *testing.T, p1 Point, p2 Point) { assert.Equal(t, p1.Dimensions(), p2.Dimensions()) for i := 0; i < p1.Dimensions(); i++ { assert.Equal(t, p1.Dimension(i), p2.Dimension(i)) diff --git a/point2d_test.go b/point2d_test.go new file mode 100644 index 0000000..d3a322e --- /dev/null +++ b/point2d_test.go @@ -0,0 +1,103 @@ +/* + * Copyright 2018 Dennis Kuhnert + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kdtree + +import ( + "fmt" + "math" + "testing" + + "github.com/stretchr/testify/assert" +) + +// Point2D ... +type SamplePoint2D struct { + X float64 + Y float64 +} + +// Dimensions ... +func (p *SamplePoint2D) Dimensions() int { + return 2 +} + +// Dimension ... +func (p *SamplePoint2D) Dimension(i int) float64 { + if i == 0 { + return p.X + } + return p.Y +} + +// String ... +func (p *SamplePoint2D) String() string { + return fmt.Sprintf("{%.2f %.2f}", p.X, p.Y) +} + +// Distance implements *Point Distance for Point2D +func (p *SamplePoint2D) Distance(p2 Point) float64 { + sum := 0. + for i := 0; i < p.Dimensions(); i++ { + sum += math.Pow(p.Dimension(i)-p2.Dimension(i), 2.0) + } + return math.Sqrt(sum) +} + +// PlaneDistance implements PlaneDistance for SamplePoint2D +func (p *SamplePoint2D) PlaneDistance(planePosition float64, dim int) float64 { + return math.Abs(planePosition - p.Dimension(dim)) +} + +func TestPoint2D_Dimensions(t *testing.T) { + assert.Equal(t, (&SamplePoint2D{}).Dimensions(), 2) +} + +func TestPoint2D_TestDimension(t *testing.T) { + tests := []struct { + name string + point SamplePoint2D + }{ + {name: "empty", point: SamplePoint2D{}}, + {name: "X", point: SamplePoint2D{X: 2}}, + {name: "Y", point: SamplePoint2D{Y: 3}}, + {name: "XY", point: SamplePoint2D{X: 2, Y: 3}}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + assert.Equal(t, test.point.Dimension(0), test.point.X) + assert.Equal(t, test.point.Dimension(1), test.point.Y) + }) + } +} + +func TestPoint2D_String(t *testing.T) { + tests := []struct { + name string + point SamplePoint2D + expected string + }{ + {name: "empty", point: SamplePoint2D{}, expected: "{0.00 0.00}"}, + {name: "X", point: SamplePoint2D{X: 2}, expected: "{2.00 0.00}"}, + {name: "Y", point: SamplePoint2D{Y: 3}, expected: "{0.00 3.00}"}, + {name: "XY", point: SamplePoint2D{X: 2, Y: 3}, expected: "{2.00 3.00}"}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + assert.Equal(t, test.point.String(), test.expected) + }) + } +} diff --git a/points/point_test.go b/point_test.go similarity index 57% rename from points/point_test.go rename to point_test.go index 1759609..29e7406 100644 --- a/points/point_test.go +++ b/point_test.go @@ -14,15 +14,59 @@ * limitations under the License. */ -package points_test +package kdtree import ( + "fmt" + "math" "testing" - "github.com/kyroy/kdtree/points" "github.com/stretchr/testify/assert" ) +// SamplePoint represents a n-dimensional point of the k-d tree. +type SamplePoint struct { + Coordinates []float64 + Data interface{} +} + +// NewSamplePoint creates a new point at the given coordinates and contains the given data. +func NewSamplePoint(coordinates []float64, data interface{}) *SamplePoint { + return &SamplePoint{ + Coordinates: coordinates, + Data: data, + } +} + +// Dimensions returns the total number of dimensions. +func (p *SamplePoint) Dimensions() int { + return len(p.Coordinates) +} + +// Dimension returns the value of the i-th dimension. +func (p *SamplePoint) Dimension(i int) float64 { + return p.Coordinates[i] +} + +// String returns the string representation of the point. +func (p *SamplePoint) String() string { + return fmt.Sprintf("{%v %v}", p.Coordinates, p.Data) +} + +// Distance implements Distance for points.Point +func (p1 *SamplePoint) Distance(p2 Point) float64 { + sum := 0. + for i := 0; i < p1.Dimensions(); i++ { + sum += math.Pow(p1.Dimension(i)-p2.Dimension(i), 2.0) + } + return math.Sqrt(sum) +} + +// PlaneDistance implements PlaneDistance for points.Point +func (p *SamplePoint) PlaneDistance(planePosition float64, dim int) float64 { + return math.Abs(planePosition - p.Dimension(dim)) +} + func TestNewPoint(t *testing.T) { tests := []struct { name string @@ -33,7 +77,7 @@ func TestNewPoint(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - p := points.NewPoint(test.coordinates, test.data) + p := NewSamplePoint(test.coordinates, test.data) assert.Equal(t, test.coordinates, p.Coordinates) assert.Equal(t, test.data, p.Data) assert.Equal(t, len(test.coordinates), p.Dimensions()) @@ -56,7 +100,7 @@ func TestPoint_Dimensions(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - p := points.Point{Coordinates: test.input} + p := SamplePoint{Coordinates: test.input} assert.Equal(t, p.Dimensions(), len(test.input)) }) } @@ -74,7 +118,7 @@ func TestPoint_Dimension(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - p := points.Point{Coordinates: test.input} + p := SamplePoint{Coordinates: test.input} for i, itm := range test.input { assert.Equal(t, p.Dimension(i), itm) } @@ -85,16 +129,16 @@ func TestPoint_Dimension(t *testing.T) { func TestPoint_String(t *testing.T) { tests := []struct { name string - point points.Point + point SamplePoint expected string }{ - {name: "empty", point: points.Point{}, expected: "{[] }"}, - {name: "string data", point: points.Point{Coordinates: []float64{1, 2}, Data: "testme"}, expected: "{[1 2] testme}"}, - {name: "float data", point: points.Point{Coordinates: []float64{1, 2}, Data: 4.3}, expected: "{[1 2] 4.3}"}, - {name: "int data", point: points.Point{Coordinates: []float64{1, 2}, Data: 42}, expected: "{[1 2] 42}"}, + {name: "empty", point: SamplePoint{}, expected: "{[] }"}, + {name: "string data", point: SamplePoint{Coordinates: []float64{1, 2}, Data: "testme"}, expected: "{[1 2] testme}"}, + {name: "float data", point: SamplePoint{Coordinates: []float64{1, 2}, Data: 4.3}, expected: "{[1 2] 4.3}"}, + {name: "int data", point: SamplePoint{Coordinates: []float64{1, 2}, Data: 42}, expected: "{[1 2] 42}"}, { name: "struct data", - point: points.Point{ + point: SamplePoint{ Coordinates: []float64{1, 2}, Data: struct { A int diff --git a/points/point.go b/points/point.go deleted file mode 100644 index 7789026..0000000 --- a/points/point.go +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2018 Dennis Kuhnert - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// Package points contains multiple example implementations of the kdtree.Point interface. -package points - -import "fmt" - -// Point represents a n-dimensional point of the k-d tree. -type Point struct { - Coordinates []float64 - Data interface{} -} - -// NewPoint creates a new point at the given coordinates and contains the given data. -func NewPoint(coordinates []float64, data interface{}) *Point { - return &Point{ - Coordinates: coordinates, - Data: data, - } -} - -// Dimensions returns the total number of dimensions. -func (p *Point) Dimensions() int { - return len(p.Coordinates) -} - -// Dimension returns the value of the i-th dimension. -func (p *Point) Dimension(i int) float64 { - return p.Coordinates[i] -} - -// String returns the string representation of the point. -func (p *Point) String() string { - return fmt.Sprintf("{%v %v}", p.Coordinates, p.Data) -} diff --git a/points/point2d.go b/points/point2d.go deleted file mode 100644 index c1b5df8..0000000 --- a/points/point2d.go +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2018 Dennis Kuhnert - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package points - -import "fmt" - -// Point2D ... -type Point2D struct { - X float64 - Y float64 -} - -// Dimensions ... -func (p *Point2D) Dimensions() int { - return 2 -} - -// Dimension ... -func (p *Point2D) Dimension(i int) float64 { - if i == 0 { - return p.X - } - return p.Y -} - -// String ... -func (p *Point2D) String() string { - return fmt.Sprintf("{%.2f %.2f}", p.X, p.Y) -} diff --git a/points/point2d_test.go b/points/point2d_test.go deleted file mode 100644 index 9cc5b84..0000000 --- a/points/point2d_test.go +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2018 Dennis Kuhnert - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package points_test - -import ( - "testing" - - "github.com/kyroy/kdtree/points" - "github.com/stretchr/testify/assert" -) - -func TestPoint2D_Dimensions(t *testing.T) { - assert.Equal(t, (&points.Point2D{}).Dimensions(), 2) -} - -func TestPoint2D_TestDimension(t *testing.T) { - tests := []struct { - name string - point points.Point2D - }{ - {name: "empty", point: points.Point2D{}}, - {name: "X", point: points.Point2D{X: 2}}, - {name: "Y", point: points.Point2D{Y: 3}}, - {name: "XY", point: points.Point2D{X: 2, Y: 3}}, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - assert.Equal(t, test.point.Dimension(0), test.point.X) - assert.Equal(t, test.point.Dimension(1), test.point.Y) - }) - } -} - -func TestPoint2D_String(t *testing.T) { - tests := []struct { - name string - point points.Point2D - expected string - }{ - {name: "empty", point: points.Point2D{}, expected: "{0.00 0.00}"}, - {name: "X", point: points.Point2D{X: 2}, expected: "{2.00 0.00}"}, - {name: "Y", point: points.Point2D{Y: 3}, expected: "{0.00 3.00}"}, - {name: "XY", point: points.Point2D{X: 2, Y: 3}, expected: "{2.00 3.00}"}, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - assert.Equal(t, test.point.String(), test.expected) - }) - } -} diff --git a/points/point3d.go b/points/point3d.go deleted file mode 100644 index 78b385b..0000000 --- a/points/point3d.go +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2018 Dennis Kuhnert - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package points - -import "fmt" - -// Point3D ... -type Point3D struct { - X float64 - Y float64 - Z float64 -} - -// Dimensions ... -func (p *Point3D) Dimensions() int { - return 3 -} - -// Dimension ... -func (p *Point3D) Dimension(i int) float64 { - switch i { - case 0: - return p.X - case 1: - return p.Y - default: - return p.Z - } -} - -// String ... -func (p *Point3D) String() string { - return fmt.Sprintf("{%.2f %.2f %.2f}", p.X, p.Y, p.Z) -} diff --git a/points/point3d_test.go b/points/point3d_test.go deleted file mode 100644 index 78a346a..0000000 --- a/points/point3d_test.go +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2018 Dennis Kuhnert - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package points_test - -import ( - "testing" - - "github.com/kyroy/kdtree/points" - "github.com/stretchr/testify/assert" -) - -func TestPoint3D_Dimensions(t *testing.T) { - assert.Equal(t, (&points.Point3D{}).Dimensions(), 3) -} - -func TestPoint3D_TestDimension(t *testing.T) { - tests := []struct { - name string - point points.Point3D - }{ - {name: "empty", point: points.Point3D{}}, - {name: "X", point: points.Point3D{X: 2}}, - {name: "Y", point: points.Point3D{Y: 3}}, - {name: "Z", point: points.Point3D{Y: 4}}, - {name: "XYZ", point: points.Point3D{X: 2, Y: 3, Z: 4}}, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - assert.Equal(t, test.point.Dimension(0), test.point.X) - assert.Equal(t, test.point.Dimension(1), test.point.Y) - assert.Equal(t, test.point.Dimension(2), test.point.Z) - }) - } -} - -func TestPoint3D_String(t *testing.T) { - tests := []struct { - name string - point points.Point3D - expected string - }{ - {name: "empty", point: points.Point3D{}, expected: "{0.00 0.00 0.00}"}, - {name: "X", point: points.Point3D{X: 2}, expected: "{2.00 0.00 0.00}"}, - {name: "Y", point: points.Point3D{Y: 3}, expected: "{0.00 3.00 0.00}"}, - {name: "Z", point: points.Point3D{Z: 4}, expected: "{0.00 0.00 4.00}"}, - {name: "XY", point: points.Point3D{X: 2, Y: 3, Z: 4}, expected: "{2.00 3.00 4.00}"}, - } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - assert.Equal(t, test.point.String(), test.expected) - }) - } -} diff --git a/kdrange/range.go b/range.go similarity index 82% rename from kdrange/range.go rename to range.go index ffd688c..66c4ee5 100644 --- a/kdrange/range.go +++ b/range.go @@ -14,13 +14,12 @@ * limitations under the License. */ -// Package kdrange contains k-dimensional range struct and helpers. -package kdrange +package kdtree -// Range represents a range in k-dimensional space. -type Range [][2]float64 +// kdrangeRange represents a range in k-dimensional space. +type kdrangeRange [][2]float64 -// New creates a new Range. +// NewKDRange creates a new Range. // // It accepts a sequence of min/max pairs that define the Range. // For example a 2-dimensional rectangle with the with 2 and height 3, starting at (1,2): @@ -30,7 +29,7 @@ type Range [][2]float64 // I.e.: // x (dim 0): 1 <= x <= 3 // y (dim 1): 2 <= y <= 5 -func New(limits ...float64) Range { +func NewKDRange(limits ...float64) kdrangeRange { if limits == nil || len(limits)%2 != 0 { return nil } diff --git a/kdrange/range_test.go b/range_test.go similarity index 90% rename from kdrange/range_test.go rename to range_test.go index 97004c6..a1c4d3e 100644 --- a/kdrange/range_test.go +++ b/range_test.go @@ -14,12 +14,11 @@ * limitations under the License. */ -package kdrange_test +package kdtree import ( "testing" - "github.com/kyroy/kdtree/kdrange" "github.com/stretchr/testify/assert" ) @@ -27,7 +26,7 @@ func TestNewRange(t *testing.T) { tests := []struct { name string input []float64 - output kdrange.Range + output kdrangeRange }{ { name: "nil", @@ -57,7 +56,7 @@ func TestNewRange(t *testing.T) { } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - assert.Equal(t, test.output, kdrange.New(test.input...)) + assert.Equal(t, test.output, NewKDRange(test.input...)) }) } }