graphinius
Version:
Generic graph library in Typescript
279 lines (223 loc) • 6.45 kB
text/typescript
export enum BinaryHeapMode {
MIN,
MAX
}
export interface PositionHeapEntry {
score: number;
position: number;
}
export interface IBinaryHeap {
// Helper methods
getMode(): BinaryHeapMode;
getArray(): Array<any>;
size(): number;
getEvalPriorityFun(): Function;
evalInputScore(obj: any): number;
getEvalObjIDFun(): Function;
evalInputObjID(obj: any): any;
// Actual heap operations
insert(obj: any): void;
// reInsert(obj: any): void;
remove(obj: any): any;
peek(): any;
pop(): any;
find(obj: any): any;
// Just temporarily, for debugging
getPositions(): any;
}
/**
* We only support unique object ID's for now !!!
* @TODO Rename into "ObjectBinaryHeap" or such...
*/
class BinaryHeap implements IBinaryHeap {
_nr_removes : number = 0; // just for debugging
private _array = [];
private _positions: { [id: string]: PositionHeapEntry } = {};
/**
* Mode of a min heap should only be set upon
* instantiation and never again afterwards...
* @param _mode MIN or MAX heap
* @param _evalObjID function to determine an object's identity
* @param _evalPriority function to determine an objects score
*/
constructor(private _mode = BinaryHeapMode.MIN,
private _evalPriority = (obj: any): number => {
if (typeof obj !== 'number' && typeof obj !== 'string') {
return NaN;
}
if (typeof obj === 'number') {
return obj | 0;
}
return parseInt(obj);
},
private _evalObjID = (obj: any): any => {
return obj;
}
) {
}
getMode(): BinaryHeapMode {
return this._mode;
}
getArray(): Array<any> {
return this._array;
}
getPositions() {
return this._positions;
}
size(): number {
return this._array.length;
}
getEvalPriorityFun(): Function {
return this._evalPriority;
}
evalInputScore(obj: any): number {
return this._evalPriority(obj);
}
getEvalObjIDFun(): Function {
return this._evalObjID;
}
evalInputObjID(obj: any): any {
return this._evalObjID(obj);
}
peek(): any {
return this._array[0];
}
pop() {
if (this.size()) {
return this.remove(this._array[0]);
}
}
find(obj: any): any {
let pos = this.getNodePosition(obj);
return this._array[pos];
}
/**
* Insert - Adding an object to the heap
* @param obj the obj to add to the heap
* @returns {number} the objects index in the internal array
*/
insert(obj: any) {
if (isNaN(this._evalPriority(obj))) {
throw new Error("Cannot insert object without numeric priority.")
}
/**
* @todo if we keep the unique ID stuff, check for it here and throw an Error if needed...
*/
this._array.push(obj);
this.setNodePosition(obj, this.size() - 1);
this.trickleUp(this.size() - 1);
}
remove(obj: any): any {
this._nr_removes++;
if (isNaN(this._evalPriority(obj))) {
throw new Error('Object invalid.');
}
let pos = this.getNodePosition(obj),
found = this._array[pos] != null ? this._array[pos] : null;
if (found === null) {
return undefined;
}
let last_array_obj = this._array.pop();
this.removeNodePosition(obj);
if ( this.size() && found !== last_array_obj ) {
this._array[pos] = last_array_obj;
this.setNodePosition(last_array_obj, pos);
this.trickleUp(pos);
this.trickleDown(pos);
}
return found;
}
private trickleDown(i: number) {
let parent = this._array[i];
while (true) {
let right_child_idx = (i + 1) * 2,
left_child_idx = right_child_idx - 1,
right_child = this._array[right_child_idx],
left_child = this._array[left_child_idx],
swap = null;
// check if left child exists && is larger than parent
if (left_child_idx < this.size() && !this.orderCorrect(parent, left_child)) {
swap = left_child_idx;
}
// check if right child exists && is larger than parent
if (right_child_idx < this.size() && !this.orderCorrect(parent, right_child)
&& !this.orderCorrect(left_child, right_child)) {
swap = right_child_idx;
}
if (swap === null) {
break;
}
// we only have to swap one child, doesn't matter which one
this._array[i] = this._array[swap];
this._array[swap] = parent;
// console.log(`Trickle down: swapping ${this._array[i]} and ${this._array[swap]}`);
this.setNodePosition(this._array[i], i);
this.setNodePosition(this._array[swap], swap);
i = swap;
}
}
private trickleUp(i: number) {
let child = this._array[i];
// Can only trickle up from positive levels
while (i) {
let parent_idx = Math.floor((i + 1) / 2) - 1,
parent = this._array[parent_idx];
if (this.orderCorrect(parent, child)) {
break;
}
else {
this._array[parent_idx] = child;
this._array[i] = parent;
// console.log(`Trickle up: swapping ${child} and ${parent}`);
this.setNodePosition(child, parent_idx);
this.setNodePosition(parent, i);
i = parent_idx;
}
}
}
private orderCorrect(obj_a, obj_b) {
let obj_a_pr = this._evalPriority(obj_a);
let obj_b_pr = this._evalPriority(obj_b);
if (this._mode === BinaryHeapMode.MIN) {
return obj_a_pr <= obj_b_pr;
}
else {
return obj_a_pr >= obj_b_pr;
}
}
/**
* Superstructure to enable search in BinHeap in O(1)
* @param obj
* @param pos
*/
private setNodePosition(obj: any, pos: number) : void {
if ( obj == null || pos == null || pos !== (pos|0) ) {
throw new Error('minium required arguments are obj and new_pos');
}
let pos_obj: PositionHeapEntry = {
score: this.evalInputScore(obj),
position: pos
};
let obj_key = this.evalInputObjID(obj);
this._positions[obj_key] = pos_obj;
}
/**
*
*/
private getNodePosition(obj: any) : number {
let obj_key = this.evalInputObjID(obj);
// console.log(obj_key);
let occurrence : PositionHeapEntry = this._positions[obj_key];
// console.log(occurrence);
return occurrence ? occurrence.position : null;
}
/**
* @param obj
* @returns {number}
*/
private removeNodePosition(obj: any) : void {
let obj_key = this.evalInputObjID(obj);
delete this._positions[obj_key];
}
}
export { BinaryHeap };