@@ -13,49 +13,6 @@ if (!String.prototype.padStart) {
1313// carterian products
1414export const cart = ( ...args ) => args . reduce ( ( xs , a ) => xs . flatMap ( xsi => a . map ( ai => [ ...xsi , ai ] ) ) , [ [ ] ] ) ;
1515
16- const NS2 = cart ( [ - 1 , 0 , 1 ] , [ - 1 , 0 , 1 ] ) ;
17-
18- export const neighbors2 = ( [ x , y ] ) => {
19- const xDec = parseInt ( x , 2 ) ;
20- const yDec = parseInt ( y , 2 ) ;
21- const nodes = [ ] ;
22- const n = x . length , N = 2 ** n ;
23- for ( let i = 0 ; i < NS2 . length ; i ++ ) {
24- const X = xDec + NS2 [ i ] [ 0 ] ,
25- Y = yDec + NS2 [ i ] [ 1 ] ;
26- if ( X >= 0 && X < N && Y >= 0 && Y < N ) {
27- nodes . push ( [
28- X . toString ( 2 ) . padStart ( n , 0 ) ,
29- Y . toString ( 2 ) . padStart ( n , 0 )
30- ] ) ;
31- }
32- }
33- return nodes ;
34- } ;
35-
36- const NS3 = cart ( [ - 1 , 0 , 1 ] , [ - 1 , 0 , 1 ] , [ - 1 , 0 , 1 ] ) ;
37-
38- export const neighbors3 = ( [ x , y , z ] ) => {
39- const xDec = parseInt ( x , 2 ) ;
40- const yDec = parseInt ( y , 2 ) ;
41- const zDec = parseInt ( z , 2 ) ;
42- const nodes = [ ] ;
43- const n = x . length , N = 2 ** n ;
44- for ( let i = 0 ; i < NS3 . length ; i ++ ) {
45- const X = xDec + NS3 [ i ] [ 0 ] ,
46- Y = yDec + NS3 [ i ] [ 1 ] ,
47- Z = zDec + NS3 [ i ] [ 2 ] ;
48- if ( X >= 0 && X < N && Y >= 0 && Y < N && Z >= 0 && Z < N ) {
49- nodes . push ( [
50- X . toString ( 2 ) . padStart ( n , 0 ) ,
51- Y . toString ( 2 ) . padStart ( n , 0 ) ,
52- Z . toString ( 2 ) . padStart ( n , 0 )
53- ] ) ;
54- }
55- }
56- return nodes ;
57- } ;
58-
5916const dist2 = ( c1 , c2 ) => ( c1 [ 0 ] - c2 [ 0 ] ) ** 2 + ( c1 [ 1 ] - c2 [ 1 ] ) ** 2 ;
6017const dist3 = ( c1 , c2 ) => ( c1 [ 0 ] - c2 [ 0 ] ) ** 2 + ( c1 [ 1 ] - c2 [ 1 ] ) ** 2 + ( c1 [ 2 ] - c2 [ 2 ] ) ** 2 ;
6118const distk = ( c1 , c2 ) => c1 . map ( ( _ , i ) => ( c1 [ i ] - c2 [ i ] ) ** 2 ) . reduce ( ( a , b ) => a + b ) ;
@@ -95,20 +52,55 @@ const getCoord2 = (bins, i) => bins[0][i] + bins[1][i];
9552const getCoord3 = ( bins , i ) => bins [ 0 ] [ i ] + bins [ 1 ] [ i ] + bins [ 2 ] [ i ] ;
9653const getCoordk = ( bins , i ) => bins . map ( s => s [ i ] ) . reduce ( ( a , b ) => a + b ) ;
9754
55+ // min distance between point coords and the bounding top-left, bottom-right points, with resolution res
56+ const minDist = ( coords , tl , br , res ) => Math . min (
57+ ...coords . map (
58+ ( c , j ) => Math . min (
59+ c - ( tl [ j ] << res ) ,
60+ ( br [ j ] + 1 ) << res ) - c
61+ )
62+ ) ;
63+
9864
9965// generate a KTree class for a given k, for k=2, k=3 we try to put inline functions (defined above) for perf
10066// todo bench if it's really faster
10167export const ktree = k => {
10268
10369 const KEYS = cart ( ...Array . from ( { length : k } , ( ) => [ 0 , 1 ] ) ) . map ( a => a . join ( '' ) ) ; // node's children keys
70+ const NS = cart ( ...Array . from ( { length : k } , ( ) => [ - 1 , 0 , 1 ] ) ) ; // used for getNeighbors
10471
105- let buildTree = buildTree2 ;
106- let getNeighbors = neighbors2 ;
107- if ( k === 3 ) {
72+ let buildTree ;
73+ let getNeighbors ;
74+ if ( k === 2 ) {
75+ buildTree = buildTree2
76+ getNeighbors = ( [ x , y ] , N ) => {
77+ const nodes = [ ] ;
78+ for ( let i = 0 ; i < NS . length ; i ++ ) {
79+ const X = x + NS [ i ] [ 0 ] ,
80+ Y = y + NS [ i ] [ 1 ] ;
81+ if ( X >= 0 && X < N && Y >= 0 && Y < N ) {
82+ nodes . push ( [ X , Y ] ) ;
83+ }
84+ }
85+ return nodes ;
86+ } ;
87+ }
88+ else if ( k === 3 ) {
10889 buildTree = buildTree3 ;
109- getNeighbors = neighbors3 ;
90+ getNeighbors = ( [ x , y , z ] , N ) => {
91+ const nodes = [ ] ;
92+ for ( let i = 0 ; i < NS . length ; i ++ ) {
93+ const X = x + NS [ i ] [ 0 ] ,
94+ Y = y + NS [ i ] [ 1 ] ,
95+ Z = z + NS [ i ] [ 2 ] ;
96+ if ( X >= 0 && X < N && Y >= 0 && Y < N && Z >= 0 && Z < N ) {
97+ nodes . push ( [ X , Y , Z ] ) ;
98+ }
99+ }
100+ return nodes ;
101+ } ;
110102 }
111- if ( k > 3 ) {
103+ else {
112104 buildTree = ( depth , n = 0 ) =>
113105 n >= depth
114106 ? { n, items : [ ] }
@@ -117,18 +109,12 @@ export const ktree = k => {
117109 items : [ ] ,
118110 ...keys . reduce ( ( o , key ) => ( { ...o , [ key ] : buildTree ( depth , n + 1 ) } ) , { } )
119111 } ;
120- const NSk = cart ( ...Array . from ( { length : k } , ( ) => [ - 1 , 0 , 1 ] ) ) ;
121- getNeighbors = bins => {
122- const decs = bins . map ( b => parseInt ( b , 2 ) )
112+ getNeighbors = ( decs , N ) => {
123113 const nodes = [ ] ;
124- const n = decs [ 0 ] . length , N = 2 ** n ;
125- for ( let i = 0 ; i < NSk . length ; i ++ ) {
126- const X = xDec + NS3 [ i ] [ 0 ] ,
127- Y = yDec + NS3 [ i ] [ 1 ] ,
128- Z = zDec + NS3 [ i ] [ 2 ] ;
129- const coords = decs . map ( ( dec , j ) => dec + NSk [ i ] [ j ] ) ;
114+ for ( let i = 0 ; i < NS . length ; i ++ ) {
115+ const coords = decs . map ( ( dec , j ) => dec + NS [ i ] [ j ] ) ;
130116 if ( coords . every ( coord => coord >= 0 && coord < N ) ) {
131- nodes . push ( coords . map ( coord => coord . toString ( 2 ) . padStart ( n , 0 ) ) ) ;
117+ nodes . push ( coords ) ;
132118 }
133119 }
134120 return nodes ;
@@ -139,6 +125,7 @@ export const ktree = k => {
139125 const dist = k === 2 ? dist2 : k === 3 ? dist3 : distk ;
140126
141127 const getCoord = k === 2 ? getCoord2 : k === 3 ? getCoord3 : getCoordk ;
128+ const getCoordV2 = ( coords , i ) => coords . map ( c => c & ( 1 << i ) ? '1' : '0' ) . reduce ( ( a , b ) => a + b ) ;
142129
143130 return class KTree {
144131 constructor ( items = [ ] , { length = 8 , depth = 4 , key = 'coords' , transform = x => x } = { } ) {
@@ -149,7 +136,7 @@ export const ktree = k => {
149136 this . root = buildTree ( depth ) ;
150137 this . add ( items ) ;
151138 }
152- coordsToBin ( coords ) {
139+ coordsToBin ( coords ) { // todo rename, bin are also coords
153140 return coords . map ( x => x . toString ( 2 ) . padStart ( this . len , 0 ) ) ;
154141 }
155142 add ( items = [ ] ) {
@@ -187,27 +174,37 @@ export const ktree = k => {
187174 }
188175 closest ( value ) {
189176 const coords = this . transform ( value ) ;
190- const bins = this . coordsToBin ( coords ) ;
191- const node = this . getNodeFromCoords ( bins ) ;
177+ const node = this . getNodeFromCoords ( this . coordsToBin ( coords ) ) ; // todo improve
192178 for ( let i = node . n ; i > 0 ; i -- ) {
193- const parentBins = bins . map ( s => s . slice ( 0 , i ) ) ;
194- const ns = getNeighbors ( parentBins , getCoord ( bins , i ) ) ;
195- const items = ns
196- . map ( n => this . getNodeFromCoords ( n ) . items )
179+ const res = this . len - i ;
180+ const cs = getNeighbors ( coords . map ( c => c >> res ) , 1 << i ) ;
181+ const items = cs
182+ . map ( c => this . getNodeFromCoordsV2 ( c , res ) . items ) // todo use flatMap
197183 . reduce ( ( cs , c ) => cs . concat ( c ) ) ;
198184 if ( items . length ) {
199- return this . closestIn ( coords , items ) ;
185+ const item = this . closestIn ( coords , items ) ;
186+ // here we must check if the minimal distance from the target to the neighbors square is more than d
187+ // if yes return this value
188+ const minDFromNeighbors = minDist ( coords , cs [ 0 ] , cs [ cs . length - 1 ] , res ) ;
189+ if ( item . d <= minDFromNeighbors ) return item ;
200190 }
201191 }
202192 // search in all
203193 const items = KEYS
204- . map ( k => this . root [ k ] . items )
194+ . map ( k => this . root [ k ] . items ) // todo use flatMap
205195 . reduce ( ( cs , c ) => cs . concat ( c ) ) ;
206196 if ( items . length ) {
207197 return this . closestIn ( coords , items ) ;
208198 }
209199 // console.log(`Couldn't find any item`);
210200 }
201+ getNodeFromCoordsV2 ( coords , resolution = 0 ) {
202+ let node = this . root ;
203+ for ( let i = this . len - 1 - resolution ; i >= 0 && node [ KEYS [ 0 ] ] ; i -- ) {
204+ node = node [ getCoordV2 ( coords , i ) ] ;
205+ }
206+ return node ;
207+ }
211208 getNodeFromCoords ( coords ) {
212209 let node = this . root ;
213210 for ( let i = 0 ; i < coords [ 0 ] . length && node [ KEYS [ 0 ] ] ; i ++ ) {
@@ -225,7 +222,7 @@ export const ktree = k => {
225222 current = item ;
226223 }
227224 } ) ;
228- return { ...current , d : minD ** .5 } ;
225+ return { ...current , d : minD ** .5 } ; // todo bench vs returning squared distance
229226 }
230227 toJSON ( indent = '' ) {
231228 return JSON . stringify ( prettify ( this . root , this . key ) , null , indent ) ;
0 commit comments