import 'dart:math'; // Define Point as a list of doubles typedef Point = List; // 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 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 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 leftSublist = exset.sublist(0, m); List 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 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 instead of MutableList final points = [ [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 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)); }