| // Copyright 2016 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import * as Platform from '../platform/platform.js'; |
| |
| export class Segment<T> { |
| begin: number; |
| end: number; |
| data: T; |
| |
| constructor(begin: number, end: number, data: T) { |
| if (begin > end) { |
| throw new Error('Invalid segment'); |
| } |
| this.begin = begin; |
| this.end = end; |
| this.data = data; |
| } |
| |
| intersects(that: Segment<T>): boolean { |
| return this.begin < that.end && that.begin < this.end; |
| } |
| } |
| |
| export class SegmentedRange<T> { |
| #segments: Array<Segment<T>> = []; |
| readonly #mergeCallback: ((arg0: Segment<T>, arg1: Segment<T>) => Segment<T>| null)|undefined; |
| |
| constructor(mergeCallback?: ((arg0: Segment<T>, arg1: Segment<T>) => Segment<T>| null)) { |
| this.#mergeCallback = mergeCallback; |
| } |
| |
| append(newSegment: Segment<T>): void { |
| // 1. Find the proper insertion point for new segment |
| let startIndex = Platform.ArrayUtilities.lowerBound(this.#segments, newSegment, (a, b) => a.begin - b.begin); |
| let endIndex = startIndex; |
| let merged: (Segment<T>|null)|null = null; |
| if (startIndex > 0) { |
| // 2. Try mering the preceding segment |
| const precedingSegment = this.#segments[startIndex - 1]; |
| merged = this.tryMerge(precedingSegment, newSegment); |
| if (merged) { |
| --startIndex; |
| newSegment = merged; |
| } else if (this.#segments[startIndex - 1].end >= newSegment.begin) { |
| // 2a. If merge failed and segments overlap, adjust preceding segment. |
| // If an old segment entirely contains new one, split it in two. |
| if (newSegment.end < precedingSegment.end) { |
| this.#segments.splice( |
| startIndex, 0, new Segment<T>(newSegment.end, precedingSegment.end, precedingSegment.data)); |
| } |
| precedingSegment.end = newSegment.begin; |
| } |
| } |
| // 3. Consume all segments that are entirely covered by the new one. |
| while (endIndex < this.#segments.length && this.#segments[endIndex].end <= newSegment.end) { |
| ++endIndex; |
| } |
| // 4. Merge or adjust the succeeding segment if it overlaps. |
| if (endIndex < this.#segments.length) { |
| merged = this.tryMerge(newSegment, this.#segments[endIndex]); |
| if (merged) { |
| endIndex++; |
| newSegment = merged; |
| } else if (newSegment.intersects(this.#segments[endIndex])) { |
| this.#segments[endIndex].begin = newSegment.end; |
| } |
| } |
| this.#segments.splice(startIndex, endIndex - startIndex, newSegment); |
| } |
| |
| segments(): Array<Segment<T>> { |
| return this.#segments; |
| } |
| |
| private tryMerge(first: Segment<T>, second: Segment<T>): Segment<T>|null { |
| const merged = this.#mergeCallback && this.#mergeCallback(first, second); |
| if (!merged) { |
| return null; |
| } |
| merged.begin = first.begin; |
| merged.end = Math.max(first.end, second.end); |
| return merged; |
| } |
| } |