From b968fb2ea2d3436b06baf6204323914efd26fe53 Mon Sep 17 00:00:00 2001 From: muendlein Date: Wed, 3 Dec 2025 22:36:27 +0100 Subject: [PATCH 1/8] added test --- test.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test.js b/test.js index fc3dca0..00b9844 100644 --- a/test.js +++ b/test.js @@ -250,4 +250,35 @@ test('quicksort should work with an inbalanced dataset', () => { }); }); +test('quicksort should work with duplicates', () => { + const n = 55000 + 5500 + 7700; + const index = new Flatbush(n); + + let x = 0; + + for (let p = 0; p < 55000; p++) { + index.add(x, 3.0, x, 3.0); + x++; + } + + for (let p = 0; p < 5500; p++) { + index.add(x, 4.0, x, 4.0); + x++; + } + + for (let p = 0; p < 7700; p++) { + index.add(x, 5.0, x, 5.0); + x++; + } + + index.finish(); + + assert.doesNotThrow(() => { + index.search(0.5, -1, 6.5, 1); + }); +}); + + + + function compare(a, b) { return a - b; } From 7493d6bde5583fa745c615690643ef37a34233c4 Mon Sep 17 00:00:00 2001 From: muendlein Date: Wed, 3 Dec 2025 22:41:47 +0100 Subject: [PATCH 2/8] add Bentley-McIlroy 3-way partitioning --- index.js | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index d4cd431..f3a3df0 100644 --- a/index.js +++ b/index.js @@ -370,15 +370,38 @@ function sort(values, boxes, indices, left, right, nodeSize) { let i = left - 1; let j = right + 1; + let p = left; + let q = right; + while (true) { do i++; while (values[i] < pivot); do j--; while (values[j] > pivot); if (i >= j) break; swap(values, boxes, indices, i, j); + // swap elements equal to pivot to left and right edge + if (values[i] === pivot) { + swap(values, boxes, indices, p, i); + p++; + } + if (pivot === values[j]) { + swap(values, boxes, indices, j, q); + q--; + } + } + + i = j + 1; + + // swap elements equal to pivot to the middle + for (let k = left; k < p; k++, j--) { + swap(values, boxes, indices, k, j); + } + + for (let k = right; k > q; k--, i++) { + swap(values, boxes, indices, i, k); } sort(values, boxes, indices, left, j, nodeSize); - sort(values, boxes, indices, j + 1, right, nodeSize); + sort(values, boxes, indices, i, right, nodeSize); } /** From ed5515b59df67031ad2aba73471cad276ccf81a6 Mon Sep 17 00:00:00 2001 From: muendlein Date: Fri, 5 Dec 2025 21:32:38 +0100 Subject: [PATCH 3/8] switch to non recursive implementation --- index.js | 90 ++++++++++++++++++++++++++------------------------------ 1 file changed, 42 insertions(+), 48 deletions(-) diff --git a/index.js b/index.js index f3a3df0..5410c8f 100644 --- a/index.js +++ b/index.js @@ -349,59 +349,53 @@ function upperBound(value, arr) { * @param {number} nodeSize */ function sort(values, boxes, indices, left, right, nodeSize) { - if (Math.floor(left / nodeSize) >= Math.floor(right / nodeSize)) return; - - // apply median of three method - const start = values[left]; - const mid = values[(left + right) >> 1]; - const end = values[right]; - - let pivot = end; - - const x = Math.max(start, mid); - if (end > x) { - pivot = x; - } else if (x === start) { - pivot = Math.max(mid, end); - } else if (x === mid) { - pivot = Math.max(start, end); - } - - let i = left - 1; - let j = right + 1; - - let p = left; - let q = right; - - while (true) { - do i++; while (values[i] < pivot); - do j--; while (values[j] > pivot); - if (i >= j) break; - swap(values, boxes, indices, i, j); - // swap elements equal to pivot to left and right edge - if (values[i] === pivot) { - swap(values, boxes, indices, p, i); - p++; + const stack = []; + let stackPointer = 0; + + stack.push(left); + stackPointer++; + stack.push(right); + stackPointer++; + + while (stackPointer > 0) { + const l = stack.pop(); + stackPointer--; + const r = stack.pop(); + stackPointer--; + + if (Math.floor(l / nodeSize) >= Math.floor(r / nodeSize)) continue; + + // apply median of three method + const start = values[l]; + const mid = values[(l + r) >> 1]; + const end = values[r]; + + let pivot = end; + + const x = Math.max(start, mid); + if (end > x) { + pivot = x; + } else if (x === start) { + pivot = Math.max(mid, end); + } else if (x === mid) { + pivot = Math.max(start, end); } - if (pivot === values[j]) { - swap(values, boxes, indices, j, q); - q--; - } - } - i = j + 1; + let i = l - 1; + let j = r + 1; - // swap elements equal to pivot to the middle - for (let k = left; k < p; k++, j--) { - swap(values, boxes, indices, k, j); - } + while (true) { + do i++; while (values[i] < pivot); + do j--; while (values[j] > pivot); + if (i >= j) break; + swap(values, boxes, indices, i, j); + } - for (let k = right; k > q; k--, i++) { - swap(values, boxes, indices, i, k); + stack.push(j); + stackPointer++; + stack.push(j + 1); + stackPointer++; } - - sort(values, boxes, indices, left, j, nodeSize); - sort(values, boxes, indices, i, right, nodeSize); } /** From fe1d02004e692d0a1f4370996412dc7459cb549d Mon Sep 17 00:00:00 2001 From: muendlein Date: Fri, 5 Dec 2025 22:10:22 +0100 Subject: [PATCH 4/8] fixed stack handling --- index.js | 49 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/index.js b/index.js index 5410c8f..890f414 100644 --- a/index.js +++ b/index.js @@ -358,28 +358,14 @@ function sort(values, boxes, indices, left, right, nodeSize) { stackPointer++; while (stackPointer > 0) { - const l = stack.pop(); - stackPointer--; const r = stack.pop(); stackPointer--; + const l = stack.pop(); + stackPointer--; if (Math.floor(l / nodeSize) >= Math.floor(r / nodeSize)) continue; - // apply median of three method - const start = values[l]; - const mid = values[(l + r) >> 1]; - const end = values[r]; - - let pivot = end; - - const x = Math.max(start, mid); - if (end > x) { - pivot = x; - } else if (x === start) { - pivot = Math.max(mid, end); - } else if (x === mid) { - pivot = Math.max(start, end); - } + const pivot = partition(values, l, r); let i = l - 1; let j = r + 1; @@ -391,11 +377,40 @@ function sort(values, boxes, indices, left, right, nodeSize) { swap(values, boxes, indices, i, j); } + stack.push(l); + stackPointer++; stack.push(j); stackPointer++; stack.push(j + 1); stackPointer++; + stack.push(r); + stackPointer++; + } +} + +/** + * Partition array. + * @param {Uint32Array} values + * @param {number} l + * @param {number} r + */ +function partition(values, l, r) { + // apply median of three method + const start = values[l]; + const mid = values[(l + r) >> 1]; + const end = values[r]; + + let pivot = end; + + const x = Math.max(start, mid); + if (end > x) { + pivot = x; + } else if (x === start) { + pivot = Math.max(mid, end); + } else if (x === mid) { + pivot = Math.max(start, end); } + return pivot; } /** From d24a3e7ecb8ddc57092f495e3ede72676fbedd38 Mon Sep 17 00:00:00 2001 From: muendlein Date: Fri, 5 Dec 2025 22:40:09 +0100 Subject: [PATCH 5/8] rename function --- index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/index.js b/index.js index 890f414..7034660 100644 --- a/index.js +++ b/index.js @@ -365,7 +365,7 @@ function sort(values, boxes, indices, left, right, nodeSize) { if (Math.floor(l / nodeSize) >= Math.floor(r / nodeSize)) continue; - const pivot = partition(values, l, r); + const pivot = getPivot(values, l, r); let i = l - 1; let j = r + 1; @@ -389,12 +389,12 @@ function sort(values, boxes, indices, left, right, nodeSize) { } /** - * Partition array. + * Determine pivot value. * @param {Uint32Array} values * @param {number} l * @param {number} r */ -function partition(values, l, r) { +function getPivot(values, l, r) { // apply median of three method const start = values[l]; const mid = values[(l + r) >> 1]; From e398ae86f87a4ef793f8de895313674c1017ef80 Mon Sep 17 00:00:00 2001 From: muendlein Date: Sun, 7 Dec 2025 20:43:24 +0100 Subject: [PATCH 6/8] escaped ts errors + enhanced performance --- index.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 7034660..44dd06d 100644 --- a/index.js +++ b/index.js @@ -358,16 +358,25 @@ function sort(values, boxes, indices, left, right, nodeSize) { stackPointer++; while (stackPointer > 0) { + // @ts-expect-error const r = stack.pop(); stackPointer--; + // @ts-expect-error const l = stack.pop(); stackPointer--; - if (Math.floor(l / nodeSize) >= Math.floor(r / nodeSize)) continue; + // @ts-expect-error + if ((r - l) <= nodeSize) { + // @ts-expect-error + if (Math.floor(l / nodeSize) >= Math.floor(r / nodeSize)) continue; + } + // @ts-expect-error const pivot = getPivot(values, l, r); + // @ts-expect-error let i = l - 1; + // @ts-expect-error let j = r + 1; while (true) { From 925f7d82f041bd86e00b44396b0f211174020255 Mon Sep 17 00:00:00 2001 From: muendlein Date: Sun, 7 Dec 2025 20:44:47 +0100 Subject: [PATCH 7/8] remove escaped error --- index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/index.js b/index.js index 44dd06d..28e8223 100644 --- a/index.js +++ b/index.js @@ -361,7 +361,6 @@ function sort(values, boxes, indices, left, right, nodeSize) { // @ts-expect-error const r = stack.pop(); stackPointer--; - // @ts-expect-error const l = stack.pop(); stackPointer--; From 123e010267005a3e99765577c4e2d723c0c82edd Mon Sep 17 00:00:00 2001 From: muendlein Date: Sun, 7 Dec 2025 20:49:13 +0100 Subject: [PATCH 8/8] additional performance enhancement --- index.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/index.js b/index.js index 28e8223..099c464 100644 --- a/index.js +++ b/index.js @@ -385,14 +385,8 @@ function sort(values, boxes, indices, left, right, nodeSize) { swap(values, boxes, indices, i, j); } - stack.push(l); - stackPointer++; - stack.push(j); - stackPointer++; - stack.push(j + 1); - stackPointer++; - stack.push(r); - stackPointer++; + stack.push(l, j, j + 1, r); + stackPointer += 4; } }