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 {
size: number;
has(x: number) { return x >= this.min && x <= this.max; }
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; }
elements() { return Iterator.Range(this.min, this.max); }
......@@ -54,6 +49,17 @@ class ArrayImpl implements Impl {
}
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 hashCode(a: OrderedSet) {
......@@ -117,17 +123,6 @@ namespace OrderedSet {
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; }
......
......@@ -9,125 +9,129 @@
* A generic chunked array builder.
*
* When adding elements, the array growns by a specified number
* of elements and no copying is done until ChunkedArray.compact
* is called.
* of elements (either linear or exponential growth) and no copying
* is done until ChunkedArray.compact is called.
*/
export interface ChunkedArray<T> {
creator: (size: number) => any;
elementSize: number;
chunkSize: number;
current: any;
currentIndex: number;
parts: any[];
elementCount: number;
interface ChunkedArray<T> {
ctor: (size: number) => any,
elementSize: number,
linearGrowth: boolean,
initialSize: number,
allocatedSize: 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> {
return x.creator && x.chunkSize;
}
export function add4<T>(array: ChunkedArray<T>, x: T, y: T, z: T, w: T) {
if (array.currentIndex >= array.chunkSize) {
array.currentIndex = 0;
array.current = array.creator(array.chunkSize);
array.parts[array.parts.length] = array.current;
}
function allocateNext(array: ChunkedArray<any>) {
let nextSize = !array.allocatedSize || array.linearGrowth
? array.initialSize * array.elementSize
: Math.max(Math.ceil(0.61 * array.allocatedSize), 1);
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;
array.current[array.currentIndex++] = y;
array.current[array.currentIndex++] = z;
array.current[array.currentIndex++] = w;
export function add4<T>(array: ChunkedArray<T>, x: T, y: T, z: T, w: T) {
if (array.currentIndex >= array.currentSize) allocateNext(array);
const c = array.currentChunk;
c[array.currentIndex++] = x;
c[array.currentIndex++] = y;
c[array.currentIndex++] = z;
c[array.currentIndex++] = w;
return array.elementCount++;
}
export function add3<T>(array: ChunkedArray<T>, x: T, y: T, z: T) {
if (array.currentIndex >= array.chunkSize) {
array.currentIndex = 0;
array.current = array.creator(array.chunkSize);
array.parts[array.parts.length] = array.current;
}
array.current[array.currentIndex++] = x;
array.current[array.currentIndex++] = y;
array.current[array.currentIndex++] = z;
if (array.currentIndex >= array.currentSize) allocateNext(array);
const c = array.currentChunk;
c[array.currentIndex++] = x;
c[array.currentIndex++] = y;
c[array.currentIndex++] = z;
return array.elementCount++;
}
export function add2<T>(array: ChunkedArray<T>, x: T, y: T) {
if (array.currentIndex >= array.chunkSize) {
array.currentIndex = 0;
array.current = array.creator(array.chunkSize);
array.parts[array.parts.length] = array.current;
}
array.current[array.currentIndex++] = x;
array.current[array.currentIndex++] = y;
if (array.currentIndex >= array.currentSize) allocateNext(array);
const c = array.currentChunk;
c[array.currentIndex++] = x;
c[array.currentIndex++] = y;
return array.elementCount++;
}
export function add<T>(array: ChunkedArray<T>, x: T) {
if (array.currentIndex >= array.chunkSize) {
array.currentIndex = 0;
array.current = array.creator(array.chunkSize);
array.parts[array.parts.length] = array.current;
}
array.current[array.currentIndex++] = x;
if (array.currentIndex >= array.currentSize) allocateNext(array);
array.currentChunk[array.currentIndex++] = x;
return array.elementCount++;
}
export function compact<T>(array: ChunkedArray<T>): T[] {
const ret = array.creator(array.elementSize * array.elementCount)
const offset = (array.parts.length - 1) * array.chunkSize
let offsetInner = 0
let part: any
export function compact<T>(array: ChunkedArray<T>): ArrayLike<T> {
const { ctor, chunks, currentIndex } = array;
if (array.parts.length > 1) {
if (array.parts[0].buffer) {
for (let i = 0; i < array.parts.length - 1; i++) {
ret.set(array.parts[i], array.chunkSize * i);
}
} else {
if (!chunks.length) return ctor(0);
if (chunks.length === 1 && currentIndex === array.allocatedSize) {
return chunks[0];
}
for (let i = 0; i < array.parts.length - 1; i++) {
offsetInner = array.chunkSize * i;
part = array.parts[i];
const ret = ctor(array.elementSize * array.elementCount);
let offset = 0;
for (let j = 0; j < array.chunkSize; j++) {
ret[offsetInner + j] = part[j];
}
}
if (ret.buffer) {
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 {
for (let i = 0; i < array.currentIndex; i++) {
ret[offset + i] = array.current[i];
for (let i = 0, _i = chunks.length - 1; i < _i; 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> {
chunkElementCount = chunkElementCount | 0;
if (chunkElementCount <= 0) chunkElementCount = 1;
const lastChunk = chunks[chunks.length - 1];
if (ret.buffer && currentIndex >= array.currentSize) {
ret.set(lastChunk, offset);
} else {
for (let j = 0, _j = lastChunk.length; j < _j; j++) ret[offset + j] = lastChunk[j];
}
let chunkSize = chunkElementCount * elementSize;
let current = creator(chunkSize)
return ret;
}
export function create<T>(ctor: (size: number) => any, elementSize: number, initialSize: number, linearGrowth: boolean): ChunkedArray<T> {
return {
ctor,
elementSize,
chunkSize,
creator,
current,
parts: [current],
linearGrowth,
initialSize,
allocatedSize: 0,
elementCount: 0,
currentSize: 0,
currentChunk: void 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