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

collections

parent a0ee98a7
No related branches found
No related tags found
No related merge requests found
import * as B from 'benchmark'
import ChunkedArray from '../utils/chunked-array'
function testNative(size: number) {
const xs = new Array(size);
for (let i = 0; i < size; i++) xs[i] = i * i;
return xs;
}
function testChunkedTyped(size: number, chunk: number, linear: boolean) {
const xs = ChunkedArray.create(s => new Int32Array(s), 1, chunk, linear);
for (let i = 0; i < size; i++) ChunkedArray.add(xs, i * i);
return ChunkedArray.compact(xs);
}
function testChunkedNative(size: number, chunk: number) {
const xs = ChunkedArray.create(s => [], 1, chunk, false);
for (let i = 0; i < size; i++) ChunkedArray.add(xs, i * i);
return ChunkedArray.compact(xs);
}
const suite = new B.Suite();
const N = 70000;
suite
.add('native', () => testNative(N))
.add('chunkedT 0.1k', () => testChunkedTyped(N, 100, false))
.add('chunkedT 4k', () => testChunkedTyped(N, 4096, false))
.add('chunkedT 4k lin', () => testChunkedTyped(N, 4096, true))
.add('chunkedT N / 2', () => testChunkedTyped(N, N / 2, false))
.add('chunkedT N', () => testChunkedTyped(N, N, false))
.add('chunkedT 2 * N', () => testChunkedTyped(N, 2 * N, false))
.add('chunkedN N', () => testChunkedNative(N, N))
.add('chunkedN 0.1k', () => testChunkedNative(N, 100))
.add('chunkedN N / 2', () => testChunkedNative(N, N / 2))
.add('chunkedN 2 * N', () => testChunkedNative(N, 2 * N))
.on('cycle', (e: any) => {
console.log(String(e.target));
})
.run();
//console.log(testChunkedTyped(10, 16));
...@@ -24,11 +24,6 @@ class RangeImpl implements Impl { ...@@ -24,11 +24,6 @@ class RangeImpl implements Impl {
size: number; size: number;
has(x: number) { return x >= this.min && x <= this.max; } has(x: number) { return x >= this.min && x <= this.max; }
indexOf(x: number) { return x >= this.min && x <= this.max ? x - this.min : -1; } indexOf(x: number) { return x >= this.min && x <= this.max ? x - this.min : -1; }
toArray() {
const ret = new Array(this.size);
for (let i = 0; i < this.size; i++) ret[i] = i + this.min;
return ret;
}
elementAt(i: number) { return this.min + i; } elementAt(i: number) { return this.min + i; }
elements() { return Iterator.Range(this.min, this.max); } elements() { return Iterator.Range(this.min, this.max); }
...@@ -54,6 +49,17 @@ class ArrayImpl implements Impl { ...@@ -54,6 +49,17 @@ class ArrayImpl implements Impl {
} }
namespace OrderedSet { namespace OrderedSet {
export function ofSingleton(value: number): OrderedSet { return new RangeImpl(value, value); }
export function ofRange(min: number, max: number): OrderedSet { return max < min ? Empty : new RangeImpl(min, max); }
/** It is the responsibility of the caller to ensure the array is sorted and contains unique values. */
export function ofSortedArray(xs: ArrayLike<number>): OrderedSet {
if (!xs.length) return Empty;
// check if the array is just a range
if (xs[xs.length - 1] - xs[0] + 1 === xs.length) return ofRange(xs[0], xs[xs.length - 1]);
return new ArrayImpl(xs);
}
export const Empty = new RangeImpl(0, -1);
export function isEmpty(a: OrderedSet) { return a.size === 0; } export function isEmpty(a: OrderedSet) { return a.size === 0; }
export function hashCode(a: OrderedSet) { export function hashCode(a: OrderedSet) {
...@@ -117,17 +123,6 @@ namespace OrderedSet { ...@@ -117,17 +123,6 @@ namespace OrderedSet {
return intersectAA((a as ArrayImpl).values, (b as ArrayImpl).values); return intersectAA((a as ArrayImpl).values, (b as ArrayImpl).values);
} }
} }
export function ofSingleton(value: number): OrderedSet { return new RangeImpl(value, value); }
export function ofRange(min: number, max: number): OrderedSet { return max < min ? Empty : new RangeImpl(min, max); }
/** It is the responsibility of the caller to ensure the array is sorted and contains unique values. */
export function ofSortedArray(xs: ArrayLike<number>): OrderedSet {
if (!xs.length) return Empty;
// check if the array is just a range
if (xs[xs.length - 1] - xs[0] + 1 === xs.length) return ofRange(xs[0], xs[xs.length - 1]);
return new ArrayImpl(xs);
}
export const Empty = new RangeImpl(0, -1);
} }
function min(a: OrderedSet) { return (a as Impl).min; } function min(a: OrderedSet) { return (a as Impl).min; }
......
...@@ -9,125 +9,129 @@ ...@@ -9,125 +9,129 @@
* A generic chunked array builder. * A generic chunked array builder.
* *
* When adding elements, the array growns by a specified number * When adding elements, the array growns by a specified number
* of elements and no copying is done until ChunkedArray.compact * of elements (either linear or exponential growth) and no copying
* is called. * is done until ChunkedArray.compact is called.
*/ */
export interface ChunkedArray<T> { interface ChunkedArray<T> {
creator: (size: number) => any; ctor: (size: number) => any,
elementSize: number; elementSize: number,
chunkSize: number;
current: any; linearGrowth: boolean,
currentIndex: number;
initialSize: number,
parts: any[]; allocatedSize: number,
elementCount: number; elementCount: number,
currentSize: number,
currentChunk: any,
currentIndex: number,
chunks: any[]
} }
export namespace ChunkedArray { // TODO: better api, write tests
namespace ChunkedArray {
export function is(x: any): x is ChunkedArray<any> { export function is(x: any): x is ChunkedArray<any> {
return x.creator && x.chunkSize; return x.creator && x.chunkSize;
} }
export function add4<T>(array: ChunkedArray<T>, x: T, y: T, z: T, w: T) { function allocateNext(array: ChunkedArray<any>) {
if (array.currentIndex >= array.chunkSize) { let nextSize = !array.allocatedSize || array.linearGrowth
array.currentIndex = 0; ? array.initialSize * array.elementSize
array.current = array.creator(array.chunkSize); : Math.max(Math.ceil(0.61 * array.allocatedSize), 1);
array.parts[array.parts.length] = array.current; if (nextSize % array.elementSize !== 0) nextSize += nextSize % array.elementSize;
} array.currentSize = nextSize;
array.currentIndex = 0;
array.currentChunk = array.ctor(nextSize);
array.allocatedSize += nextSize;
array.chunks[array.chunks.length] = array.currentChunk;
}
array.current[array.currentIndex++] = x; export function add4<T>(array: ChunkedArray<T>, x: T, y: T, z: T, w: T) {
array.current[array.currentIndex++] = y; if (array.currentIndex >= array.currentSize) allocateNext(array);
array.current[array.currentIndex++] = z; const c = array.currentChunk;
array.current[array.currentIndex++] = w; c[array.currentIndex++] = x;
c[array.currentIndex++] = y;
c[array.currentIndex++] = z;
c[array.currentIndex++] = w;
return array.elementCount++; return array.elementCount++;
} }
export function add3<T>(array: ChunkedArray<T>, x: T, y: T, z: T) { export function add3<T>(array: ChunkedArray<T>, x: T, y: T, z: T) {
if (array.currentIndex >= array.chunkSize) { if (array.currentIndex >= array.currentSize) allocateNext(array);
array.currentIndex = 0; const c = array.currentChunk;
array.current = array.creator(array.chunkSize); c[array.currentIndex++] = x;
array.parts[array.parts.length] = array.current; c[array.currentIndex++] = y;
} c[array.currentIndex++] = z;
array.current[array.currentIndex++] = x;
array.current[array.currentIndex++] = y;
array.current[array.currentIndex++] = z;
return array.elementCount++; return array.elementCount++;
} }
export function add2<T>(array: ChunkedArray<T>, x: T, y: T) { export function add2<T>(array: ChunkedArray<T>, x: T, y: T) {
if (array.currentIndex >= array.chunkSize) { if (array.currentIndex >= array.currentSize) allocateNext(array);
array.currentIndex = 0; const c = array.currentChunk;
array.current = array.creator(array.chunkSize); c[array.currentIndex++] = x;
array.parts[array.parts.length] = array.current; c[array.currentIndex++] = y;
}
array.current[array.currentIndex++] = x;
array.current[array.currentIndex++] = y;
return array.elementCount++; return array.elementCount++;
} }
export function add<T>(array: ChunkedArray<T>, x: T) { export function add<T>(array: ChunkedArray<T>, x: T) {
if (array.currentIndex >= array.chunkSize) { if (array.currentIndex >= array.currentSize) allocateNext(array);
array.currentIndex = 0; array.currentChunk[array.currentIndex++] = x;
array.current = array.creator(array.chunkSize);
array.parts[array.parts.length] = array.current;
}
array.current[array.currentIndex++] = x;
return array.elementCount++; return array.elementCount++;
} }
export function compact<T>(array: ChunkedArray<T>): T[] { export function compact<T>(array: ChunkedArray<T>): ArrayLike<T> {
const ret = array.creator(array.elementSize * array.elementCount) const { ctor, chunks, currentIndex } = array;
const offset = (array.parts.length - 1) * array.chunkSize
let offsetInner = 0
let part: any
if (array.parts.length > 1) { if (!chunks.length) return ctor(0);
if (array.parts[0].buffer) { if (chunks.length === 1 && currentIndex === array.allocatedSize) {
for (let i = 0; i < array.parts.length - 1; i++) { return chunks[0];
ret.set(array.parts[i], array.chunkSize * i); }
}
} else {
for (let i = 0; i < array.parts.length - 1; i++) { const ret = ctor(array.elementSize * array.elementCount);
offsetInner = array.chunkSize * i; let offset = 0;
part = array.parts[i];
for (let j = 0; j < array.chunkSize; j++) { if (ret.buffer) {
ret[offsetInner + j] = part[j]; for (let i = 0, _i = chunks.length - 1; i < _i; i++) {
} ret.set(chunks[i], offset);
} offset += chunks[i].length;
} }
}
if (array.current.buffer && array.currentIndex >= array.chunkSize) {
ret.set(array.current, array.chunkSize * (array.parts.length - 1));
} else { } else {
for (let i = 0; i < array.currentIndex; i++) { for (let i = 0, _i = chunks.length - 1; i < _i; i++) {
ret[offset + i] = array.current[i]; const chunk = chunks[i];
for (let j = 0, _j = chunk.length; j < _j; j++) ret[offset + j] = chunk[j];
offset += chunk.length;
} }
} }
return ret as any;
}
export function create<T>(creator: (size: number) => any, chunkElementCount: number, elementSize: number): ChunkedArray<T> { const lastChunk = chunks[chunks.length - 1];
chunkElementCount = chunkElementCount | 0; if (ret.buffer && currentIndex >= array.currentSize) {
if (chunkElementCount <= 0) chunkElementCount = 1; ret.set(lastChunk, offset);
} else {
for (let j = 0, _j = lastChunk.length; j < _j; j++) ret[offset + j] = lastChunk[j];
}
let chunkSize = chunkElementCount * elementSize; return ret;
let current = creator(chunkSize) }
export function create<T>(ctor: (size: number) => any, elementSize: number, initialSize: number, linearGrowth: boolean): ChunkedArray<T> {
return { return {
ctor,
elementSize, elementSize,
chunkSize, linearGrowth,
creator,
current, initialSize,
parts: [current], allocatedSize: 0,
elementCount: 0,
currentSize: 0,
currentChunk: void 0,
currentIndex: 0, currentIndex: 0,
elementCount: 0
} as ChunkedArray<T> chunks: []
} as ChunkedArray<T>;
} }
} }
export default ChunkedArray
\ No newline at end of file
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