Data update
This commit is contained in:
parent
4d5544505c
commit
4924dd0264
3073 changed files with 55820 additions and 4408 deletions
262
Task/K-d-tree/Elixir/k-d-tree.ex
Normal file
262
Task/K-d-tree/Elixir/k-d-tree.ex
Normal file
|
|
@ -0,0 +1,262 @@
|
|||
defmodule KDTree do
|
||||
@moduledoc """
|
||||
K-D Tree implementation for nearest neighbor search in k-dimensional space.
|
||||
"""
|
||||
|
||||
# Tree data structure
|
||||
# {:node, value, left, right} | :empty
|
||||
# KDTree: %{dims: [function], tree: tree}
|
||||
|
||||
defstruct dims: [], tree: :empty
|
||||
|
||||
@doc """
|
||||
Create an empty k-d tree with the given dimensional accessors.
|
||||
"""
|
||||
def empty(dims) do
|
||||
%KDTree{dims: dims, tree: :empty}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Create a k-d tree with a single value.
|
||||
"""
|
||||
def singleton(dims, value) do
|
||||
empty(dims) |> insert(value)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Insert a value into a k-d tree.
|
||||
"""
|
||||
def insert(%KDTree{dims: dims, tree: tree} = kdtree, value) do
|
||||
cyclic_dims = Stream.cycle(dims)
|
||||
new_tree = ins(cyclic_dims, tree, value)
|
||||
%{kdtree | tree: new_tree}
|
||||
end
|
||||
|
||||
defp ins(_dims, :empty, value) do
|
||||
{:node, value, :empty, :empty}
|
||||
end
|
||||
|
||||
defp ins(dims, {:node, split, left, right}, value) do
|
||||
[d] = Enum.take(dims, 1)
|
||||
remaining_dims = Stream.drop(dims, 1)
|
||||
|
||||
if d.(value) < d.(split) do
|
||||
{:node, split, ins(remaining_dims, left, value), right}
|
||||
else
|
||||
{:node, split, left, ins(remaining_dims, right, value)}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Create a k-d tree from a list using the median-finding algorithm.
|
||||
"""
|
||||
def from_list(dims, values) do
|
||||
cyclic_dims = Stream.cycle(dims)
|
||||
tree = f_list(cyclic_dims, values)
|
||||
%KDTree{dims: dims, tree: tree}
|
||||
end
|
||||
|
||||
defp f_list(_dims, []) do
|
||||
:empty
|
||||
end
|
||||
|
||||
defp f_list(dims, values) do
|
||||
[d] = Enum.take(dims, 1)
|
||||
remaining_dims = Stream.drop(dims, 1)
|
||||
|
||||
sorted = Enum.sort_by(values, d)
|
||||
length = length(sorted)
|
||||
mid_index = div(length, 2)
|
||||
|
||||
{lower, higher} = Enum.split(sorted, mid_index)
|
||||
|
||||
case higher do
|
||||
[] ->
|
||||
:empty
|
||||
[median | rest] ->
|
||||
{:node, median, f_list(remaining_dims, lower), f_list(remaining_dims, rest)}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Create a k-d tree from a list by repeatedly inserting values.
|
||||
Faster than median-finding but can create unbalanced trees.
|
||||
"""
|
||||
def from_list_linear(dims, values) do
|
||||
Enum.reduce(values, empty(dims), &insert(&2, &1))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Find the nearest value to a given value.
|
||||
Returns {nearest_value_or_nil, nodes_visited}.
|
||||
"""
|
||||
def nearest(%KDTree{dims: dims, tree: tree}, value) do
|
||||
cyclic_dims = Stream.cycle(dims)
|
||||
near(cyclic_dims, tree, value, dims)
|
||||
end
|
||||
|
||||
defp near(_dims, :empty, _value, _all_dims) do
|
||||
{nil, 1}
|
||||
end
|
||||
|
||||
defp near(_dims, {:node, split, :empty, :empty}, _value, _all_dims) do
|
||||
{split, 1}
|
||||
end
|
||||
|
||||
defp near(dims, {:node, split, left, right}, value, all_dims) do
|
||||
[d] = Enum.take(dims, 1)
|
||||
remaining_dims = Stream.drop(dims, 1)
|
||||
|
||||
split_dist = sqr_dist(all_dims, value, split)
|
||||
hyperplane_dist = square(d.(value) - d.(split))
|
||||
|
||||
{best_left, left_count} = near(remaining_dims, left, value, all_dims)
|
||||
{best_right, right_count} = near(remaining_dims, right, value, all_dims)
|
||||
|
||||
{{maybe_this_best, this_count}, {maybe_other_best, other_count}} =
|
||||
if d.(value) < d.(split) do
|
||||
{{best_left, left_count}, {best_right, right_count}}
|
||||
else
|
||||
{{best_right, right_count}, {best_left, left_count}}
|
||||
end
|
||||
|
||||
case maybe_this_best do
|
||||
nil ->
|
||||
count = 1 + this_count + other_count
|
||||
case maybe_other_best do
|
||||
nil ->
|
||||
{split, count}
|
||||
other_best ->
|
||||
if sqr_dist(all_dims, value, other_best) < split_dist do
|
||||
{other_best, count}
|
||||
else
|
||||
{split, count}
|
||||
end
|
||||
end
|
||||
|
||||
this_best ->
|
||||
this_best_dist = sqr_dist(all_dims, value, this_best)
|
||||
{best, best_dist} =
|
||||
if split_dist < this_best_dist do
|
||||
{split, split_dist}
|
||||
else
|
||||
{this_best, this_best_dist}
|
||||
end
|
||||
|
||||
if best_dist < hyperplane_dist do
|
||||
{best, 1 + this_count}
|
||||
else
|
||||
count = 1 + this_count + other_count
|
||||
case maybe_other_best do
|
||||
nil ->
|
||||
{best, count}
|
||||
other_best ->
|
||||
if best_dist < sqr_dist(all_dims, value, other_best) do
|
||||
{best, count}
|
||||
else
|
||||
{other_best, count}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Calculate squared Euclidean distance between two points.
|
||||
"""
|
||||
def sqr_dist(dims, a, b) do
|
||||
a_values = Enum.map(dims, & &1.(a))
|
||||
b_values = Enum.map(dims, & &1.(b))
|
||||
|
||||
Enum.zip(a_values, b_values)
|
||||
|> Enum.map(fn {x, y} -> square(x - y) end)
|
||||
|> Enum.sum()
|
||||
end
|
||||
|
||||
defp square(x), do: x * x
|
||||
|
||||
@doc """
|
||||
Dimensional accessors for 2-tuple.
|
||||
"""
|
||||
def tuple_2d do
|
||||
[&elem(&1, 0), &elem(&1, 1)]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Dimensional accessors for 3-tuple.
|
||||
"""
|
||||
def tuple_3d do
|
||||
[&elem(&1, 0), &elem(&1, 1), &elem(&1, 2)]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Naive nearest search for verification.
|
||||
"""
|
||||
def linear_nearest(_dims, _value, []) do
|
||||
nil
|
||||
end
|
||||
|
||||
def linear_nearest(dims, value, [head | tail]) do
|
||||
Enum.min_by([head | tail], &sqr_dist(dims, value, &1))
|
||||
end
|
||||
|
||||
@doc """
|
||||
Print search results.
|
||||
"""
|
||||
def print_results(point, {nearest, visited}, dims) do
|
||||
case nearest do
|
||||
nil ->
|
||||
IO.puts("Could not find nearest.")
|
||||
value ->
|
||||
dist = :math.sqrt(sqr_dist(dims, point, value))
|
||||
IO.puts("Point: #{inspect(point)}")
|
||||
IO.puts("Nearest: #{inspect(value)}")
|
||||
IO.puts("Distance: #{dist}")
|
||||
IO.puts("Visited: #{visited}")
|
||||
IO.puts("")
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Generate random 3D points.
|
||||
"""
|
||||
def random_3d_points(n, {min_x, min_y, min_z}, {max_x, max_y, max_z}) do
|
||||
for _ <- 1..n do
|
||||
{
|
||||
min_x + :rand.uniform() * (max_x - min_x),
|
||||
min_y + :rand.uniform() * (max_y - min_y),
|
||||
min_z + :rand.uniform() * (max_z - min_z)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Main demonstration function.
|
||||
"""
|
||||
def main do
|
||||
# Wikipedia example
|
||||
wiki_values = [{2, 3}, {5, 4}, {9, 6}, {4, 7}, {8, 1}, {7, 2}]
|
||||
wiki_tree = from_list(tuple_2d(), wiki_values)
|
||||
wiki_search = {9, 2}
|
||||
wiki_nearest = nearest(wiki_tree, wiki_search)
|
||||
|
||||
IO.puts("Wikipedia example:")
|
||||
print_results(wiki_search, wiki_nearest, tuple_2d())
|
||||
|
||||
# Random 3D example
|
||||
:rand.seed(:exsplus, {1, 2, 3}) # Seed for reproducible results
|
||||
rand_range = {{0, 0, 0}, {1000, 1000, 1000}}
|
||||
rand_values = random_3d_points(1000, elem(rand_range, 0), elem(rand_range, 1))
|
||||
rand_search = hd(random_3d_points(1, elem(rand_range, 0), elem(rand_range, 1)))
|
||||
rand_tree = from_list(tuple_3d(), rand_values)
|
||||
rand_nearest = nearest(rand_tree, rand_search)
|
||||
rand_nearest_linear = linear_nearest(tuple_3d(), rand_search, rand_values)
|
||||
|
||||
IO.puts("1000 random 3D points on the range of [0, 1000):")
|
||||
print_results(rand_search, rand_nearest, tuple_3d())
|
||||
IO.puts("Confirm naive nearest:")
|
||||
IO.puts("#{inspect(rand_nearest_linear)}")
|
||||
end
|
||||
end
|
||||
|
||||
KDTree.main()
|
||||
203
Task/K-d-tree/Swift/k-d-tree.swift
Normal file
203
Task/K-d-tree/Swift/k-d-tree.swift
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
import Foundation
|
||||
|
||||
// point is a k-dimensional point.
|
||||
typealias Point = [Double]
|
||||
|
||||
extension Point {
|
||||
// sqd returns the square of the euclidean distance.
|
||||
func sqd(to q: Point) -> Double {
|
||||
var sum: Double = 0
|
||||
for dim in 0..<self.count {
|
||||
let d = self[dim] - q[dim]
|
||||
sum += d * d
|
||||
}
|
||||
return sum
|
||||
}
|
||||
}
|
||||
|
||||
// kdNode following field names in the paper.
|
||||
// rangeElt would be whatever data is associated with the point.
|
||||
class KDNode {
|
||||
let domElt: Point
|
||||
let split: Int
|
||||
let left: KDNode?
|
||||
let right: KDNode?
|
||||
|
||||
init(domElt: Point, split: Int, left: KDNode?, right: KDNode?) {
|
||||
self.domElt = domElt
|
||||
self.split = split
|
||||
self.left = left
|
||||
self.right = right
|
||||
}
|
||||
}
|
||||
|
||||
struct HyperRect {
|
||||
var min: Point
|
||||
var max: Point
|
||||
|
||||
// Create a copy of the hyperrect
|
||||
func copy() -> HyperRect {
|
||||
return HyperRect(min: Array(self.min), max: Array(self.max))
|
||||
}
|
||||
}
|
||||
|
||||
class KDTree {
|
||||
let root: KDNode?
|
||||
let bounds: HyperRect
|
||||
|
||||
init(root: KDNode?, bounds: HyperRect) {
|
||||
self.root = root
|
||||
self.bounds = bounds
|
||||
}
|
||||
|
||||
// nearest. find nearest neighbor. return values are:
|
||||
// nearest neighbor - the point within the tree that is nearest p.
|
||||
// square of the distance to that point.
|
||||
// a count of the nodes visited in the search.
|
||||
func nearest(to target: Point) -> (nearest: Point?, distSqd: Double, nodesVisited: Int) {
|
||||
return nn(kd: root, target: target, hr: bounds, maxDistSqd: .infinity)
|
||||
}
|
||||
|
||||
// algorithm is table 6.4 from the paper
|
||||
private func nn(kd: KDNode?, target: Point, hr: HyperRect,
|
||||
maxDistSqd: Double) -> (nearest: Point?, distSqd: Double, nodesVisited: Int) {
|
||||
if kd == nil {
|
||||
return (nil, .infinity, 0)
|
||||
}
|
||||
|
||||
var nodesVisited = 1
|
||||
let s = kd!.split
|
||||
let pivot = kd!.domElt
|
||||
var leftHr = hr.copy()
|
||||
var rightHr = hr.copy()
|
||||
leftHr.max[s] = pivot[s]
|
||||
rightHr.min[s] = pivot[s]
|
||||
let targetInLeft = target[s] <= pivot[s]
|
||||
|
||||
let (nearerKd, nearerHr, furtherKd, furtherHr): (KDNode?, HyperRect, KDNode?, HyperRect)
|
||||
if targetInLeft {
|
||||
nearerKd = kd!.left
|
||||
nearerHr = leftHr
|
||||
furtherKd = kd!.right
|
||||
furtherHr = rightHr
|
||||
} else {
|
||||
nearerKd = kd!.right
|
||||
nearerHr = rightHr
|
||||
furtherKd = kd!.left
|
||||
furtherHr = leftHr
|
||||
}
|
||||
|
||||
var (nearest, distSqd, nv) = nn(kd: nearerKd, target: target, hr: nearerHr, maxDistSqd: maxDistSqd)
|
||||
nodesVisited += nv
|
||||
|
||||
var maxDist = maxDistSqd
|
||||
if distSqd < maxDist {
|
||||
maxDist = distSqd
|
||||
}
|
||||
|
||||
var d = pivot[s] - target[s]
|
||||
d *= d
|
||||
if d > maxDist {
|
||||
return (nearest, distSqd, nodesVisited)
|
||||
}
|
||||
|
||||
d = pivot.sqd(to: target)
|
||||
if d < distSqd {
|
||||
nearest = pivot
|
||||
distSqd = d
|
||||
maxDist = distSqd
|
||||
}
|
||||
|
||||
let (tempNearest, tempSqd, nv2) = nn(kd: furtherKd, target: target, hr: furtherHr, maxDistSqd: maxDist)
|
||||
nodesVisited += nv2
|
||||
|
||||
if tempSqd < distSqd {
|
||||
nearest = tempNearest
|
||||
distSqd = tempSqd
|
||||
}
|
||||
|
||||
return (nearest, distSqd, nodesVisited)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to create a new KD tree
|
||||
func newKd(pts: [Point], bounds: HyperRect) -> KDTree {
|
||||
func nk2(exset: [Point], split: Int) -> KDNode? {
|
||||
if exset.isEmpty {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Sort points by the split dimension
|
||||
let sortedPoints = exset.sorted { $0[split] < $1[split] }
|
||||
var m = sortedPoints.count / 2
|
||||
let d = sortedPoints[m]
|
||||
|
||||
// Find largest index of points with median value
|
||||
while m+1 < sortedPoints.count && sortedPoints[m+1][split] == d[split] {
|
||||
m += 1
|
||||
}
|
||||
|
||||
// Next split
|
||||
var s2 = split + 1
|
||||
if s2 == d.count {
|
||||
s2 = 0
|
||||
}
|
||||
|
||||
return KDNode(
|
||||
domElt: d,
|
||||
split: split,
|
||||
left: nk2(exset: Array(sortedPoints[0..<m]), split: s2),
|
||||
right: nk2(exset: Array(sortedPoints[(m+1)...]), split: s2)
|
||||
)
|
||||
}
|
||||
|
||||
return KDTree(root: nk2(exset: pts, split: 0), bounds: bounds)
|
||||
}
|
||||
|
||||
// Helper functions to generate random points
|
||||
func randomPt(dim: Int) -> Point {
|
||||
var p = Point(repeating: 0, count: dim)
|
||||
for d in 0..<dim {
|
||||
p[d] = Double.random(in: 0...1)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func randomPts(dim: Int, n: Int) -> [Point] {
|
||||
var p = [Point](repeating: [], count: n)
|
||||
for i in 0..<n {
|
||||
p[i] = randomPt(dim: dim)
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
func showNearest(heading: String, kd: KDTree, p: Point) {
|
||||
print()
|
||||
print(heading)
|
||||
print("point: ", p)
|
||||
let (nn, ssq, nv) = kd.nearest(to: p)
|
||||
print("nearest neighbor:", nn ?? "nil")
|
||||
print("distance: ", sqrt(ssq))
|
||||
print("nodes visited: ", nv)
|
||||
}
|
||||
|
||||
// Main function
|
||||
func main() {
|
||||
// Set random seed
|
||||
srand48(Int(time(nil)))
|
||||
|
||||
var kd = newKd(
|
||||
pts: [[2, 3], [5, 4], [9, 6], [4, 7], [8, 1], [7, 2]],
|
||||
bounds: HyperRect(min: [0, 0], max: [10, 10])
|
||||
)
|
||||
showNearest(heading: "WP example data", kd: kd, p: [9, 2])
|
||||
|
||||
kd = newKd(
|
||||
pts: randomPts(dim: 3, n: 1000),
|
||||
bounds: HyperRect(min: [0, 0, 0], max: [1, 1, 1])
|
||||
)
|
||||
showNearest(heading: "1000 random 3d points", kd: kd, p: randomPt(dim: 3))
|
||||
}
|
||||
|
||||
// Run the main function
|
||||
main()
|
||||
358
Task/K-d-tree/Zig/k-d-tree.zig
Normal file
358
Task/K-d-tree/Zig/k-d-tree.zig
Normal file
|
|
@ -0,0 +1,358 @@
|
|||
const std = @import("std");
|
||||
const print = std.debug.print;
|
||||
const ArrayList = std.ArrayList;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const Random = std.Random;
|
||||
|
||||
const Point = struct {
|
||||
coords: []f32,
|
||||
allocator: Allocator,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(allocator: Allocator, coords: []const f32) !Self {
|
||||
const owned_coords = try allocator.dupe(f32, coords);
|
||||
return Self{
|
||||
.coords = owned_coords,
|
||||
.allocator = allocator,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: Self) void {
|
||||
self.allocator.free(self.coords);
|
||||
}
|
||||
|
||||
pub fn clone(self: Self) !Self {
|
||||
return Self.init(self.allocator, self.coords);
|
||||
}
|
||||
|
||||
pub fn sub(self: Self, other: Self, allocator: Allocator) !Point {
|
||||
std.debug.assert(self.coords.len == other.coords.len);
|
||||
const result_coords = try allocator.alloc(f32, self.coords.len);
|
||||
for (0..self.coords.len) |i| {
|
||||
result_coords[i] = self.coords[i] - other.coords[i];
|
||||
}
|
||||
return Point{
|
||||
.coords = result_coords,
|
||||
.allocator = allocator,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn normSq(self: Self) f32 {
|
||||
var sum: f32 = 0.0;
|
||||
for (self.coords) |coord| {
|
||||
sum += coord * coord;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
pub fn format(self: Self, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
|
||||
_ = fmt;
|
||||
_ = options;
|
||||
try writer.writeAll("[");
|
||||
for (self.coords, 0..) |coord, i| {
|
||||
if (i > 0) try writer.writeAll(", ");
|
||||
try writer.print("{d:.1}", .{coord});
|
||||
}
|
||||
try writer.writeAll("]");
|
||||
}
|
||||
};
|
||||
|
||||
const KDTreeNode = struct {
|
||||
point: Point,
|
||||
dim: usize,
|
||||
left: ?*KDTreeNode,
|
||||
right: ?*KDTreeNode,
|
||||
allocator: Allocator,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(allocator: Allocator, points: []Point, dim: usize) !*Self {
|
||||
const points_len = points.len;
|
||||
|
||||
if (points_len == 1) {
|
||||
const node = try allocator.create(Self);
|
||||
node.* = Self{
|
||||
.point = try points[0].clone(),
|
||||
.dim = dim,
|
||||
.left = null,
|
||||
.right = null,
|
||||
.allocator = allocator,
|
||||
};
|
||||
return node;
|
||||
}
|
||||
|
||||
// Split around the median
|
||||
const pivot = try quickselectBy(points, points_len / 2, dim, allocator);
|
||||
|
||||
const left = if (points_len >= 2)
|
||||
try Self.init(allocator, points[0..points_len / 2], (dim + 1) % pivot.coords.len)
|
||||
else
|
||||
null;
|
||||
|
||||
const right = if (points_len >= 3)
|
||||
try Self.init(allocator, points[points_len / 2 + 1..points_len], (dim + 1) % pivot.coords.len)
|
||||
else
|
||||
null;
|
||||
|
||||
const node = try allocator.create(Self);
|
||||
node.* = Self{
|
||||
.point = pivot,
|
||||
.dim = dim,
|
||||
.left = left,
|
||||
.right = right,
|
||||
.allocator = allocator,
|
||||
};
|
||||
return node;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.point.deinit();
|
||||
if (self.left) |left| {
|
||||
left.deinit();
|
||||
self.allocator.destroy(left);
|
||||
}
|
||||
if (self.right) |right| {
|
||||
right.deinit();
|
||||
self.allocator.destroy(right);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn findNearestNeighbor(self: *const Self, point: Point, allocator: Allocator) !struct { point: Point, n_visited: usize } {
|
||||
const diff = try point.sub(self.point, allocator);
|
||||
defer diff.deinit();
|
||||
const initial_dist_sq = diff.normSq();
|
||||
|
||||
const result = try self.findNearestNeighborHelper(point, self.point, initial_dist_sq, 1, allocator);
|
||||
return .{ .point = try result.point.clone(), .n_visited = result.n_visited };
|
||||
}
|
||||
|
||||
fn findNearestNeighborHelper(
|
||||
self: *const Self,
|
||||
point: Point,
|
||||
best: Point,
|
||||
best_dist_sq: f32,
|
||||
n_visited: usize,
|
||||
allocator: Allocator,
|
||||
) !struct { point: Point, n_visited: usize } {
|
||||
var my_best = best;
|
||||
var my_best_dist_sq = best_dist_sq;
|
||||
var my_n_visited = n_visited;
|
||||
|
||||
// Examine the near side first
|
||||
if (self.point.coords[self.dim] < point.coords[self.dim] and self.right != null) {
|
||||
const result = try self.right.?.findNearestNeighborHelper(
|
||||
point, my_best, my_best_dist_sq, my_n_visited, allocator
|
||||
);
|
||||
my_best = result.point;
|
||||
my_n_visited = result.n_visited;
|
||||
} else if (self.left != null) {
|
||||
const result = try self.left.?.findNearestNeighborHelper(
|
||||
point, my_best, my_best_dist_sq, my_n_visited, allocator
|
||||
);
|
||||
my_best = result.point;
|
||||
my_n_visited = result.n_visited;
|
||||
}
|
||||
|
||||
// Distance along this node's axis
|
||||
const axis_dist_sq = std.math.pow(f32, self.point.coords[self.dim] - point.coords[self.dim], 2);
|
||||
if (axis_dist_sq <= my_best_dist_sq) {
|
||||
// Check if this node is closer than current best
|
||||
const self_diff = try point.sub(self.point, allocator);
|
||||
defer self_diff.deinit();
|
||||
const self_dist_sq = self_diff.normSq();
|
||||
|
||||
if (self_dist_sq < my_best_dist_sq) {
|
||||
my_best = self.point;
|
||||
my_best_dist_sq = self_dist_sq;
|
||||
}
|
||||
|
||||
my_n_visited += 1;
|
||||
|
||||
// Check the far side of the split
|
||||
if (self.point.coords[self.dim] < point.coords[self.dim] and self.left != null) {
|
||||
const result = try self.left.?.findNearestNeighborHelper(
|
||||
point, my_best, my_best_dist_sq, my_n_visited, allocator
|
||||
);
|
||||
my_best = result.point;
|
||||
my_n_visited = result.n_visited;
|
||||
} else if (self.right != null) {
|
||||
const result = try self.right.?.findNearestNeighborHelper(
|
||||
point, my_best, my_best_dist_sq, my_n_visited, allocator
|
||||
);
|
||||
my_best = result.point;
|
||||
my_n_visited = result.n_visited;
|
||||
}
|
||||
}
|
||||
|
||||
return .{ .point = my_best, .n_visited = my_n_visited };
|
||||
}
|
||||
};
|
||||
|
||||
fn quickselectBy(arr: []Point, position: usize, dim: usize, allocator: Allocator) !Point {
|
||||
if (arr.len == 1) return try arr[0].clone();
|
||||
|
||||
var rng = std.Random.DefaultPrng.init(@intCast(std.time.timestamp()));
|
||||
const random = rng.random();
|
||||
|
||||
var pivot_index = random.uintLessThan(usize, arr.len);
|
||||
pivot_index = partitionBy(arr, pivot_index, dim);
|
||||
|
||||
const array_len = arr.len;
|
||||
if (position == pivot_index) {
|
||||
return try arr[position].clone();
|
||||
} else if (position < pivot_index) {
|
||||
return quickselectBy(arr[0..pivot_index], position, dim, allocator);
|
||||
} else {
|
||||
return quickselectBy(arr[pivot_index + 1..array_len], position - pivot_index - 1, dim, allocator);
|
||||
}
|
||||
}
|
||||
|
||||
fn partitionBy(arr: []Point, pivot_index: usize, dim: usize) usize {
|
||||
const array_len = arr.len;
|
||||
std.mem.swap(Point, &arr[pivot_index], &arr[array_len - 1]);
|
||||
var store_index: usize = 0;
|
||||
|
||||
for (0..array_len - 1) |i| {
|
||||
if (arr[i].coords[dim] < arr[array_len - 1].coords[dim]) {
|
||||
std.mem.swap(Point, &arr[i], &arr[store_index]);
|
||||
store_index += 1;
|
||||
}
|
||||
}
|
||||
std.mem.swap(Point, &arr[array_len - 1], &arr[store_index]);
|
||||
return store_index;
|
||||
}
|
||||
|
||||
pub fn main() !void {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer _ = gpa.deinit();
|
||||
const allocator = gpa.allocator();
|
||||
|
||||
var rng = std.Random.DefaultPrng.init(@intCast(std.time.timestamp()));
|
||||
const random = rng.random();
|
||||
|
||||
// Wikipedia example
|
||||
const wp_coords = [_][]const f32{
|
||||
&[_]f32{ 2.0, 3.0 },
|
||||
&[_]f32{ 5.0, 4.0 },
|
||||
&[_]f32{ 9.0, 6.0 },
|
||||
&[_]f32{ 4.0, 7.0 },
|
||||
&[_]f32{ 8.0, 1.0 },
|
||||
&[_]f32{ 7.0, 2.0 },
|
||||
};
|
||||
|
||||
var wp_points = ArrayList(Point).init(allocator);
|
||||
defer {
|
||||
for (wp_points.items) |point| {
|
||||
point.deinit();
|
||||
}
|
||||
wp_points.deinit();
|
||||
}
|
||||
|
||||
for (wp_coords) |coords| {
|
||||
try wp_points.append(try Point.init(allocator, coords));
|
||||
}
|
||||
|
||||
const wp_tree = try KDTreeNode.init(allocator, wp_points.items, 0);
|
||||
defer {
|
||||
wp_tree.deinit();
|
||||
allocator.destroy(wp_tree);
|
||||
}
|
||||
|
||||
const wp_target = try Point.init(allocator, &[_]f32{ 9.0, 2.0 });
|
||||
defer wp_target.deinit();
|
||||
|
||||
const wp_result = try wp_tree.findNearestNeighbor(wp_target, allocator);
|
||||
defer wp_result.point.deinit();
|
||||
|
||||
const wp_diff = try wp_result.point.sub(wp_target, allocator);
|
||||
defer wp_diff.deinit();
|
||||
|
||||
print("Wikipedia example data:\n", .{});
|
||||
print("Point: {}\n", .{wp_target});
|
||||
print("Nearest neighbor: {}\n", .{wp_result.point});
|
||||
print("Distance: {d:.6}\n", .{@sqrt(wp_diff.normSq())});
|
||||
print("Nodes visited: {}\n", .{wp_result.n_visited});
|
||||
|
||||
// Randomly generated 3D points
|
||||
const n_random = 1000;
|
||||
var random_points = ArrayList(Point).init(allocator);
|
||||
defer {
|
||||
for (random_points.items) |point| {
|
||||
point.deinit();
|
||||
}
|
||||
random_points.deinit();
|
||||
}
|
||||
|
||||
for (0..n_random) |_| {
|
||||
const coords = [_]f32{
|
||||
(random.float(f32) - 0.5) * 1000.0,
|
||||
(random.float(f32) - 0.5) * 1000.0,
|
||||
(random.float(f32) - 0.5) * 1000.0,
|
||||
};
|
||||
try random_points.append(try Point.init(allocator, &coords));
|
||||
}
|
||||
|
||||
const start_cons_time = std.time.nanoTimestamp();
|
||||
const random_tree = try KDTreeNode.init(allocator, random_points.items, 0);
|
||||
const cons_time = std.time.nanoTimestamp() - start_cons_time;
|
||||
defer {
|
||||
random_tree.deinit();
|
||||
allocator.destroy(random_tree);
|
||||
}
|
||||
|
||||
print("1,000 3d points (Construction time: {}ms)\n", .{@divTrunc(cons_time, 1_000_000)});
|
||||
|
||||
const random_target_coords = [_]f32{
|
||||
(random.float(f32) - 0.5) * 1000.0,
|
||||
(random.float(f32) - 0.5) * 1000.0,
|
||||
(random.float(f32) - 0.5) * 1000.0,
|
||||
};
|
||||
const random_target = try Point.init(allocator, &random_target_coords);
|
||||
defer random_target.deinit();
|
||||
|
||||
const random_result = try random_tree.findNearestNeighbor(random_target, allocator);
|
||||
defer random_result.point.deinit();
|
||||
|
||||
const random_diff = try random_result.point.sub(random_target, allocator);
|
||||
defer random_diff.deinit();
|
||||
|
||||
print("Point: {}\n", .{random_target});
|
||||
print("Nearest neighbor: {}\n", .{random_result.point});
|
||||
print("Distance: {d:.6}\n", .{@sqrt(random_diff.normSq())});
|
||||
print("Nodes visited: {}\n", .{random_result.n_visited});
|
||||
|
||||
// Benchmark search time
|
||||
const n_searches = 1000;
|
||||
var random_targets = ArrayList(Point).init(allocator);
|
||||
defer {
|
||||
for (random_targets.items) |point| {
|
||||
point.deinit();
|
||||
}
|
||||
random_targets.deinit();
|
||||
}
|
||||
|
||||
for (0..n_searches) |_| {
|
||||
const coords = [_]f32{
|
||||
(random.float(f32) - 0.5) * 1000.0,
|
||||
(random.float(f32) - 0.5) * 1000.0,
|
||||
(random.float(f32) - 0.5) * 1000.0,
|
||||
};
|
||||
try random_targets.append(try Point.init(allocator, &coords));
|
||||
}
|
||||
|
||||
const start_search_time = std.time.nanoTimestamp();
|
||||
var total_n_visited: usize = 0;
|
||||
for (random_targets.items) |target| {
|
||||
const result = try random_tree.findNearestNeighbor(target, allocator);
|
||||
defer result.point.deinit();
|
||||
total_n_visited += result.n_visited;
|
||||
}
|
||||
const search_time = std.time.nanoTimestamp() - start_search_time;
|
||||
|
||||
print("Visited an average of {d:.1} nodes on {} searches in {} ms\n", .{
|
||||
@as(f32, @floatFromInt(total_n_visited)) / @as(f32, @floatFromInt(n_searches)),
|
||||
n_searches,
|
||||
@divTrunc(search_time, 1_000_000),
|
||||
});
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue