Data update

This commit is contained in:
Ingy döt Net 2025-06-11 20:16:52 -04:00
parent 72eb4943cb
commit 4d5544505c
2347 changed files with 62432 additions and 16731 deletions

View file

@ -0,0 +1,472 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text; // For StringBuilder in Point.ToString
//-----------------------------------------------------------------------------
// Point Struct
//-----------------------------------------------------------------------------
/// <summary>
/// Represents a point in N-dimensional space.
/// TCoord must be a numeric type supporting comparison and conversion.
/// </summary>
/// <typeparam name="TCoord">The numeric type for coordinates (e.g., int, double, float).</typeparam>
public readonly struct Point<TCoord> : IEquatable<Point<TCoord>>
where TCoord : struct, IComparable<TCoord>, IConvertible // Constraints for numeric operations & sorting
{
private readonly TCoord[] _coords;
public int Dimensions { get; }
/// <summary>
/// Initializes a new point with the specified coordinates.
/// The number of coordinates determines the dimension.
/// </summary>
/// <param name="coords">The coordinates for the point.</param>
/// <exception cref="ArgumentNullException">Thrown if coords is null.</exception>
/// <exception cref="ArgumentException">Thrown if coords is empty.</exception>
public Point(params TCoord[] coords)
{
if (coords == null)
throw new ArgumentNullException(nameof(coords));
if (coords.Length == 0)
throw new ArgumentException("Coordinates cannot be empty.", nameof(coords));
_coords = (TCoord[])coords.Clone(); // Defensive copy
Dimensions = _coords.Length;
}
/// <summary>
/// Initializes a new point by copying coordinates from an enumerable.
/// </summary>
/// <param name="coords">The coordinates for the point.</param>
/// <exception cref="ArgumentNullException">Thrown if coords is null.</exception>
/// <exception cref="ArgumentException">Thrown if coords results in an empty collection.</exception>
public Point(IEnumerable<TCoord> coords)
{
if (coords == null)
throw new ArgumentNullException(nameof(coords));
_coords = coords.ToArray();
if (_coords.Length == 0)
throw new ArgumentException("Coordinates cannot be empty.", nameof(coords));
Dimensions = _coords.Length;
}
/// <summary>
/// Gets the coordinate value in the specified dimension.
/// </summary>
/// <param name="index">Dimension index (zero-based).</param>
/// <returns>The coordinate value.</returns>
/// <exception cref="IndexOutOfRangeException">Thrown if index is out of bounds.</exception>
public TCoord Get(int index)
{
// Rely on array's built-in bounds checking for performance
// if (index < 0 || index >= Dimensions)
// throw new IndexOutOfRangeException($"Index {index} is out of range for dimension {Dimensions}.");
return _coords[index];
}
/// <summary>
/// Calculates the squared Euclidean distance between this point and another point.
/// </summary>
/// <param name="other">The other point.</param>
/// <returns>The squared distance.</returns>
/// <exception cref="ArgumentException">Thrown if points have different dimensions.</exception>
public double DistanceSquared(Point<TCoord> other)
{
if (Dimensions != other.Dimensions)
throw new ArgumentException("Points must have the same dimensions.");
double distSq = 0;
for (int i = 0; i < Dimensions; ++i)
{
// Convert coordinates to double for calculation.
// This is a common approach when TCoord isn't guaranteed to be double.
// Using System.Numerics.INumber<T> in .NET 7+ would allow direct arithmetic.
double d = Convert.ToDouble(Get(i)) - Convert.ToDouble(other.Get(i));
distSq += d * d;
}
return distSq;
}
/// <summary>
/// Returns a string representation of the point (e.g., "(1, 2, 3)").
/// </summary>
public override string ToString()
{
var sb = new StringBuilder();
sb.Append('(');
for (int i = 0; i < Dimensions; ++i)
{
if (i > 0)
sb.Append(", ");
sb.Append(_coords[i]);
}
sb.Append(')');
return sb.ToString();
}
// --- Equality Members ---
public bool Equals(Point<TCoord> other)
{
if (Dimensions != other.Dimensions)
return false;
// Using SequenceEqual for robust array comparison
return _coords.SequenceEqual(other._coords);
}
public override bool Equals(object obj)
{
return obj is Point<TCoord> other && Equals(other);
}
public override int GetHashCode()
{
int hashCode = Dimensions.GetHashCode();
if (_coords != null)
{
foreach (var coord in _coords)
{
// Simple hash combining approach
hashCode = HashCode.Combine(hashCode, coord.GetHashCode());
}
}
return hashCode;
}
public static bool operator ==(Point<TCoord> left, Point<TCoord> right)
{
return left.Equals(right);
}
public static bool operator !=(Point<TCoord> left, Point<TCoord> right)
{
return !(left == right);
}
}
//-----------------------------------------------------------------------------
// KdTree Class
//-----------------------------------------------------------------------------
/// <summary>
/// C# k-d tree implementation for fast nearest neighbor searches.
/// </summary>
/// <typeparam name="TCoord">The numeric type for coordinates.</typeparam>
public class KdTree<TCoord>
where TCoord : struct, IComparable<TCoord>, IConvertible
{
// Internal Node class
private class Node
{
public Point<TCoord> Point { get; }
public Node Left { get; set; } // Using properties with private setters if needed, or public fields
public Node Right { get; set; }
public Node(Point<TCoord> point)
{
Point = point;
Left = null;
Right = null;
}
// Helper to access coordinate for sorting/comparison
public TCoord Get(int index) => Point.Get(index);
// Helper for distance calculation
public double DistanceSquared(Point<TCoord> pt) => Point.DistanceSquared(pt);
}
// --- Fields ---
private readonly Node[] _nodes; // Store nodes contiguously for potential cache benefits
private readonly Node _root;
private readonly int _dimensions;
// State for the nearest neighbor search
private Node _bestNode = null;
private double _bestDistSq = double.MaxValue;
private int _visitedCount = 0;
// --- Comparer for Sorting Nodes ---
private class NodeComparer : IComparer<Node>
{
private readonly int _dimensionIndex;
public NodeComparer(int dimensionIndex)
{
_dimensionIndex = dimensionIndex;
}
public int Compare(Node x, Node y)
{
// Comparison relies on the IComparable<TCoord> constraint
return x.Get(_dimensionIndex).CompareTo(y.Get(_dimensionIndex));
}
}
// --- Constructors ---
/// <summary>
/// Builds a k-d tree from a collection of points.
/// </summary>
/// <param name="points">The points to add to the tree.</param>
/// <exception cref="ArgumentNullException">Thrown if points is null.</exception>
/// <exception cref="ArgumentException">Thrown if points is empty or contains points with inconsistent dimensions.</exception>
public KdTree(IEnumerable<Point<TCoord>> points)
{
if (points == null) throw new ArgumentNullException(nameof(points));
var pointList = points.ToList(); // Materialize the list
if (pointList.Count == 0) throw new ArgumentException("Point collection cannot be empty.", nameof(points));
_dimensions = pointList[0].Dimensions;
_nodes = new Node[pointList.Count];
for (int i = 0; i < pointList.Count; i++)
{
if (pointList[i].Dimensions != _dimensions)
throw new ArgumentException($"All points must have the same dimension ({_dimensions}). Point {i} has dimension {pointList[i].Dimensions}.", nameof(points));
_nodes[i] = new Node(pointList[i]);
}
_root = MakeTree(0, _nodes.Length, 0);
}
/// <summary>
/// Builds a k-d tree by generating points using a function.
/// </summary>
/// <param name="pointGenerator">A function that returns a Point<TCoord>.</param>
/// <param name="count">The number of points to generate.</param>
/// <exception cref="ArgumentNullException">Thrown if pointGenerator is null.</exception>
/// <exception cref="ArgumentOutOfRangeException">Thrown if count is zero or negative.</exception>
/// <exception cref="InvalidOperationException">Thrown if the generator produces points with inconsistent dimensions.</exception>
public KdTree(Func<Point<TCoord>> pointGenerator, int count)
{
if (pointGenerator == null) throw new ArgumentNullException(nameof(pointGenerator));
if (count <= 0) throw new ArgumentOutOfRangeException(nameof(count), "Count must be positive.");
_nodes = new Node[count];
Point<TCoord> firstPoint = pointGenerator();
_dimensions = firstPoint.Dimensions;
_nodes[0] = new Node(firstPoint);
for (int i = 1; i < count; i++)
{
Point<TCoord> p = pointGenerator();
if (p.Dimensions != _dimensions)
throw new InvalidOperationException($"Generated points must have consistent dimensions ({_dimensions}). Point {i} has dimension {p.Dimensions}.");
_nodes[i] = new Node(p);
}
_root = MakeTree(0, _nodes.Length, 0);
}
// --- Tree Building Method ---
private Node MakeTree(int begin, int end, int index)
{
if (end <= begin)
return null; // Base case: empty range
// Calculate median index
int n = begin + (end - begin) / 2;
// Sort the segment [begin, end) based on the current dimension 'index'
// This partitions the array around the median element at index 'n'
// Array.Sort sorts the range [begin, begin + length), so length is end - begin
Array.Sort(_nodes, begin, end - begin, new NodeComparer(index));
// Median element becomes the current node
Node currentNode = _nodes[n];
// Cycle to the next dimension for children
int nextIndex = (index + 1) % _dimensions;
// Recursively build left and right subtrees
currentNode.Left = MakeTree(begin, n, nextIndex);
currentNode.Right = MakeTree(n + 1, end, nextIndex);
return currentNode;
}
// --- Public API ---
/// <summary>
/// Gets the number of dimensions for points in this tree.
/// </summary>
public int Dimensions => _dimensions;
/// <summary>
/// Returns true if the tree is empty.
/// </summary>
public bool IsEmpty => _nodes.Length == 0;
/// <summary>
/// Returns the number of nodes visited during the last call to Nearest.
/// </summary>
public int Visited => _visitedCount;
/// <summary>
/// Returns the squared distance between the query point and the nearest point found by the last call to Nearest.
/// Returns double.PositiveInfinity if Nearest hasn't been called or the tree is empty.
/// </summary>
public double DistanceSquared => _bestNode != null ? _bestDistSq : double.PositiveInfinity;
/// <summary>
/// Returns the Euclidean distance between the query point and the nearest point found by the last call to Nearest.
/// Returns double.PositiveInfinity if Nearest hasn't been called or the tree is empty.
/// </summary>
public double Distance => _bestNode != null ? Math.Sqrt(_bestDistSq) : double.PositiveInfinity;
/// <summary>
/// Finds the nearest point in the tree to the given point.
/// </summary>
/// <param name="point">The query point.</param>
/// <returns>The nearest point found in the tree.</returns>
/// <exception cref="InvalidOperationException">Thrown if the tree is empty.</exception>
/// <exception cref="ArgumentException">Thrown if the query point has different dimensions than the tree.</exception>
public Point<TCoord> Nearest(Point<TCoord> point)
{
if (_root == null)
throw new InvalidOperationException("Cannot search an empty tree.");
if (point.Dimensions != _dimensions)
throw new ArgumentException($"Query point dimension ({point.Dimensions}) must match tree dimension ({_dimensions}).");
// Reset search state
_bestNode = null;
_bestDistSq = double.MaxValue; // Use MaxValue for initial comparison
_visitedCount = 0;
// Start recursive search
Nearest(_root, point, 0);
// _bestNode should not be null if _root wasn't null
return _bestNode.Point;
}
// --- Recursive Nearest Neighbor Search ---
private void Nearest(Node node, Point<TCoord> point, int index)
{
if (node == null)
return;
_visitedCount++;
double dSq = node.DistanceSquared(point);
// If this node is better than the current best, update best
if (_bestNode == null || dSq < _bestDistSq)
{
_bestDistSq = dSq;
_bestNode = node;
}
// Perfect match found, can stop (early exit)
if (_bestDistSq == 0)
return;
// Determine difference along the splitting dimension
// Convert coordinates to double for the difference calculation
double dx = Convert.ToDouble(node.Get(index)) - Convert.ToDouble(point.Get(index));
// Cycle dimension
int nextIndex = (index + 1) % _dimensions;
// Decide which subtree to visit first (the one containing the point)
Node nearerNode = dx > 0 ? node.Left : node.Right;
Node furtherNode = dx > 0 ? node.Right : node.Left;
// Search the nearer subtree first
Nearest(nearerNode, point, nextIndex);
// Pruning: Check if the hypersphere crosses the splitting plane.
// If dx^2 >= bestDistSq, the other subtree cannot contain a closer point.
if (dx * dx >= _bestDistSq)
{
return; // Prune the further subtree
}
// Hypersphere crosses the plane, search the further subtree
Nearest(furtherNode, point, nextIndex);
}
}
//-----------------------------------------------------------------------------
// Example Usage & Testing
//-----------------------------------------------------------------------------
public class Program
{
static void TestWikipedia()
{
Console.WriteLine("Wikipedia example data:");
var points = new Point<int>[] {
new Point<int>(2, 3), new Point<int>(5, 4), new Point<int>(9, 6),
new Point<int>(4, 7), new Point<int>(8, 1), new Point<int>(7, 2)
};
var tree = new KdTree<int>(points);
var queryPoint = new Point<int>(9, 2);
Point<int> nearest = tree.Nearest(queryPoint);
Console.WriteLine($"Query point: {queryPoint}");
Console.WriteLine($"Nearest point: {nearest}");
Console.WriteLine($"Distance: {tree.Distance:F6}"); // Format distance
Console.WriteLine($"Nodes visited: {tree.Visited}");
Console.WriteLine("------------------------------------");
}
// Simple random point generator for 3D doubles
private static Random _random = new Random();
public static Point<double> RandomPointGenerator(double min, double max)
{
double range = max - min;
double x = min + _random.NextDouble() * range;
double y = min + _random.NextDouble() * range;
double z = min + _random.NextDouble() * range;
return new Point<double>(x, y, z);
}
static void TestRandom(int count)
{
Console.WriteLine($"Random data ({count} points):");
// Use the generator constructor
var tree = new KdTree<double>(() => RandomPointGenerator(0, 1), count);
// Generate a random query point
var queryPoint = RandomPointGenerator(0, 1);
Point<double> nearest = tree.Nearest(queryPoint);
Console.WriteLine($"Query point: {queryPoint}");
Console.WriteLine($"Nearest point: {nearest}");
Console.WriteLine($"Distance: {tree.Distance:F6}");
Console.WriteLine($"Nodes visited: {tree.Visited}");
Console.WriteLine("------------------------------------");
}
public static void Main(string[] args)
{
try
{
TestWikipedia();
TestRandom(1000);
TestRandom(100000); // Reduced count for faster C# demo
// TestRandom(1000000); // Can take longer in C# due to sorting overhead vs nth_element
}
catch (Exception e)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.Error.WriteLine($"An error occurred: {e.Message}");
Console.Error.WriteLine(e.StackTrace);
Console.ResetColor();
}
}
}

View file

@ -0,0 +1,220 @@
import 'dart:math';
// Define Point as a list of doubles
typedef Point = List<double>;
// Extension method for calculating squared distance
extension PointExtension on Point {
double sqd(Point p) {
if (length != p.length) {
throw ArgumentError('Points must have the same dimension');
}
double sum = 0.0;
for (int i = 0; i < length; i++) {
double diff = this[i] - p[i];
sum += diff * diff;
}
return sum;
}
}
class HyperRect {
final Point min;
final Point max;
HyperRect(this.min, this.max);
// Create a copy with new lists for min and max
HyperRect copy() => HyperRect(List.from(min), List.from(max));
}
class NearestNeighbor {
final Point? nearest; // Nullable Point
final double distSqd;
final int nodesVisited;
NearestNeighbor(this.nearest, this.distSqd, this.nodesVisited);
}
class KdNode {
final Point domElt;
final int split;
KdNode? left; // Nullable and mutable
KdNode? right; // Nullable and mutable
KdNode(this.domElt, this.split, this.left, this.right);
}
class KdTree {
final KdNode? n; // Root node (nullable)
final HyperRect bounds;
// Constructor builds the tree
KdTree(List<Point> pts, this.bounds)
: n = _buildTree(
List.from(pts),
0,
); // Pass a copy to avoid modifying original
// Static helper method for recursive tree building (like nk2)
static KdNode? _buildTree(List<Point> exset, int split) {
if (exset.isEmpty) {
return null;
}
// Sort the list in-place based on the current split dimension
exset.sort((a, b) => a[split].compareTo(b[split]));
int m = exset.length ~/ 2; // Integer division
final Point d = exset[m];
// Handle duplicate points at the median split value
// Find the last index of the element equal to the median along the split dimension
while (m + 1 < exset.length && exset[m + 1][split] == d[split]) {
m++;
}
int s2 = split + 1;
if (s2 == d.length) {
s2 = 0; // Cycle through dimensions
}
// Dart's sublist creates copies, which is suitable here
List<Point> leftSublist = exset.sublist(0, m);
List<Point> rightSublist = exset.sublist(m + 1);
return KdNode(
d,
split,
_buildTree(leftSublist, s2),
_buildTree(rightSublist, s2),
);
}
// Public method to find the nearest neighbor
NearestNeighbor nearest(Point p) {
// Pass a copy of bounds because _nn might modify its HyperRect parameter
return _nn(n, p, bounds.copy(), double.infinity);
}
// Private recursive helper for nearest neighbor search
NearestNeighbor _nn(
KdNode? kd, // Current node (nullable)
Point target, // Target point
HyperRect hr, // Hyper-rectangle for the current node's space
double maxDistSqd, // Current best squared distance found so far
) {
if (kd == null) {
return NearestNeighbor(null, double.infinity, 0);
}
int nodesVisited = 1;
final int s = kd.split;
final Point pivot = kd.domElt;
// Create copies for left and right potential subspaces
final HyperRect leftHr = hr.copy();
final HyperRect rightHr = hr.copy();
// Adjust bounds for children
leftHr.max[s] = pivot[s];
rightHr.min[s] = pivot[s];
// Determine which branch is nearer
final bool targetInLeft = target[s] <= pivot[s];
final KdNode? nearerKd = targetInLeft ? kd.left : kd.right;
final HyperRect nearerHr = targetInLeft ? leftHr : rightHr;
final KdNode? furtherKd = targetInLeft ? kd.right : kd.left;
final HyperRect furtherHr = targetInLeft ? rightHr : leftHr;
// Recurse down the nearer branch
NearestNeighbor nearerResult = _nn(nearerKd, target, nearerHr, maxDistSqd);
Point? nearest = nearerResult.nearest;
double distSqd = nearerResult.distSqd;
nodesVisited += nearerResult.nodesVisited;
// Update the maximum distance squared if a closer point was found
double maxDistSqd2 = (distSqd < maxDistSqd) ? distSqd : maxDistSqd;
// Check the distance from the target to the splitting plane
double d = pivot[s] - target[s];
d *= d;
// If the splitting plane is further than the current nearest, no need to check further branch
if (d > maxDistSqd2) {
return NearestNeighbor(nearest, distSqd, nodesVisited);
}
// Check distance from target to the pivot point itself
d = pivot.sqd(target); // Use extension method
if (d < distSqd) {
nearest = pivot;
distSqd = d;
maxDistSqd2 = distSqd; // Update max distance again
}
// Recurse down the further branch if necessary
NearestNeighbor furtherResult = _nn(
furtherKd,
target,
furtherHr,
maxDistSqd2,
);
nodesVisited += furtherResult.nodesVisited;
// If the further branch found an even closer point
if (furtherResult.distSqd < distSqd) {
nearest = furtherResult.nearest;
distSqd = furtherResult.distSqd;
}
return NearestNeighbor(nearest, distSqd, nodesVisited);
}
}
// --- Helper Functions and Main ---
final rand = Random();
Point randomPt(int dim) => List.generate(dim, (_) => rand.nextDouble());
List<Point> randomPts(int dim, int n) => List.generate(n, (_) => randomPt(dim));
void showNearest(String heading, KdTree kd, Point p) {
print('$heading:');
print('Point : $p'); // Default List.toString() is fine
final result = kd.nearest(p);
print('Nearest neighbor : ${result.nearest}');
print(
'Distance : ${sqrt(result.distSqd)}',
); // Use sqrt from dart:math
print('Nodes visited : ${result.nodesVisited}');
print(''); // Empty line
}
void main() {
// Note: Using List<Point> instead of MutableList
final points = <Point>[
[2.0, 3.0],
[5.0, 4.0],
[9.0, 6.0],
[4.0, 7.0],
[8.0, 1.0],
[7.0, 2.0],
];
var hr = HyperRect([0.0, 0.0], [10.0, 10.0]);
var kd = KdTree(points, hr); // Pass the original list
showNearest('WP example data', kd, [9.0, 2.0]);
hr = HyperRect([0.0, 0.0, 0.0], [1.0, 1.0, 1.0]);
// Generate random points directly as List<Point>
kd = KdTree(randomPts(3, 1000), hr);
showNearest('1000 random 3D points', kd, randomPt(3));
// Use hr.copy() if you intend to modify hr later, otherwise it's not strictly needed
// The Kotlin code uses copy, so we replicate it here.
var hrCopy = hr.copy();
kd = KdTree(randomPts(3, 400000), hrCopy); // Pass the copy
showNearest('400,000 random 3D points', kd, randomPt(3));
}

View file

@ -0,0 +1,235 @@
/**
* Class for representing a point. coordinate_type must be a numeric type.
*/
class Point {
constructor(coords) {
if (Array.isArray(coords)) {
this.coords = [...coords];
} else if (coords instanceof Object) {
this.coords = Array.from(coords);
}
}
/**
* Returns the coordinate in the given dimension.
*
* @param {number} index dimension index (zero based)
* @return {number} coordinate in the given dimension
*/
get(index) {
return this.coords[index];
}
/**
* Returns the distance squared from this point to another point.
*
* @param {Point} pt another point
* @return {number} distance squared from this point to the other point
*/
distance(pt) {
let dist = 0;
for (let i = 0; i < this.coords.length; ++i) {
const d = this.get(i) - pt.get(i);
dist += d * d;
}
return dist;
}
toString() {
return `(${this.coords.join(', ')})`;
}
}
/**
* JavaScript k-d tree implementation, based on the C++ version.
*/
class KDTree {
constructor(pointsOrGenerator, count) {
this.nodes = [];
this.dimensions = 0;
this.root = null;
this.best = null;
this.bestDist = 0;
this.visited = 0;
if (typeof pointsOrGenerator === 'function' && typeof count === 'number') {
// Constructor with generator function
for (let i = 0; i < count; ++i) {
const point = pointsOrGenerator();
if (i === 0) {
this.dimensions = point.coords.length;
}
this.nodes.push({ point, left: null, right: null });
}
} else if (Array.isArray(pointsOrGenerator)) {
// Constructor with array of points
this.nodes = pointsOrGenerator.map(point => ({ point, left: null, right: null }));
if (this.nodes.length > 0) {
this.dimensions = this.nodes[0].point.coords.length;
}
}
if (this.nodes.length > 0) {
this.root = this.makeTree(0, this.nodes.length, 0);
}
}
/**
* Comparison function for sorting nodes by a specific dimension
*/
nodeCmp(a, b, index) {
return a.point.get(index) - b.point.get(index);
}
/**
* Builds the tree recursively
*/
makeTree(begin, end, index) {
if (end <= begin) return null;
const n = begin + Math.floor((end - begin) / 2);
// Sort and find median
this.nodes.slice(begin, end).sort((a, b) => this.nodeCmp(a, b, index));
// Update index for the next level
const nextIndex = (index + 1) % this.dimensions;
// Recursively build left and right subtrees
this.nodes[n].left = this.makeTree(begin, n, nextIndex);
this.nodes[n].right = this.makeTree(n + 1, end, nextIndex);
return this.nodes[n];
}
/**
* Recursively finds the nearest node to the given point
*/
findNearest(root, point, index) {
if (!root) return;
this.visited++;
const d = root.point.distance(point);
if (!this.best || d < this.bestDist) {
this.bestDist = d;
this.best = root;
}
if (this.bestDist === 0) return;
const dx = root.point.get(index) - point.get(index);
const nextIndex = (index + 1) % this.dimensions;
// Search the side of the splitting plane that contains the point
this.findNearest(dx > 0 ? root.left : root.right, point, nextIndex);
// If the distance to the splitting plane is less than current best distance,
// we need to check the other side too
if (dx * dx >= this.bestDist) return;
this.findNearest(dx > 0 ? root.right : root.left, point, nextIndex);
}
/**
* Returns true if the tree is empty, false otherwise.
*/
empty() {
return this.nodes.length === 0;
}
/**
* Returns the number of nodes visited by the last call to nearest().
*/
getVisited() {
return this.visited;
}
/**
* Returns the distance between the input point and return value
* from the last call to nearest().
*/
getDistance() {
return Math.sqrt(this.bestDist);
}
/**
* Finds the nearest point in the tree to the given point.
* It is not valid to call this function if the tree is empty.
*
* @param {Point} point a point
* @return {Point} the nearest point in the tree to the given point
*/
nearest(point) {
if (!this.root) {
throw new Error("Tree is empty");
}
this.best = null;
this.visited = 0;
this.bestDist = 0;
this.findNearest(this.root, point, 0);
return this.best.point;
}
}
/**
* Recreates the Wikipedia example from the original code
*/
function testWikipedia() {
const points = [
new Point([2, 3]),
new Point([5, 4]),
new Point([9, 6]),
new Point([4, 7]),
new Point([8, 1]),
new Point([7, 2])
];
const tree = new KDTree(points);
const searchPoint = new Point([9, 2]);
const nearest = tree.nearest(searchPoint);
console.log("Wikipedia example data:");
console.log("nearest point:", nearest.toString());
console.log("distance:", tree.getDistance());
console.log("nodes visited:", tree.getVisited());
}
/**
* Generates random points and finds the nearest neighbor
*/
function randomPointGenerator(min, max) {
return () => {
const x = min + Math.random() * (max - min);
const y = min + Math.random() * (max - min);
const z = min + Math.random() * (max - min);
return new Point([x, y, z]);
};
}
function testRandom(count) {
const rpg = randomPointGenerator(0, 1);
const tree = new KDTree(rpg, count);
const point = rpg();
const nearest = tree.nearest(point);
console.log(`Random data (${count} points):`);
console.log("point:", point.toString());
console.log("nearest point:", nearest.toString());
console.log("distance:", tree.getDistance());
console.log("nodes visited:", tree.getVisited());
}
// Main execution
try {
testWikipedia();
console.log();
testRandom(1000);
console.log();
testRandom(10000); // Using 10,000 instead of 1,000,000 for browser performance
} catch (e) {
console.error(e.message);
}