Data update
This commit is contained in:
parent
72eb4943cb
commit
4d5544505c
2347 changed files with 62432 additions and 16731 deletions
472
Task/K-d-tree/C-sharp/k-d-tree.cs
Normal file
472
Task/K-d-tree/C-sharp/k-d-tree.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
220
Task/K-d-tree/Dart/k-d-tree.dart
Normal file
220
Task/K-d-tree/Dart/k-d-tree.dart
Normal 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));
|
||||
}
|
||||
235
Task/K-d-tree/JavaScript/k-d-tree.js
Normal file
235
Task/K-d-tree/JavaScript/k-d-tree.js
Normal 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);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue