From 34d7277eecf4dcaf3af208a0b39f69c09595387d Mon Sep 17 00:00:00 2001 From: muendlein <71090451+muendlein@users.noreply.github.com> Date: Sun, 7 Dec 2025 23:56:17 +0100 Subject: [PATCH] Switch to non recursive quicksort (#70) * added test * add Bentley-McIlroy 3-way partitioning * switch to non recursive implementation * fixed stack handling * rename function * escaped ts errors + enhanced performance * remove escaped error * additional performance enhancement --- index.js | 68 ++++++++++++++++++++++++++++++++++++++++++-------------- test.js | 31 ++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 17 deletions(-) diff --git a/index.js b/index.js index d4cd431..099c464 100644 --- a/index.js +++ b/index.js @@ -349,12 +349,58 @@ 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; + const stack = []; + let stackPointer = 0; + stack.push(left); + stackPointer++; + stack.push(right); + stackPointer++; + + while (stackPointer > 0) { + // @ts-expect-error + const r = stack.pop(); + stackPointer--; + const l = stack.pop(); + stackPointer--; + + // @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) { + do i++; while (values[i] < pivot); + do j--; while (values[j] > pivot); + if (i >= j) break; + swap(values, boxes, indices, i, j); + } + + stack.push(l, j, j + 1, r); + stackPointer += 4; + } +} + +/** + * Determine pivot value. + * @param {Uint32Array} values + * @param {number} l + * @param {number} r + */ +function getPivot(values, l, r) { // apply median of three method - const start = values[left]; - const mid = values[(left + right) >> 1]; - const end = values[right]; + const start = values[l]; + const mid = values[(l + r) >> 1]; + const end = values[r]; let pivot = end; @@ -366,19 +412,7 @@ function sort(values, boxes, indices, left, right, nodeSize) { } else if (x === mid) { pivot = Math.max(start, end); } - - let i = left - 1; - let j = right + 1; - - while (true) { - do i++; while (values[i] < pivot); - do j--; while (values[j] > pivot); - if (i >= j) break; - swap(values, boxes, indices, i, j); - } - - sort(values, boxes, indices, left, j, nodeSize); - sort(values, boxes, indices, j + 1, right, nodeSize); + return pivot; } /** 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; }