/** * Code generated with this prompt by Cursor: * * I want to build a class to manage ranges * * I can add ranges to it with a start& an end (both integer, end > start). It should store those ranges efficently. * * When several ranges overlap, eg [1, 100] and [30, 50], I want the class to split the range into non-overlapping ranges, and add a "ref counter" to the ranges. For example, [1, 30], [30, 50] * 2, [50, 100] * * I also want to be able to remove ranges, it will decrease the ref counter or remove the range altogether. I can only remove ranges at existing boundaries. For example, with the [1, 30], [30, 50] * 2, [50, 100] configuration * * - removing [1, 100] => the only range remaning is [30, 50] * - removing [2, 50] => error, because "2' is not a boundary * - removing [30, 50] => [1, 30], [30, 50], [50, 100] (do not "merge" the ranges back together) * * I want to be able to associate data to each range. And I want to be able to get the ranges inside boundaries. For example , with [1, 30], [30, 50] * 2, [50, 100] configuration * * - getting [30, 100] => I receive [30, 50] * 2, [50, 100], and I can get / modify the data assocaited to each range by accessing their data prop. Note the "*2" is just the ref counter, there is onlly one range object for the interval returned * - getting [2, 50] => I get [30, 50] * 2 * * ---- * * Could optimize with binary search, but the ranges we want to handle are not that many. */ interface Range { start: number; end: number; refCount: number; data: T | null; } export class RangeList { private ranges: Range[] = []; /** * Add a range to the list. If it overlaps with existing ranges, * it will split them and increment reference counts accordingly. */ add(start: number, end: number): void { if (end <= start) { throw new TypeError("End must be greater than start"); } // Find all ranges that overlap with the new range const overlappingRanges: { index: number; range: Range }[] = []; for (let i = 0; i < this.ranges.length; i++) { const range = this.ranges[i]; if (start < range.end && end > range.start) { overlappingRanges.push({ index: i, range }); } if (range.data !== null) { throw new Error("Overlapping range already has data"); } } if (overlappingRanges.length === 0) { // No overlaps, just add the new range this.ranges.push({ start, end, refCount: 1, data: null }); this.ranges.sort((a, b) => a.start - b.start); return; } // Handle overlaps by splitting ranges const newRanges: Range[] = []; let currentPos = start; for (let i = 0; i < overlappingRanges.length; i++) { const { range } = overlappingRanges[i]; // Add range before overlap if exists if (currentPos < range.start) { newRanges.push({ start: currentPos, end: range.start, refCount: 1, data: null, }); } else if (range.start < currentPos) { newRanges.push({ start: range.start, end: currentPos, refCount: range.refCount, data: null, }); } // Add overlapping part with increased ref count newRanges.push({ start: Math.max(currentPos, range.start), end: Math.min(end, range.end), refCount: range.refCount + 1, data: null, }); // Add remaining part of existing range if exists if (range.end > end) { newRanges.push({ start: end, end: range.end, refCount: range.refCount, data: null, }); } currentPos = Math.max(currentPos, range.end); } // Add remaining part after last overlap if exists if (currentPos < end) { newRanges.push({ start: currentPos, end, refCount: 1, data: null, }); } // Remove old overlapping ranges and insert new ones const firstIndex = overlappingRanges[0].index; const lastIndex = overlappingRanges[overlappingRanges.length - 1].index; this.ranges.splice(firstIndex, lastIndex - firstIndex + 1, ...newRanges); this.ranges.sort((a, b) => a.start - b.start); } /** * Remove a range from the list. The range must start and end at existing boundaries. */ remove(start: number, end: number): void { if (end <= start) { throw new TypeError("End must be greater than start"); } // Find ranges that need to be modified const affectedRanges: { index: number; range: Range }[] = []; for (let i = 0; i < this.ranges.length; i++) { const range = this.ranges[i]; if (start < range.end && end > range.start) { affectedRanges.push({ index: i, range }); } } if (affectedRanges.length === 0) { throw new Error("No ranges found to remove"); } // Verify boundaries match if (start !== affectedRanges[0].range.start || end !== affectedRanges[affectedRanges.length - 1].range.end) { throw new Error("Range boundaries must match existing boundaries"); } // Todo: also check if there's a gap in the middle but it should not happen with our usage for (let i = 0; i < affectedRanges.length; i++) { const { range } = affectedRanges[i]; range.refCount--; } this.ranges = this.ranges.filter((range) => range.refCount > 0); } /** * Get all ranges within the specified boundaries. */ getRanges(start: number, end: number): Range[] { if (end <= start) { throw new TypeError("End must be greater than start"); } return this.ranges.filter((range) => start < range.end && end > range.start); } /** * Get all ranges in the list */ getAllRanges(): Range[] { return [...this.ranges]; } }