From 9f7e1f1cb04e2ab93799ab33c9ab5bebbd09eb88 Mon Sep 17 00:00:00 2001 From: Bruce Martin Date: Wed, 7 Nov 2018 13:35:00 -0800 Subject: [PATCH] use quicksort instead of default system sort (#413) --- .../util/typedCrossfilter/sort.test.js | 56 ++++++++++ client/src/util/typedCrossfilter/sort.js | 100 ++++++++++++++++++ client/src/util/typedCrossfilter/util.js | 4 +- 3 files changed, 159 insertions(+), 1 deletion(-) create mode 100644 client/__tests__/util/typedCrossfilter/sort.test.js create mode 100644 client/src/util/typedCrossfilter/sort.js diff --git a/client/__tests__/util/typedCrossfilter/sort.test.js b/client/__tests__/util/typedCrossfilter/sort.test.js new file mode 100644 index 00000000..71ce948e --- /dev/null +++ b/client/__tests__/util/typedCrossfilter/sort.test.js @@ -0,0 +1,56 @@ +import { sort, sortIndex } from "../../../src/util/typedCrossfilter/sort"; + +function fillRange(arr, start = 0) { + const larr = arr; + for (let i = 0, len = larr.length; i < len; i += 1) { + larr[i] = i + start; + } + return larr; +} + +function fillRand(arr) { + for (let i = 0, len = arr.length; i < len; i += 1) { + arr[i] = Math.random(); + } + return arr; +} + +describe("sort", () => { + [Array, Float32Array, Uint32Array, Int32Array, Float64Array].map(Type => + test(Type.name, () => { + expect(sort(Type.from([6, 5, 4, 3, 2, 1, 0]))).toMatchObject( + Type.from([0, 1, 2, 3, 4, 5, 6]) + ); + expect(sort(Type.from([6, 5, 4, 3, 2, 1]))).toMatchObject( + Type.from([1, 2, 3, 4, 5, 6]) + ); + + const source = fillRand(new Type(1000)); + expect(sort(Type.from(source))).toMatchObject(Type.from(source).sort()); + }) + ); +}); + +describe("sortIndex", () => { + [Array, Float32Array, Uint32Array, Int32Array, Float64Array].map(Type => + test(Type.name, () => { + const source1 = Type.from([6, 5, 4, 3, 2, 1, 0]); + const index1 = fillRange(new Uint32Array(source1.length)); + expect(sortIndex(index1, source1)).toMatchObject( + index1.sort((a, b) => source1[a] - source1[b]) + ); + + const source2 = Type.from([6, 5, 4, 3, 2, 1]); + const index2 = fillRange(new Uint32Array(source2.length)); + expect(sortIndex(index2, source2)).toMatchObject( + index2.sort((a, b) => source1[a] - source1[b]) + ); + + const source3 = fillRand(new Type(1000)); + const index3 = fillRange(new Uint32Array(source3.length)); + expect(sortIndex(index3, source3)).toMatchObject( + index3.sort((a, b) => source1[a] - source1[b]) + ); + }) + ); +}); diff --git a/client/src/util/typedCrossfilter/sort.js b/client/src/util/typedCrossfilter/sort.js new file mode 100644 index 00000000..08fb3ec4 --- /dev/null +++ b/client/src/util/typedCrossfilter/sort.js @@ -0,0 +1,100 @@ +const SmallArray = 32; + +function insertionsort(a, lo, hi) { + for (let i = lo + 1; i < hi + 1; i += 1) { + const x = a[i]; + let j; + for (j = i; j > lo && a[j - 1] > x; j -= 1) { + a[j] = a[j - 1]; + } + a[j] = x; + } + return a; +} + +function insertionsortIndirect(a, s, lo, hi) { + for (let i = lo + 1; i < hi + 1; i += 1) { + const x = a[i]; + const t = s[x]; + let j; + for (j = i; j > lo && s[a[j - 1]] > t; j -= 1) { + a[j] = a[j - 1]; + } + a[j] = x; + } + return a; +} + +function quicksort(a, lo, hi) { + if (hi - lo < SmallArray) { + return insertionsort(a, lo, hi); + } + if (lo < hi) { + // partition + const mid = Math.floor((lo + hi) / 2); + const p = a[mid]; + let i = lo - 1; + let j = hi + 1; + while (i < j) { + do { + i += 1; + } while (a[i] < p); + do { + j -= 1; + } while (a[j] > p); + if (i < j) { + const tmp = a[i]; + a[i] = a[j]; + a[j] = tmp; + } + } + // sort + quicksort(a, lo, j); + quicksort(a, j + 1, hi); + } + return a; +} + +function quicksortIndirect(a, s, lo, hi) { + if (hi - lo < SmallArray) { + return insertionsortIndirect(a, s, lo, hi); + } + if (lo < hi) { + // partition + const mid = Math.floor((lo + hi) / 2); + const p = a[mid]; + const t = s[p]; + let i = lo - 1; + let j = hi + 1; + while (i < j) { + do { + i += 1; + } while (s[a[i]] < t); + do { + j -= 1; + } while (s[a[j]] > t); + if (i < j) { + const tmp = a[i]; + a[i] = a[j]; + a[j] = tmp; + } + } + // sort + quicksortIndirect(a, s, lo, j); + quicksortIndirect(a, s, j + 1, hi); + } + return a; +} + +// Convenience wrappers +export function sort(arr, comparator = undefined) { + if (comparator !== undefined) { + // XXX for now + return arr.sort(arr, comparator); + } + return quicksort(arr, 0, arr.length - 1); +} + +export function sortIndex(index, source) { + return quicksortIndirect(index, source, 0, index.length - 1); +} diff --git a/client/src/util/typedCrossfilter/util.js b/client/src/util/typedCrossfilter/util.js index 108f1923..1aacafc0 100644 --- a/client/src/util/typedCrossfilter/util.js +++ b/client/src/util/typedCrossfilter/util.js @@ -1,6 +1,8 @@ // jshint esversion: 6 /* eslint no-bitwise: "off" */ +import { sortIndex } from "./sort"; + /* Utility functions, private to this module. */ @@ -31,7 +33,7 @@ export function sliceByIndex(src, index) { export function makeSortIndex(src) { const index = fillRange(new Uint32Array(src.length)); - index.sort((a, b) => src[a] - src[b]); + sortIndex(index, src); return index; }