Skip to content
Snippets Groups Projects
Commit a91b5123 authored by David Sehnal's avatar David Sehnal
Browse files

collections

parent 6a80405a
No related branches found
No related tags found
No related merge requests found
import * as B from 'benchmark' import * as B from 'benchmark'
import * as Sort from '../structure/collections/sort' import * as Sort from '../structure/collections/sort'
function shuffle(a: number[]) {
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const t = a[i];
a[i] = a[j];
a[j] = t;
}
return a;
}
function createTestData(n: number) { function createTestData(n: number) {
const data = new Int32Array(n); //new Array(n); const data = new Int32Array(n); //new Array(n);
for (let i = 0; i < n; i++) { for (let i = 0; i < n; i++) {
data[i] = (n * Math.random()) | 0; data[i] = i;
//data[i] = (n * Math.random()) | 0;
} }
shuffle(data as any);
return data; return data;
} }
...@@ -24,21 +36,51 @@ export function checkSorted(arr: ArrayLike<number>) { ...@@ -24,21 +36,51 @@ export function checkSorted(arr: ArrayLike<number>) {
return true; return true;
} }
const _data = createTestData(10000);
const data = () => copyArray(_data);
const suite = new B.Suite(); export function runTest(size: number) {
const _data = createTestData(size);
const dataCopies: number[][] = [];
let dataOffset = 0;
function prepareData() {
dataOffset = 0;
for (let i = 0; i < 100; i++) {
dataCopies[i] = copyArray(_data);
}
}
function getData() {
if (dataOffset < dataCopies.length) return dataCopies[dataOffset++];
return copyArray(_data);
}
prepareData();
const suite = new B.Suite();
function le(x: number, y: number) { return x - y; } function le(x: number, y: number) { return x - y; }
function name(n: string) { return `${n} (${size} elems)` }
// TODO: the data copying skewes the benchmark -- write a simple benchmark util that allows for a preparation step.
suite
.add(name('native'), () => Array.prototype.sort.call(getData(), le))
.add(name('qsort'), () => Sort.sortArray(getData()))
//.add(name('qsort'), () => Sort.sort(getData(), 0, _data.length, Sort.arrayLess, Sort.arraySwap))
.add(name('native sorted'), () => Array.prototype.sort.call(_data, le))
.add(name('qsort sorted'), () => Sort.sortArray(_data))
//.add(name('qsort sorted'), () => Sort.sort(_data, 0, _data.length, Sort.arrayLess, Sort.arraySwap))
.on('cycle', (e: any) => {
prepareData();
console.log(String(e.target));
})
.run();
console.log('---------------------');
}
suite //runTest(10);
.add('native', () => Array.prototype.sort.call(data(), le)) //runTest(100);
.add('qsort (array asc)', () => Sort.sortArray(data())) //runTest(1000);
.add('qsort (generic)', () => Sort.sort(data(), _data.length, Sort.arrayLess, Sort.arraySwap)) runTest(10000);
.add('native sorted', () => Array.prototype.sort.call(_data, le)) //runTest(100000);
.add('qsort sorted (array asc)', () => Sort.sortArray(_data)) \ No newline at end of file
.add('qsort sorted (generic)', () => Sort.sort(_data, _data.length, Sort.arrayLess, Sort.arraySwap))
.on('cycle', (e: any) => {
console.log(String(e.target));
})
.run();
\ No newline at end of file
// TODO: fixed length doubly linked list used for graph traversal
\ No newline at end of file
...@@ -10,6 +10,7 @@ interface RangeSet { ...@@ -10,6 +10,7 @@ interface RangeSet {
readonly size: number, readonly size: number,
has(x: number): boolean, has(x: number): boolean,
indexOf(x: number): number, indexOf(x: number): number,
elementAt(i: number): number,
elements(): Iterator<number> elements(): Iterator<number>
} }
...@@ -51,6 +52,7 @@ namespace RangeSet { ...@@ -51,6 +52,7 @@ namespace RangeSet {
for (let i = 0; i < this.size; i++) ret[i] = i + this.min; for (let i = 0; i < this.size; i++) ret[i] = i + this.min;
return ret; return ret;
} }
elementAt(i: number) { return this.min + i; }
elements() { return Iterator.Range(this.min, this.max); } elements() { return Iterator.Range(this.min, this.max); }
constructor(public min: number, public max: number) { constructor(public min: number, public max: number) {
...@@ -65,6 +67,7 @@ namespace RangeSet { ...@@ -65,6 +67,7 @@ namespace RangeSet {
has(x: number) { return x >= this.min && x <= this.max && binarySearch(this.values, x) >= 0; } has(x: number) { return x >= this.min && x <= this.max && binarySearch(this.values, x) >= 0; }
indexOf(x: number) { return x >= this.min && x <= this.max ? binarySearch(this.values, x) : -1; } indexOf(x: number) { return x >= this.min && x <= this.max ? binarySearch(this.values, x) : -1; }
toArray() { return this.values; } toArray() { return this.values; }
elementAt(i: number) { return this.values[i]; }
elements() { return Iterator.Array(this.values); } elements() { return Iterator.Array(this.values); }
constructor(public values: ArrayLike<number>) { constructor(public values: ArrayLike<number>) {
......
...@@ -7,12 +7,7 @@ ...@@ -7,12 +7,7 @@
export type Comparer<T = any> = (data: T, i: number, j: number) => number export type Comparer<T = any> = (data: T, i: number, j: number) => number
export type Swapper<T = any> = (data: T, i: number, j: number) => void export type Swapper<T = any> = (data: T, i: number, j: number) => void
type Ctx = { type Ctx = { cmp: Comparer, swap: Swapper, parts: number[], data: any }
cmp: Comparer,
swap: Swapper,
parts: number[],
data: any
}
export function arrayLess(arr: ArrayLike<number>, i: number, j: number) { export function arrayLess(arr: ArrayLike<number>, i: number, j: number) {
return arr[i] - arr[j]; return arr[i] - arr[j];
...@@ -30,7 +25,7 @@ function medianPivotIndex(data: any, cmp: Comparer, l: number, r: number) { ...@@ -30,7 +25,7 @@ function medianPivotIndex(data: any, cmp: Comparer, l: number, r: number) {
else return cmp(data, r, m) > 0 ? cmp(data, m, l) > 0 ? m : l : r; else return cmp(data, r, m) > 0 ? cmp(data, m, l) > 0 ? m : l : r;
} }
function partitionGeneric(ctx: Ctx, l: number, r: number) { function partition(ctx: Ctx, l: number, r: number) {
const { cmp, swap, data, parts } = ctx; const { cmp, swap, data, parts } = ctx;
let equals = l + 1, tail = r; let equals = l + 1, tail = r;
...@@ -57,33 +52,6 @@ function partitionGeneric(ctx: Ctx, l: number, r: number) { ...@@ -57,33 +52,6 @@ function partitionGeneric(ctx: Ctx, l: number, r: number) {
parts[1] = tail; parts[1] = tail;
} }
function partitionArrayAsc(data: number[], parts: number[], l: number, r: number) {
let equals = l + 1, tail = r;
// move the median to the 1st spot
arraySwap(data, l, medianPivotIndex(data, arrayLess, l, r));
const pivot = data[l];
while (data[tail] > pivot) { --tail; }
for (let i = l + 1; i <= tail; i++) {
const v = data[i];
if (v > pivot) {
arraySwap(data, i, tail);
--tail;
while (data[tail] > pivot) { --tail; }
i--;
} else if (v === pivot) {
arraySwap(data, i, equals);
++equals;
}
}
// move all medians to the correct spots
for (let i = l; i < equals; i++) { arraySwap(data, i, l + tail - i); }
parts[0] = tail - equals + l + 1;
parts[1] = tail;
}
function insertionSort({ data, cmp, swap }: Ctx, start: number, end: number) { function insertionSort({ data, cmp, swap }: Ctx, start: number, end: number) {
for (let i = start + 1; i <= end; i++) { for (let i = start + 1; i <= end; i++) {
let j = i - 1; let j = i - 1;
...@@ -102,7 +70,7 @@ function quickSort(ctx: Ctx, low: number, high: number) { ...@@ -102,7 +70,7 @@ function quickSort(ctx: Ctx, low: number, high: number) {
return; return;
} }
partitionGeneric(ctx, low, high); partition(ctx, low, high);
const li = parts[0], ri = parts[1]; const li = parts[0], ri = parts[1];
if (li - low < high - ri) { if (li - low < high - ri) {
...@@ -115,46 +83,17 @@ function quickSort(ctx: Ctx, low: number, high: number) { ...@@ -115,46 +83,17 @@ function quickSort(ctx: Ctx, low: number, high: number) {
} }
} }
function insertionSortArrayAsc(data: number[], start: number, end: number) { export function sortArray(data: ArrayLike<number>, cmp: Comparer<ArrayLike<number>> = arrayLess): ArrayLike<number> {
for (let i = start + 1; i <= end; i++) { return sortArrayRange(data, 0, data.length, cmp);
const key = data[i];
let j = i - 1;
while (j >= start && data[j] > key) {
data[j + 1] = data[j];
j = j - 1;
}
data[j + 1] = key;
}
}
function quickSortArrayAsc(data: number[], parts: number[], low: number, high: number) {
while (low < high) {
if (high - low < 16) {
insertionSortArrayAsc(data, low, high);
return;
}
partitionArrayAsc(data, parts, low, high);
const li = parts[0], ri = parts[1];
if (li - low < high - ri) {
quickSortArrayAsc(data, parts, low, li - 1);
low = ri + 1;
} else {
quickSortArrayAsc(data, parts, ri + 1, high);
high = li - 1;
}
}
} }
export function sortArray(data: ArrayLike<number>, cmp: Comparer<ArrayLike<number>> = arrayLess): ArrayLike<number> { export function sortArrayRange(data: ArrayLike<number>, start: number, end: number, cmp: Comparer<ArrayLike<number>> = arrayLess): ArrayLike<number> {
if (cmp === arrayLess) quickSortArrayAsc(data as any, [0, 0], 0, data.length - 1); quickSort({ data, cmp, swap: arraySwap, parts: [0, 0] }, start, end - 1);
else quickSort({ data, cmp, swap: arraySwap, parts: [0, 0] }, 0, data.length - 1);
return data; return data;
} }
export function sort<T>(data: T, count: number, cmp: Comparer<T>, swap: Swapper<T>): T { export function sort<T>(data: T, start: number, end: number, cmp: Comparer<T>, swap: Swapper<T>): T {
const ctx: Ctx = { data, cmp, swap, parts: [0, 0] }; const ctx: Ctx = { data, cmp, swap, parts: [0, 0] };
quickSort(ctx, 0, count - 1); quickSort(ctx, start, end - 1);
return data; return data;
} }
\ No newline at end of file
...@@ -82,10 +82,10 @@ describe('qsort-array generic', () => { ...@@ -82,10 +82,10 @@ describe('qsort-array generic', () => {
// [ 3, 1, 6, 4, 4, 6, 4, 2, 6, 1, 2, 3 ]; // [ 3, 1, 6, 4, 4, 6, 4, 2, 6, 1, 2, 3 ];
if (randomize) { if (randomize) {
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
expect(Sort.sort(shuffleArray(data), data.length, Sort.arrayLess, Sort.arraySwap)).toEqual(data); expect(Sort.sort(shuffleArray(data), 0, data.length, Sort.arrayLess, Sort.arraySwap)).toEqual(data);
} }
} else { } else {
expect(Sort.sort([...data], data.length, Sort.arrayLess, Sort.arraySwap)).toEqual(data); expect(Sort.sort([...data], 0, data.length, Sort.arrayLess, Sort.arraySwap)).toEqual(data);
} }
}); });
} }
...@@ -108,10 +108,10 @@ describe('qsort-dual array', () => { ...@@ -108,10 +108,10 @@ describe('qsort-dual array', () => {
// [ 3, 1, 6, 4, 4, 6, 4, 2, 6, 1, 2, 3 ]; // [ 3, 1, 6, 4, 4, 6, 4, 2, 6, 1, 2, 3 ];
if (randomize) { if (randomize) {
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
expect(Sort.sort(shuffle(src, len, clone, swap), len, cmp, swap)).toEqual(data); expect(Sort.sort(shuffle(src, len, clone, swap), 0, len, cmp, swap)).toEqual(data);
} }
} else { } else {
expect(Sort.sort(clone(src), len, cmp, swap)).toEqual(data); expect(Sort.sort(clone(src), 0, len, cmp, swap)).toEqual(data);
} }
}); });
} }
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment