structurae
Advanced tools
Comparing version 0.0.5 to 0.0.9
@@ -18,5 +18,8 @@ // Type definitions for structurae | ||
type Coordinates = [number, number] | ||
interface Coordinates { | ||
row: number; | ||
column: number; | ||
} | ||
export declare class Grid { | ||
declare class Grid { | ||
columns: number; | ||
@@ -26,2 +29,3 @@ rows: number; | ||
pad: any; | ||
lastCoordinates: Coordinates; | ||
@@ -32,3 +36,3 @@ constructor(options?: GridOptions, data?: Collection); | ||
getCoordinates(index: number): Coordinates; | ||
toArrays(withPadding: boolean): any[][]; | ||
toArrays(withPadding?: boolean): any[][]; | ||
static getOffset(columns: number): number; | ||
@@ -61,3 +65,3 @@ static fromArrays(arrays: any[][], pad: any): Grid; | ||
export class PackedInt { | ||
export class BitField { | ||
value: AnyNumber; | ||
@@ -96,20 +100,16 @@ constructor(data?: AnyNumber|number[]); | ||
export class SortedArray extends Array { | ||
unique: boolean; | ||
declare class SortedCollection { | ||
compare(a: any, b: any): CompareResult; | ||
isSorted(): boolean; | ||
isUnique(): boolean; | ||
reset(arr: Collection): this; | ||
range(start: number, end: number): SortedArray; | ||
range(start: number, end: number, subarray?: boolean): SortedCollection; | ||
rank(element: any): number; | ||
uniquify(): this; | ||
static getDifference<T extends Collection>(a: T, b: Collection, symmetric?: boolean, comparator?: Comparator): T; | ||
static getDifference<T extends Collection>(a: Collection, b: Collection, symmetric?: boolean, comparator?: Comparator, container?: T): T; | ||
static getDifferenceScore(a: Collection, b: Collection, symmetric?: boolean, comparator?: Comparator): number; | ||
static getIndex(arr: Collection, target: any, comparator?: Comparator, rank?: boolean, start?: number, end?: number): number; | ||
static getIntersection<T extends Collection>(a: T, b: Collection, comparator?: Comparator): T; | ||
static getIntersection<T extends Collection>(a: Collection, b: Collection, comparator?: Comparator, container?: T): T; | ||
static getIntersectionScore(a: Collection, b: Collection, comparator?: Comparator): number; | ||
static getRange<T extends Collection>(arr: T, start?: number, end?: number, comparator?: Comparator): T; | ||
static getRank(arr: Collection, target: any, comparator?: Comparator): number; | ||
static getUnion<T extends Collection>(a: T, b: Collection, unique?: boolean, comparator?: Comparator): T; | ||
static getUnique<T extends Collection>(arr: T, comparator?: Comparator): T; | ||
static getRange<T extends Collection>(arr: T, start?: number, end?: number, comparator?: Comparator, subarray?: boolean): T; | ||
static getUnion<T extends Collection>(a: Collection, b: Collection, unique?: boolean, comparator?: Comparator, container?: T): T; | ||
static getUnique<T extends Collection>(arr: Collection, comparator?: Comparator, container?: T): T; | ||
static isSorted(arr: Collection, comparator?: Comparator): boolean; | ||
@@ -119,1 +119,35 @@ static isUnique(arr: Collection, comparator?: Comparator): boolean; | ||
export declare function SortedMixin<T extends Collection>(Base?: Constructor<T>): Constructor<T & SortedCollection> | ||
export class SortedArray extends SortedMixin(Array) { | ||
unique: boolean; | ||
set(arr: Collection): this; | ||
uniquify(): this; | ||
} | ||
interface RecordField { | ||
name: string; | ||
type: string; | ||
size?: number; | ||
littleEndian?: boolean; | ||
} | ||
interface RecordSchema { | ||
[propName: string]: RecordField; | ||
} | ||
export declare class RecordArray extends DataView { | ||
size: number; | ||
private fields: RecordField[]; | ||
private schema: RecordSchema; | ||
private offset: number; | ||
private offsets: object; | ||
private stringView: Uint8Array; | ||
constructor(fields: RecordField[], size: number, buffer?: ArrayBuffer, byteOffset?: number, byteLength?: number); | ||
get(index: number, field: string): any; | ||
set(index: number, field: string, value: any, littleEndian?: boolean): this; | ||
getString(offset: number, littleEndian: boolean, size: number): Uint8Array; | ||
setString(offset: number, value: Collection): void; | ||
toObject(index: number): object; | ||
} |
27
index.js
@@ -0,9 +1,28 @@ | ||
const GridMixin = require('./lib/grid'); | ||
const BitField = require('./lib/bit-field'); | ||
const RecordArray = require('./lib/record-array'); | ||
const SortedArray = require('./lib/sorted-array'); | ||
const PackedInt = require('./lib/packed-int'); | ||
const GridMixin = require('./lib/grid'); | ||
const SortedMixin = require('./lib/sorted-collection'); | ||
/** | ||
* @external ArrayBuffer | ||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer|ArrayBuffer} | ||
*/ | ||
/** | ||
* @external DataView | ||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView|DataView} | ||
*/ | ||
/** | ||
* @external TypedArray | ||
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray|TypedArray} | ||
*/ | ||
module.exports = { | ||
GridMixin, | ||
BitField, | ||
RecordArray, | ||
SortedArray, | ||
PackedInt, | ||
GridMixin, | ||
SortedMixin, | ||
}; |
@@ -18,3 +18,2 @@ /** | ||
* Int8Array| | ||
* Int8Array| | ||
* Uint8Array| | ||
@@ -31,5 +30,5 @@ * Uint8ClampedArray| | ||
/** | ||
* @typedef {Array} Coordinates | ||
* @property {number} 0 row index | ||
* @property {number} 1 column index | ||
* @typedef {Object} Coordinates | ||
* @property {number} row row index | ||
* @property {number} column column index | ||
*/ | ||
@@ -87,2 +86,3 @@ | ||
pad: { value: pad, writable: true }, | ||
lastCoordinates: { value: Object.seal({ row: 0, column: 0 }) }, | ||
}); | ||
@@ -118,2 +118,18 @@ } | ||
/** | ||
* Returns an array index of an element at given coordinates. | ||
* | ||
* @param {number} row | ||
* @param {number} column | ||
* @returns {*} | ||
* @example | ||
* | ||
* const a = ArrayGrid({ rows: 3, columns: 2, pad: 3}); | ||
* a.get(1, 0); | ||
* //=> 2 | ||
*/ | ||
getIndex(row, column) { | ||
return (row << this.offset) + column; | ||
} | ||
/** | ||
* Returns an element from given coordinates. | ||
@@ -131,3 +147,3 @@ * | ||
get(row, column) { | ||
return this[(row << this.offset) + column]; | ||
return this[this.getIndex(row, column)]; | ||
} | ||
@@ -141,3 +157,3 @@ | ||
* @param {*} value | ||
* @returns {*} | ||
* @returns {Grid} the instance | ||
* @example | ||
@@ -151,3 +167,4 @@ * | ||
set(row, column, value) { | ||
return this[(row << this.offset) + column] = value; | ||
this[this.getIndex(row, column)] = value; | ||
return this; | ||
} | ||
@@ -168,5 +185,5 @@ | ||
getCoordinates(index) { | ||
const row = index >> this.offset; | ||
const column = index - (row << this.offset); | ||
return [row, column]; | ||
this.lastCoordinates.row = index >> this.offset; | ||
this.lastCoordinates.column = index - (this.lastCoordinates.row << this.offset); | ||
return this.lastCoordinates; | ||
} | ||
@@ -173,0 +190,0 @@ |
@@ -1,36 +0,8 @@ | ||
/** | ||
* @name Comparator | ||
* @function | ||
* @param {*} a | ||
* @param {*} b | ||
* @returns {number} | ||
*/ | ||
const SortedMixin = require('./sorted-collection'); | ||
/** | ||
* Extends built-in Array to efficiently handle sorted data. | ||
* | ||
* @extends Array | ||
* @extends SortedCollection | ||
*/ | ||
class SortedArray extends Array { | ||
class SortedArray extends SortedMixin(Array) { | ||
/** | ||
* The default comparator. | ||
* | ||
* @param {*} a the first value | ||
* @param {*} b the second value | ||
* @returns {number} | ||
* @example | ||
* | ||
* //=> SortedArray [ 2, 3, 4, 5, 9 ]; | ||
* sortedArray.compare = (a, b) => (a > b ? -1 : a < b ? 1 : 0); | ||
* sortedArray.sort(); | ||
* //=> [ 9, 5, 4, 3, 2 ] | ||
*/ | ||
compare(a, b) { | ||
if (a > b) return 1; | ||
if (a < b) return -1; | ||
if (a === b) return 0; | ||
throw new RangeError('Unstable comparison.'); | ||
} | ||
/** | ||
* Returns a merger of the array with one or more provided sorted arrays. | ||
@@ -45,3 +17,5 @@ * | ||
for (let i = 0; i < arrays.length; i++) { | ||
result = this.constructor.getUnion(result, arrays[i], this.unique, this.compare); | ||
result = this.constructor.getUnion( | ||
result, arrays[i], this.unique, this.compare, new this.constructor(), | ||
); | ||
} | ||
@@ -52,58 +26,2 @@ return result; | ||
/** | ||
* Uses binary search to quickly check if the element is the array. | ||
* | ||
* @private | ||
* @param {*} element the element to check | ||
* @returns {boolean} whether the element is in the array | ||
*/ | ||
includes(element) { | ||
return !!~this.indexOf(element); | ||
} | ||
/** | ||
* Looks for the index of a given element in the array or -1 | ||
* | ||
* @private | ||
* @param {*} element the element to look for | ||
* @returns {number} the element's index in the array or -1 | ||
*/ | ||
indexOf(element) { | ||
return this.constructor.getIndex(this, element, this.compare); | ||
} | ||
/** | ||
* Checks if the array is sorted. | ||
* | ||
* @returns {boolean} whether the array is sorted | ||
* @example | ||
* | ||
* //=> SortedArray [ 2, 3, 4, 5, 9 ]; | ||
* sortedArray.isSorted(); | ||
* //=> true | ||
* sortedArray.reverse(); | ||
* sortedArray.isSorted(); | ||
* //=> false; | ||
*/ | ||
isSorted() { | ||
return this.constructor.isSorted(this, this.compare); | ||
} | ||
/** | ||
* Checks if the array has duplicating elements. | ||
* | ||
* @returns {boolean} whether the array has duplicating elements | ||
* @example | ||
* | ||
* //=> SortedArray [ 2, 3, 4, 5, 9 ]; | ||
* sortedArray.isUnique(); | ||
* //=> true | ||
* sortedArray.push(2); | ||
* sortedArray.isUnique(); | ||
* //=> false; | ||
*/ | ||
isUnique() { | ||
return this.constructor.isUnique(this, this.compare); | ||
} | ||
/** | ||
* Adds provided elements to the array preserving the sorted order of the array. | ||
@@ -128,3 +46,5 @@ * | ||
const toAdd = this.unique ? this.constructor.getUnique(elements, this.compare) : elements; | ||
this.reset(this.constructor.getUnion(this, toAdd, this.unique, this.compare)); | ||
this.set(this.constructor.getUnion( | ||
this, toAdd, this.unique, this.compare, new this.constructor(), | ||
)); | ||
} | ||
@@ -137,3 +57,3 @@ return this.length; | ||
* | ||
* @param {Array} arr an array of new elements to use | ||
* @param {Collection} arr an array of new elements to use | ||
* @returns {SortedArray} | ||
@@ -143,7 +63,7 @@ * @example | ||
* //=> SortedArray [ 2, 3, 4, 5, 9 ]; | ||
* sortedArray.reset([1, 2, 3]); | ||
* sortedArray.set([1, 2, 3]); | ||
* //=> SortedArray [ 1, 2, 3 ] | ||
*/ | ||
reset(arr) { | ||
this.length = 0; | ||
set(arr) { | ||
this.length = arr.length; | ||
for (let i = 0; i < arr.length; i++) { | ||
@@ -156,51 +76,2 @@ this[i] = arr[i]; | ||
/** | ||
* Returns a range of elements of the array that are greater or equal to the provided | ||
* starting element and less or equal to the provided ending element. | ||
* | ||
* @param {*} start the starting element | ||
* @param {*} end the ending element | ||
* @returns {SortedArray} the resulting range of elements | ||
* @example | ||
* | ||
* //=> SortedArray [ 2, 3, 4, 5, 9 ]; | ||
* sortedArray.range(3, 5); | ||
* // => [ 3, 4, 5 ] | ||
* sortedArray.range(undefined, 4); | ||
* // => [ 2, 3, 4 ] | ||
* sortedArray.range(4); | ||
* // => [ 4, 5, 8 ] | ||
*/ | ||
range(start, end) { | ||
return this.constructor.getRange(this, start, end, this.compare); | ||
} | ||
/** | ||
* Returns the rank of an element in the array. | ||
* | ||
* @param {*} element the element to look for | ||
* @returns {number} the rank in the array | ||
* @example | ||
* | ||
* //=> SortedArray [ 2, 3, 4, 5, 9 ]; | ||
* sortedArray.rank(1); | ||
* // => 0 | ||
* sortedArray.rank(6); | ||
* // => 4 | ||
*/ | ||
rank(element) { | ||
return this.constructor.getIndex(this, element, this.compare, true); | ||
} | ||
/** | ||
* Sorts the array with a provided compare function. | ||
* | ||
* @private | ||
* @param {Comparator} compareFunction the function to use for comparison | ||
* @returns {void} | ||
*/ | ||
sort(compareFunction = this.compare) { | ||
super.sort(compareFunction); | ||
} | ||
/** | ||
* Changes the array by removing existing elements and adding new ones. | ||
@@ -231,3 +102,3 @@ * | ||
uniquify() { | ||
return this.reset(this.constructor.getUnique(this, this.compare)); | ||
return this.set(this.constructor.getUnique(this, this.compare, new this.constructor())); | ||
} | ||
@@ -245,364 +116,4 @@ | ||
} | ||
/** | ||
* Creates a new SortedArray from a given array-like object. | ||
* | ||
* @private | ||
* @param {*} arrayLike an array-like object to convert to a SortedArray | ||
* @param {Function} mapFn a map function to call on every element of the array | ||
* @param {Object} thisArg the value to use as `this` when invoking the `mapFn` | ||
* @returns {SortedArray} a new SortedArray | ||
*/ | ||
static from(arrayLike, mapFn, thisArg) { | ||
const result = super.from(arrayLike, mapFn, thisArg); | ||
result.sort(); | ||
return result; | ||
} | ||
/** | ||
* Returns the difference of two sorted arrays, i.e. elements present in the first array but not | ||
* in the second array. If `symmetric=true` finds the symmetric difference of two arrays, that is, | ||
* the elements that are absent in one or another array. | ||
* | ||
* @param {Array} a the first array | ||
* @param {Array} b the second array | ||
* @param {boolean} [symmetric=false] whether to get symmetric difference. | ||
* @param {Comparator} [comparator] the comparator static used to sort the arrays | ||
* @returns {Array} the difference of the arrays | ||
* @example | ||
* | ||
* SortedArray.getDifference([1, 2, 3, 4, 8], [2, 4, 6, 7, 9]); | ||
* //=> [ 1, 3, 8 ] | ||
* | ||
* // symmetric difference of sorted arrays: | ||
* SortedArray.getDifference(first, second, true); | ||
* //=> [ 1, 3, 6, 7, 8, 9 ] | ||
* // difference using a custom comparator: | ||
* const customComparator = (a, b) => (a > b ? -1 : a < b ? 1 : 0); | ||
* SortedArray.getDifference([8, 4, 3, 2, 1], [9, 7, 6, 4, 2], false, customComparator); | ||
* //=> [ 8, 3, 1 ] | ||
*/ | ||
static getDifference(a, b, symmetric, comparator = this.prototype.compare) { | ||
const difference = new a.constructor(); | ||
let i = 0; | ||
let j = 0; | ||
while (i < a.length && j < b.length) { | ||
const compared = comparator(a[i], b[j]); | ||
if (compared > 0) { | ||
if (symmetric) difference[difference.length] = b[j]; | ||
j++; | ||
} else if (compared < 0) { | ||
difference[difference.length] = a[i]; | ||
i++; | ||
} else { | ||
i++; | ||
j++; | ||
} | ||
} | ||
while (i < a.length) { | ||
difference[difference.length] = a[i]; | ||
i++; | ||
} | ||
if (symmetric) { | ||
while (j < b.length) { | ||
difference[difference.length] = b[j]; | ||
j++; | ||
} | ||
} | ||
return difference; | ||
} | ||
/** | ||
* Returns the amount of differing elements in the first array. | ||
* | ||
* @param {Array} a the first array | ||
* @param {Array} b the second array | ||
* @param {boolean} [symmetric=false] whether to use symmetric difference | ||
* @param {Comparator} [comparator] the comparator static used to sort the arrays | ||
* @returns {number} the amount of differing elements | ||
* @example | ||
* | ||
* SortedArray.getDifferenceScore([1, 2, 3, 4, 8], [2, 4, 6, 7, 9]); | ||
* //=> 3 | ||
*/ | ||
static getDifferenceScore(a, b, symmetric, comparator) { | ||
const score = this.getIntersectionScore(a, b, comparator); | ||
return symmetric ? (a.length + b.length) - (2 * score) : a.length - score; | ||
} | ||
/** | ||
* Uses binary search to find the index of an element inside a sorted array. | ||
* | ||
* @param {Array} arr the array to search | ||
* @param {*} target the target value to search for | ||
* @param {Comparator} [comparator] a custom comparator | ||
* @param {boolean} [rank] whether to return the element's rank if the element isn't found | ||
* @param {number} [start] the start position of the search | ||
* @param {number} [end] the end position of the search | ||
* @returns {number} the index of the searched element or it's rank | ||
* @example | ||
* | ||
* SortedArray.getIndex([1, 2, 3, 4, 8], 4); | ||
* //=> 3 | ||
*/ | ||
static getIndex( | ||
arr, target, comparator = this.prototype.compare, | ||
rank = false, start = 0, end = arr.length - 1, | ||
) { | ||
let left = start; | ||
let right = end; | ||
let m; | ||
while (left <= right) { | ||
m = (left + right) >> 1; | ||
const compared = comparator(arr[m], target); | ||
if (compared < 0) { | ||
left = m + 1; | ||
} else if (compared > 0) { | ||
right = m - 1; | ||
} else { | ||
return m; | ||
} | ||
} | ||
return rank ? left : -1; | ||
} | ||
/** | ||
* Returns the intersection of two sorted arrays. | ||
* | ||
* @param {Array} a the first array | ||
* @param {Array} b the second array | ||
* @param {Comparator} [comparator] the comparator static used to sort the arrays | ||
* @returns {Array} the intersection of the arrays | ||
* @example | ||
* | ||
* SortedArray.getIntersection([1, 2, 3, 4, 8], [2, 4, 6, 7, 9]); | ||
* //=> [ 2, 4 ] | ||
* | ||
* // intersection using a custom comparator: | ||
* const customComparator = (a, b) => (a > b ? -1 : a < b ? 1 : 0); | ||
* SortedArray.getIntersection([8, 4, 3, 2, 1], [9, 7, 6, 4, 2], customComparator); | ||
* //=> [ 4, 2 ] | ||
*/ | ||
static getIntersection(a, b, comparator = this.prototype.compare) { | ||
const intersection = new a.constructor(); | ||
let i = 0; | ||
let j = 0; | ||
while (i < a.length && j < b.length) { | ||
const compared = comparator(a[i], b[j]); | ||
if (compared > 0) { | ||
j++; | ||
} else if (compared < 0) { | ||
i++; | ||
} else { | ||
intersection[intersection.length] = a[i]; | ||
i++; | ||
j++; | ||
} | ||
} | ||
return intersection; | ||
} | ||
/** | ||
* Returns the amount of common elements in two sorted arrays. | ||
* | ||
* @param {Array} a the first array | ||
* @param {Array} b the second array | ||
* @param {Comparator} [comparator] the comparator static used to sort the arrays | ||
* @returns {number} the amount of different elements | ||
* @example | ||
* | ||
* SortedArray.getIntersection([1, 2, 3, 4, 8], [2, 4, 6, 7, 9]); | ||
* //=> 2 | ||
*/ | ||
static getIntersectionScore(a, b, comparator = this.prototype.compare) { | ||
let score = 0; | ||
let i = 0; | ||
let j = 0; | ||
while (i < a.length && j < b.length) { | ||
const compared = comparator(a[i], b[j]); | ||
if (compared > 0) { | ||
j++; | ||
} else if (compared < 0) { | ||
i++; | ||
} else { | ||
score++; | ||
i++; | ||
j++; | ||
} | ||
} | ||
return score; | ||
} | ||
/** | ||
* Returns a range of elements of a sorted array from the start through the end inclusively. | ||
* | ||
* @param {Array} arr the array | ||
* @param {number} [start] the starting item | ||
* @param {number} [end] the ending item | ||
* @param {Comparator} [comparator] a custom comparator | ||
* @returns {Array} the range of items | ||
* @example | ||
* | ||
* SortedArray.getRange([1, 2, 3, 4, 8], 2, 4); | ||
* //=> [ 2, 3, 4 ] | ||
* | ||
* const customComparator = (a, b) => (a > b ? -1 : a < b ? 1 : 0); | ||
* SortedArray.getRange([8, 4, 3, 2, 1], 8, 3, customComparator); | ||
* //=> [ 8, 4, 3 ] | ||
*/ | ||
static getRange(arr, start, end, comparator) { | ||
const startIndex = start === undefined ? 0 : this.getIndex(arr, start, comparator, true); | ||
const endIndex = end === undefined ? arr.length | ||
: this.getIndex(arr, end, comparator, true, startIndex) + 1; | ||
return arr.slice(startIndex, endIndex); | ||
} | ||
/** | ||
* Uses binary search to find the rank of an item inside a sorted array. | ||
* | ||
* @deprecated use SortedArray.getIndex directly instead | ||
* @param {Array} arr the array to search | ||
* @param {*} target the target value to search for | ||
* @param {Comparator} [comparator] a custom comparator | ||
* @returns {number} the rank of the searched item | ||
* @example | ||
* | ||
* SortedArray.getRank([1, 2, 3, 4, 8], 5); | ||
* //=> 4 | ||
* | ||
* const customComparator = (a, b) => (a > b ? -1 : a < b ? 1 : 0); | ||
* SortedArray.getRank([8, 4, 3, 2, 1], 5, customComparator); | ||
* //=> 3 | ||
*/ | ||
static getRank(arr, target, comparator) { | ||
return this.getIndex(arr, target, comparator, true); | ||
} | ||
/** | ||
* Returns the union of two sorted arrays as a sorted array. | ||
* | ||
* @param {Array} a the first array | ||
* @param {Array} b the second array | ||
* @param {boolean} [unique=false] whether to avoid duplicating items when merging unique arrays | ||
* @param {Comparator} [comparator] the comparator static used to sort the arrays | ||
* @returns {Array} the union of the arrays | ||
* @example | ||
* | ||
* SortedArray.getUnion([1, 2, 3, 4, 8], [2, 4, 6, 7, 9]); | ||
* //=> [ 1, 2, 2, 3, 4, 4, 6, 7, 8, 9 ] | ||
* | ||
* // union of sorted arrays without duplicates: | ||
* SortedArray.getUnion([1, 2, 3, 4, 8], [2, 4, 6, 7, 9], true); | ||
* //=> [ 1, 2, 3, 4, 6, 7, 8, 9 ] | ||
* | ||
* //union using a custom comparator: | ||
* SortedArray.getUnion([8, 4, 3, 2, 1], [9, 7, 6, 4, 2], true, customComparator); | ||
* //=> [ 9, 8, 7, 6, 4, 3, 2, 1 ] | ||
*/ | ||
static getUnion(a, b, unique, comparator = this.prototype.compare) { | ||
const union = new a.constructor(); | ||
let i = 0; | ||
let j = 0; | ||
while (i < a.length && j < b.length) { | ||
const compared = comparator(a[i], b[j]); | ||
if (compared > 0) { | ||
union[union.length] = b[j]; | ||
j++; | ||
} else if (compared < 0) { | ||
union[union.length] = a[i]; | ||
i++; | ||
} else { | ||
union[union.length] = a[i]; | ||
if (!unique) union[union.length] = b[j]; | ||
i++; | ||
j++; | ||
} | ||
} | ||
while (i < a.length) { | ||
union[union.length] = a[i]; | ||
i++; | ||
} | ||
while (j < b.length) { | ||
union[union.length] = b[j]; | ||
j++; | ||
} | ||
return union; | ||
} | ||
/** | ||
* Returns an array of unique elements from a sorted array. | ||
* | ||
* @param {Array} arr the sorted array | ||
* @param {Comparator} [comparator] a custom comparator | ||
* @returns {Array} the sorted array without duplicates | ||
* @example | ||
* | ||
* SortedArray.getUnique([1, 1, 2, 2, 3, 4]); | ||
* //=> [ 1, 2, 3, 4 ] | ||
*/ | ||
static getUnique(arr, comparator = this.prototype.compare) { | ||
const unique = new arr.constructor(); | ||
unique[0] = arr[0]; | ||
for (let i = 1; i < arr.length; i++) { | ||
if (comparator(arr[i - 1], arr[i]) !== 0) { | ||
unique[unique.length] = arr[i]; | ||
} | ||
} | ||
return unique; | ||
} | ||
/** | ||
* Creates a new SortedArray instance with a variable number of arguments, | ||
* regardless of number or type of the arguments | ||
* | ||
* @private | ||
* @param {...*} elements the elements of which to create the array | ||
* @returns {SortedArray} the new SortedArray | ||
*/ | ||
static of(...elements) { | ||
const result = super.of(...elements); | ||
result.sort(); | ||
return result; | ||
} | ||
/** | ||
* Checks whether an array is sorted according to a provided comparator. | ||
* | ||
* @param {Array} arr the array to check | ||
* @param {Comparator} [comparator] a custom comparator | ||
* @returns {boolean} whether the array is sorted | ||
* | ||
* @example | ||
* | ||
* SortedArray.isSorted([1, 2, 3, 4, 8]); | ||
* //=> true | ||
*/ | ||
static isSorted(arr, comparator = this.prototype.compare) { | ||
for (let i = 1; i < arr.length; i++) { | ||
if (comparator(arr[i - 1], arr[i]) > 0) return false; | ||
} | ||
return true; | ||
} | ||
/** | ||
* Checks whether an array has any duplicating elements. | ||
* | ||
* @param {Array} arr the array to check | ||
* @param {Comparator} [comparator] a custom comparator | ||
* @returns {boolean} whether the array has duplicating elements | ||
* @example | ||
* | ||
* SortedArray.isUnique([1, 2, 2, 3, 4]); | ||
* //=> false | ||
*/ | ||
static isUnique(arr, comparator = this.prototype.compare) { | ||
for (let i = 1; i < arr.length; i++) { | ||
if (comparator(arr[i - 1], arr[i]) === 0) return false; | ||
} | ||
return true; | ||
} | ||
} | ||
module.exports = SortedArray; |
{ | ||
"name": "structurae", | ||
"version": "0.0.5", | ||
"version": "0.0.9", | ||
"description": "Data structures for performance-sensitive modern JavaScript applications.", | ||
@@ -48,2 +48,3 @@ "main": "index.js", | ||
"jest": { | ||
"testEnvironment": "node", | ||
"collectCoverage": true, | ||
@@ -50,0 +51,0 @@ "collectCoverageFrom": [ |
181
README.md
@@ -7,7 +7,8 @@ # Structurae | ||
A collection of data structures for performance-sensitive modern JavaScript applications that includes: | ||
A collection of data structures for high-performance modern JavaScript applications that includes: | ||
- [Grid](https://github.com/zandaqo/structurae#grid) - extends built-in indexed collections to handle 2 dimensional data (e.g. nested arrays). | ||
- [PackedInt](https://github.com/zandaqo/structurae#PackedInt) - stores and operates on data in Numbers and BigInts treating them as bitfields. | ||
- [SortedArray](https://github.com/zandaqo/structurae#SortedArray) - extends built-in Array to efficiently handle sorted data. | ||
- [BitField](https://github.com/zandaqo/structurae#BitField) - stores and operates on data in Numbers and BigInts treating them as bitfields. | ||
- [RecordArray](https://github.com/zandaqo/structurae#RecordArray) - extends DataView to use ArrayBuffer as an array of records or C-like structs. | ||
- [SortedCollection](https://github.com/zandaqo/structurae#SortedCollection) & [SortedArray](https://github.com/zandaqo/structurae#SortedArray) - extends built-in Array or TypedArrays to efficiently handle sorted data. | ||
@@ -22,6 +23,6 @@ ## Installation | ||
```javascript | ||
import { Grid, PackedInt, SortedArray } from 'structurae'; | ||
import { GridMixin, BitField, RecordArray, SortedArray, SortedMixin } from 'structurae'; | ||
// or | ||
const { Grid, PackedInt, SortedArray } = require('structurae'); | ||
const { GridMixin, BitField, RecordArray, SortedArray, SortedMixin } = require('structurae'); | ||
``` | ||
@@ -35,3 +36,3 @@ | ||
```javascript | ||
const ArrayGrid = Grid(Array); | ||
const ArrayGrid = GridMixin(Array); | ||
@@ -66,10 +67,15 @@ // create a grid of 5 rows and 4 columns filled with 0 | ||
// use `getCoordinates` method to find out row and column indexes of a given element by its array index: | ||
// use `getIndex` to get an array index of an element at given coordinates | ||
grid.getIndex(0, 1); | ||
//=> 1 | ||
// use `getCoordinates` to find out row and column indexes of a given element by its array index: | ||
grid.getCoordinates(0); | ||
//=> [0, 0] | ||
//=> { row: 0, column: 0 } | ||
grid.getCoordinates(1); | ||
//=> [0, 1] | ||
//=> { row: 0, column: 1 } | ||
``` | ||
A grid can be turned to and from an array of nested arrays using respectively `Grid#fromArrays` and `Grid#toArrays` methods: | ||
A grid can be turned to and from an array of nested arrays using respectively `Grid.fromArrays` and `Grid#toArrays` methods: | ||
```javascript | ||
@@ -96,7 +102,7 @@ const grid = ArrayGrid.fromArrays([[1,2], [3, 4]]); | ||
### PackedInt | ||
PackedInt uses JavaScript Numbers and BigInts as bitfields to store and operate on data using bitwise operations. | ||
By default, PackedInt operates on 31 bit long bitfield where bits are indexed from least significant to most: | ||
### BitField | ||
BitField uses JavaScript Numbers and BigInts as bitfields to store and operate on data using bitwise operations. | ||
By default, BitField operates on 31 bit long bitfield where bits are indexed from least significant to most: | ||
```javascript | ||
const bitfield = new PackedInt(29); // 29 === 0b11101 | ||
const bitfield = new BitField(29); // 29 === 0b11101 | ||
bitfield.get(0); | ||
@@ -110,5 +116,5 @@ //=> 1 | ||
You can extend PackedInt and use your own schema by specifying field names and their respective sizes in bits: | ||
You can extend BitField and use your own schema by specifying field names and their respective sizes in bits: | ||
```javascript | ||
class Person extends PackedInt {} | ||
class Person extends BitField {} | ||
Person.fields = [ | ||
@@ -127,3 +133,3 @@ { name: 'age', size: 7 }, | ||
person.toObject(); | ||
//=> { age: 20, gender: 1 } | ||
//=> { age: 18, gender: 1 } | ||
``` | ||
@@ -133,3 +139,3 @@ | ||
```javascript | ||
class Privileges extends PackedInt {} | ||
class Privileges extends BitField {} | ||
Privileges.fields = ['user', 'moderator', 'administrator']; | ||
@@ -145,6 +151,6 @@ | ||
If the total size of your fields exceeds 31 bits, PackedInt will internally use a BigInt to represent the resulting number, | ||
If the total size of your fields exceeds 31 bits, BitField will internally use a BigInt to represent the resulting number, | ||
however, you can still use normal numbers to set each field and get their value as a number as well: | ||
```javascript | ||
class LargeField extends PackedInt {} | ||
class LargeField extends BitField {} | ||
LargeField.fields = [ | ||
@@ -166,3 +172,3 @@ { name: 'width', size: 20 }, | ||
```javascript | ||
class OldPerson extends PackedInt {} | ||
class OldPerson extends BitField {} | ||
OldPerson.fields = [ | ||
@@ -176,3 +182,3 @@ { name: 'age', size: 7 }, | ||
class Person extends PackedInt {} | ||
class Person extends BitField {} | ||
Person.fields = [ | ||
@@ -193,5 +199,5 @@ { name: 'age', size: 7 }, | ||
If you only want to encode or decode a set of field values without creating an instance, you can do so by use static methods | ||
`PackedInt.encode` and `PackedInt.decode` respectively: | ||
`BitField.encode` and `BitField.decode` respectively: | ||
```javascript | ||
class Person extends PackedInt {} | ||
class Person extends BitField {} | ||
Person.fields = [ | ||
@@ -209,11 +215,11 @@ { name: 'age', size: 7 }, | ||
If you don't know beforehand how many bits you need for your field, you can call `PackedInt.getMinSize` with the maximum | ||
If you don't know beforehand how many bits you need for your field, you can call `BitField.getMinSize` with the maximum | ||
possible value of your field to find out: | ||
```javascript | ||
PackedInt.getMinSize(100); | ||
BitField.getMinSize(100); | ||
//=> 7 | ||
class Person extends PackedInt {} | ||
class Person extends BitField {} | ||
Person.fields = [ | ||
{ name: 'age', size: PackedInt.getMinSize(100) }, | ||
{ name: 'age', size: BitField.getMinSize(100) }, | ||
{ name: 'gender', size: 1 }, | ||
@@ -223,6 +229,6 @@ ]; | ||
For performance sake, PackedInt doesn't check the size of values being set and setting values that exceed the specified | ||
field size will lead to undefined behavior. If you want to check whether values fit their respective fields, you can use `PackedInt.isValid`: | ||
For performance sake, BitField doesn't check the size of values being set and setting values that exceed the specified | ||
field size will lead to undefined behavior. If you want to check whether values fit their respective fields, you can use `BitField.isValid`: | ||
```javascript | ||
class Person extends PackedInt {} | ||
class Person extends BitField {} | ||
Person.fields = [ | ||
@@ -243,3 +249,3 @@ { name: 'age', size: 7 }, | ||
`PackedInt#match` (and its static variation `PackedInt.match`) can be used to check values of multiple fields at once: | ||
`BitField#match` (and its static variation `BitField.match`) can be used to check values of multiple fields at once: | ||
```javascript | ||
@@ -257,3 +263,3 @@ const person = new Person([20, 1]); | ||
If you have to check multiple PackedInts for the same values, create a special matcher with `PackedInt.getMatcher` | ||
If you have to check multiple BitField instances for the same values, create a special matcher with `BitField.getMatcher` | ||
and use it in the match method, that way each check will require only one bitwise operation and a comparison: | ||
@@ -268,30 +274,91 @@ ```javascript | ||
### SortedArray | ||
SortedArray extends built-in Array to efficiently handle sorted data. | ||
### RecordArray | ||
RecordArray extends DataView to use ArrayBuffer as an array of records or C-like structs. | ||
Fields of the records can be of any type supported by DataView plus string type. | ||
For a string type, the maximum size in bytes should be set in our schema. | ||
To create a SortedArray from unsorted array-like objects or items use `SortedArray.from` and `SortedArray.of` respectively: | ||
```javascript | ||
// create an array of 20 records where each has 'age', 'score', and 'name' fields | ||
const people = new RecordArray([ | ||
{ name: 'age', type: 'Uint8' }, | ||
{ name: 'score', type: 'Float32' }, | ||
{ name: 'name', type: 'String', size: 10 }, | ||
], 20); | ||
// get the 'age' field value for the first struct in the array | ||
people.get(0, 'age'); | ||
//=> 0 | ||
// set the 'age' and 'score' field values for the first struct | ||
people.set(0, 'age', 10).set(0, 'score', 5.0); | ||
people.toObject(0); | ||
//=> { age: 10, score: 5.0 } | ||
``` | ||
### SortedCollection | ||
SortedCollection extends a given built-in indexed collection with methods to efficiently handle sorted data. | ||
```javascript | ||
const SortedInt32Array = SortedMixin(Int32Array); | ||
``` | ||
To create a sorted collection from unsorted array-like objects or items use `from` and `of` static methods respectively: | ||
```js | ||
SortedArray.from(unsorted); | ||
//=> SortedArray [ 2, 3, 4, 5, 9 ] | ||
SortedArray.of(8, 5, 6); | ||
//=> SortedArray [ 5, 6, 8 ] | ||
SortedInt32Array.from(unsorted); | ||
//=> SortedInt32Array [ 2, 3, 4, 5, 9 ] | ||
SortedInt32Array.of(8, 5, 6); | ||
//=> SortedInt32Array [ 5, 6, 8 ] | ||
``` | ||
`new SortedArray` behaves the same way as `new Array` and should be used with already sorted elements: | ||
`new SortedInt32Array` behaves the same way as `new Int32Array` and should be used with already sorted elements: | ||
```js | ||
new SortedArray(...first); | ||
//=> SortedArray [ 1, 2, 3, 4, 8 ]; | ||
new SortedArray(2,3,4); | ||
//=> SortedArray [ 2, 3, 4 ]; | ||
new SortedInt32Array(...[ 1, 2, 3, 4, 8 ]); | ||
//=> SortedInt32Array [ 1, 2, 3, 4, 8 ]; | ||
new SortedInt32Array(2,3,4); | ||
//=> SortedInt32Array [ 2, 3, 4 ]; | ||
``` | ||
A custom comparison function can be specified on the array instance to be used for sorting: | ||
A custom comparison function can be specified on the collection instance to be used for sorting: | ||
```js | ||
sortedArray.compare = (a, b) => (a > b ? -1 : a < b ? 1 : 0); | ||
sortedArray.sort(); | ||
//=> [ 9, 5, 4, 3, 2 ] | ||
//=> SortedInt32Array [ 2, 3, 4, 5, 9 ] | ||
sortedInt32Array.compare = (a, b) => (a > b ? -1 : a < b ? 1 : 0); | ||
sortedInt32Array.sort(); | ||
//=> SortedInt32Array [ 9, 5, 4, 3, 2 ] | ||
``` | ||
SortedArray supports all methods of Array. The methods that change the contents of an array do so while preserving the sorted order: | ||
SortedCollection supports all the methods of its base class: | ||
```javascript | ||
//=> SortedInt32Array [ 2, 3, 4, 5, 9 ] | ||
sortedInt32Array.slice(0, 2) | ||
//=> SortedInt32Array [ 2, 3 ] | ||
sortedInt32Array.set([0, 0, 1]) | ||
//=> SortedInt32Array [ 0, 0, 1, 5, 9 ] | ||
``` | ||
`indexOf` and `includes` use binary search that increasingly outperforms the built-in methods as the size of the collection grows. | ||
SortedCollection provides `isSorted` method to check if the collection is sorted, | ||
and `range` method to get elements of the collection whose values are between the specified range: | ||
```js | ||
//=> SortedInt32Array [ 2, 3, 4, 5, 9 ] | ||
sortedInt32Array.range(3, 5); | ||
// => SortedInt32Array [ 3, 4, 5 ] | ||
sortedInt32Array.range(undefined, 4); | ||
// => SortedInt32Array [ 2, 3, 4 ] | ||
sortedInt32Array.range(4); | ||
// => SortedInt32Array [ 4, 5, 8 ] | ||
// set `subarray` to `true` to use `TypedArray#subarray` for the return value instead of copying it with slice: | ||
sortedInt32Array.range(3, 5, true).buffer === sortedInt32Array.buffer; | ||
// => true; | ||
``` | ||
SortedCollection also provides a set of functions to perform common set operations | ||
and find statistics of any sorted array-like objects without converting them to sorted collection. | ||
Check [API documentation](https://github.com/zandaqo/structurae/blob/master/doc/API.md) for more information. | ||
### SortedArray | ||
SortedArray extends SortedCollection using built-in Array. | ||
SortedArray supports all the methods of Array as well as those provided by SortedCollection. | ||
The methods that change the contents of an array do so while preserving the sorted order: | ||
```js | ||
sortedArray.push(1); | ||
@@ -305,14 +372,2 @@ //=> SortedArray [ 1, 2, 3, 4, 5, 9 ] | ||
`indexOf` and `includes` use binary search that increasingly outperforms the built-in methods as the size of the array grows. | ||
In addition to the Array instance methods, SortedArray provides `isSorted` method to check if the array is sorted, and `range` method to get elements of the array whose values are between the specified range: | ||
```js | ||
sortedArray.range(3, 5); | ||
// => [ 3, 4, 5 ] | ||
sortedArray.range(undefined, 4); | ||
// => [ 2, 3, 4 ] | ||
sortedArray.range(4); | ||
// => [ 4, 5, 8 ] | ||
``` | ||
`uniquify` can be used to remove duplicating elements from the array: | ||
@@ -339,4 +394,2 @@ ```js | ||
SortedArray also provides a set of functions to perform common set operations and find statistics of any sorted array-like objects without converting them to SortedArrays. Check [API documentation](https://github.com/zandaqo/structurae/blob/master/doc/API.md) for more information. | ||
## Documentation | ||
@@ -343,0 +396,0 @@ - [API documentation](https://github.com/zandaqo/structurae/blob/master/doc/API.md) |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
64333
10
1578
380
1