Skip to content

Commit

Permalink
feat(distance): update INeighborhood, KNearest
Browse files Browse the repository at this point in the history
- expose `dist` impl in INeighborhood
- disable sort-by-default in KNearest
- fix Knearest.reset dist metric
- add docs
- update tests
  • Loading branch information
postspectacular committed Jan 20, 2021
1 parent eb334fa commit be3e43d
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 19 deletions.
4 changes: 4 additions & 0 deletions packages/distance/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ export interface IDistance<T> {
}

export interface INeighborhood<P, T> extends IReset {
/**
* The distance metric used by this neighborhood
*/
readonly dist: IDistance<P>;
/**
* The neighborhood's target position / centroid
*/
Expand Down
51 changes: 41 additions & 10 deletions packages/distance/src/knearest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ import type { ReadonlyVec } from "@thi.ng/vectors";
import type { IDistance, INeighborhood, Neighbor } from "./api";
import { DIST_SQ, DIST_SQ1, DIST_SQ2, DIST_SQ3 } from "./squared";

/**
* A {@link INeighborhood} implementation for K-nearest neighbor queries around
* a given target location, initial query radius and {@link IDistance} metric to
* determine proximity.
*
* @remarks
* The K-nearest neighbors will be accumulated via an internal
* {@link @thi.ng/heaps#Heap} and results can be optionally returned in order of
* proximity. For K=1 it will be more efficient to use {@link Nearest} to avoid
* the additional overhead.
*/
export class KNearest<D, T>
implements INeighborhood<D, T>, IDeref<Neighbor<T>[]> {
protected maxR!: number;
Expand All @@ -16,13 +27,13 @@ export class KNearest<D, T>
public readonly target: D,
public readonly k: number,
public readonly radius = Infinity,
public sorted = true
public sorted = false
) {
this.reset();
}

reset() {
this.maxR = this.dist.from(this.radius);
this.maxR = this.dist.to(this.radius);
this.heap.clear();
return this;
}
Expand All @@ -31,11 +42,23 @@ export class KNearest<D, T>
* Returns an array of current nearest neighbor result tuples (each `[dist,
* val]`). The array will contain at most `k` items and if the `sorted` ctor
* arg was true, will be sorted by distance.
*
* @remarks
* Use {@link KNearest.values} to obtain result values **without** their distance
* metrics.
*/
deref() {
return this.sorted ? this.heap.max() : this.heap.values;
}

/**
* Similar to {@link KNearest.deref}, but returns array of result values **without**
* their distance metrics.
*/
values() {
return this.deref().map((x) => x[1]);
}

includesDistance(d: number, eucledian = true) {
return (eucledian ? this.dist.to(d) : d) <= this.maxR;
}
Expand Down Expand Up @@ -64,13 +87,15 @@ export class KNearest<D, T>
* @param k
* @param r
* @param dist
* @param sorted
*/
export const knearest = <T>(
p: ReadonlyVec,
k: number,
r?: number,
dist = DIST_SQ
) => new KNearest<ReadonlyVec, T>(dist, p, k, r);
dist = DIST_SQ,
sorted?: boolean
) => new KNearest<ReadonlyVec, T>(dist, p, k, r, sorted);

/**
* Defines a {@link KNearest} instance for 2D vector positions and, by default,
Expand All @@ -80,13 +105,15 @@ export const knearest = <T>(
* @param k
* @param r
* @param dist
* @param sorted
*/
export const knearest2 = <T>(
p: ReadonlyVec,
k: number,
r?: number,
dist = DIST_SQ2
) => new KNearest<ReadonlyVec, T>(dist, p, k, r);
dist = DIST_SQ2,
sorted?: boolean
) => new KNearest<ReadonlyVec, T>(dist, p, k, r, sorted);

/**
* Defines a {@link KNearest} instance for 3D vector positions, by default,
Expand All @@ -96,13 +123,15 @@ export const knearest2 = <T>(
* @param k
* @param r
* @param dist
* @param sorted
*/
export const knearest3 = <T>(
p: ReadonlyVec,
k: number,
r?: number,
dist = DIST_SQ3
) => new KNearest<ReadonlyVec, T>(dist, p, k, r);
dist = DIST_SQ3,
sorted?: boolean
) => new KNearest<ReadonlyVec, T>(dist, p, k, r, sorted);

/**
* Defines a {@link KNearest} instance for numeric positions and, by default,
Expand All @@ -112,10 +141,12 @@ export const knearest3 = <T>(
* @param k
* @param r
* @param dist
* @param sorted
*/
export const knearestN = <T>(
p: number,
k: number,
r?: number,
dist = DIST_SQ1
) => new KNearest<number, T>(dist, p, k, r);
dist = DIST_SQ1,
sorted?: boolean
) => new KNearest<number, T>(dist, p, k, r, sorted);
2 changes: 1 addition & 1 deletion packages/distance/src/nearest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export class Nearest<D, T>

/**
* Defines a {@link Nearest} instance for arbitrary length vector positions, by
* default, using an infinite region radius and {@link DIST_SQ3} distance
* default, using an infinite region radius and {@link DIST_SQ} distance
* metric.
*
* @param p
Expand Down
16 changes: 8 additions & 8 deletions packages/distance/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,26 +71,26 @@ describe("distance", () => {
});

it("knearestN (inf)", () => {
const a = knearestN<number>(10, 2, Infinity, DIST_SQ1);
const a = knearestN<number>(10, 2, Infinity, DIST_SQ1, true);
assert.deepStrictEqual(
[5, 9, 12, 11].map((x) => a.consider(x, x)),
[25, 1, 4, 1]
[5, 8, 13, 11].map((x) => a.consider(x, x)),
[25, 4, 9, 1]
);
assert.deepStrictEqual(a.deref(), [
[1, 11],
[1, 9],
[4, 8],
]);
});

it("knearestN (radius)", () => {
const a = knearestN<number>(10, 2, 2, DIST_SQ1);
const a = knearestN<number>(10, 2, 2, DIST_SQ1, true);
assert.deepStrictEqual(
[5, 9, 12, 11].map((x) => a.consider(x, x)),
[25, 1, 4, 1]
[5, 8, 13, 11].map((x) => a.consider(x, x)),
[25, 4, 9, 1]
);
assert.deepStrictEqual(a.deref(), [
[1, 9],
[1, 11],
[4, 8],
]);
});
});

0 comments on commit be3e43d

Please sign in to comment.