Skip to main content

mapWithHistory

/**
* MapWithHistory stores string values by key across timestamps.
*
* - set(key, value, timestamp): appends a new historical value for key.
* - get(key, timestamp): returns value for greatest stored timestamp <= query,
* or '' when none exists.
*
* Timestamps for the same key are guaranteed strictly increasing.
*/
export default class MapWithHistory {
constructor() {
/** @type {Map<string, Array<{timestamp: number, value: string}>>} */
this.store = new Map();
}

/**
* @param {string} key
* @param {string} value
* @param {number} timestamp
* @returns {void}
*/
set(key, value, timestamp) {
if (!this.store.has(key)) {
this.store.set(key, []);
}

// Strictly increasing timestamps per key means append is valid.
this.store.get(key).push({ timestamp, value });
}

/**
* @param {string} key
* @param {number} timestamp
* @returns {string}
*/
get(key, timestamp) {
const history = this.store.get(key);

if (!history || history.length === 0) {
return '';
}

// Binary search for rightmost entry with entry.timestamp <= timestamp.
let left = 0;
let right = history.length - 1;
let bestIndex = -1;

while (left <= right) {
const mid = left + Math.floor((right - left) / 2);
const midTimestamp = history[mid].timestamp;

if (midTimestamp <= timestamp) {
bestIndex = mid;
left = mid + 1;
} else {
right = mid - 1;
}
}

return bestIndex === -1 ? '' : history[bestIndex].value;
}
}

/**
* Test
***
const mapWithHistory = new MapWithHistory();

mapWithHistory.set('language', 'JavaScript', 1);
mapWithHistory.set('language', 'TypeScript', 4);

mapWithHistory.get('language', 1);
// 'JavaScript'

mapWithHistory.get('language', 3);
// 'JavaScript'

mapWithHistory.get('language', 4);
// 'TypeScript'

mapWithHistory.get('language', 10);
// 'TypeScript'
*/