Skip to content

Instantly share code, notes, and snippets.

@ardeshireshghi
Last active November 1, 2023 03:37
Show Gist options
  • Save ardeshireshghi/7f720021d1896070ef7cd929dca643af to your computer and use it in GitHub Desktop.
Save ardeshireshghi/7f720021d1896070ef7cd929dca643af to your computer and use it in GitHub Desktop.
This is a simple implementation of a sorted set based on score and unique values
function findScoreIndexOrClosest<T>(
arr: [score: number, value: T][],
score: number,
): [matched: boolean, index: number] {
if (arr.length === 1) {
return arr[0][0] === score
? [true, 0]
: arr[0][0] > score
? [false, 0]
: [false, 1];
}
let low = 0;
let high = arr.length - 1;
let mid = 0;
while (low <= high) {
mid = Math.floor((low + high) / 2);
if (arr[mid][0] < score) {
low = mid + 1;
} else if (arr[mid][0] > score) {
high = mid - 1;
} else {
return [true, mid];
}
}
return [false, score > arr[mid]?.[0] ? mid + 1 : mid];
}
type ScoreSortedSetSettings = {
updateExistingValues?: boolean;
updateMode?: 'GT' | 'LT'; // GT = Greater Than current score, LT = Less Than current score
};
const defaultOptions: ScoreSortedSetSettings = {
updateExistingValues: true,
updateMode: 'GT',
};
export class ScoreSortedSet<T = any> {
private _size: number = 0;
private _values: Map<T, number> = new Map();
private _scoreValuePairs: [score: number, value: T][] = [];
private settings: ScoreSortedSetSettings;
constructor(settings: ScoreSortedSetSettings = {}) {
this.settings = { ...defaultOptions, ...settings };
}
add(score: number, value: T): this {
const currentIndex = this._values.get(value);
const isValueExists = currentIndex !== undefined;
if (isValueExists) {
// Don't add the value if it already exists and the updateExistingValues is false
if (!this.settings.updateExistingValues) {
return this;
}
// check if based on the updateMode, the value should be updated
if (!this._shouldUpdateExistingValue(score, currentIndex)) {
return this;
}
this.delete(value, currentIndex);
}
const [_, closestIndex] = findScoreIndexOrClosest(
this._scoreValuePairs,
score,
);
this._scoreValuePairs.splice(closestIndex, 0, [score, value]);
this._values.set(value, closestIndex);
this._size += 1;
return this;
}
get(value: T): undefined | T {
const indexOfValue = this._values.get(value);
return indexOfValue !== undefined ? this._scoreValuePairs[indexOfValue][1] : undefined;
}
clear(): void {
this._size = 0;
this._values.clear();
this._scoreValuePairs = [];
}
/**
* Removes a specified value from the Set.
* @returns Returns true if an element in the Set existed and has been removed, or false if the element does not exist.
*/
delete(value: T, currentIndex?: number): boolean {
const indexOfTheValue = currentIndex ?? this._values.get(value);
if (indexOfTheValue === undefined) {
return false;
}
this._scoreValuePairs.splice(indexOfTheValue, 1);
this._values.delete(value);
this._size -= 1;
return true;
}
/**
* Executes a provided function once per each value in the Set object, in insertion order.
*/
forEach(
callbackfn: (value: T, index: number, set: ScoreSortedSet<T>) => void,
thisArg?: any,
): void {
this._scoreValuePairs.forEach(([_, value], index) => {
callbackfn.call(thisArg, value, index, this);
});
}
/**
* @returns a boolean indicating whether an element with the specified value exists in the Set or not.
*/
has(value: T): boolean {
const currentRank = this._values.get(value);
return !!currentRank;
}
values(): [score: number, value: T][] {
return this._scoreValuePairs;
}
valuesRangeByScore(
lowScore: number,
highScore: number,
inclusive: boolean = true,
): [score: number, value: T][] {
const [hasValueWithLowScore, lowIndex] = findScoreIndexOrClosest(
this.values(),
lowScore,
);
const low = hasValueWithLowScore && !inclusive ? lowIndex + 1 : lowIndex;
let i = low;
const result: [score: number, value: T][] = [];
while (
this._scoreValuePairs[i]?.[0] <= highScore &&
i < this._scoreValuePairs.length
) {
if (!inclusive && this._scoreValuePairs[i][0] === highScore) {
break;
}
result.push(this._scoreValuePairs[i]);
i += 1;
}
return result;
}
/**
* @returns the number of (unique) elements in Set.
*/
get size() {
return this._size;
}
protected _shouldUpdateExistingValue(
score: number,
currentIndex: number,
): boolean {
const [currentScore] = this._scoreValuePairs[currentIndex];
const shouldUpdate =
this.settings.updateMode === 'GT'
? currentScore < score
: currentScore > score;
return shouldUpdate;
}
}
// Example usage:
// const set = new ScoreSortedSet({
// updateExistingValues: true,
// updateMode: 'GT'
//});
// set.add(1, 'foo');
// set.add(4, 'bar');
// set.add(2, 'ping');
// set.add(3, 'foo');
// set.add(10, 'pong');
// set.add(9, 'pong');
// console.log(set.values(), set.size)
// Output:
// [[2, "ping"], [3, "foo"], [4, "bar"], [10, "pong"]], 4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment