From fa4d8a20e2dca8f8bd3f0b4a70fa28dfa2267a11 Mon Sep 17 00:00:00 2001 From: Alexander Rose <alexander.rose@weirdbyte.de> Date: Wed, 11 Jul 2018 19:27:41 -0700 Subject: [PATCH] added SortedRanges transient segments iterator --- src/mol-data/int/_spec/sorted-ranges.spec.ts | 72 +++++++++++++++++ src/mol-data/int/sorted-ranges.ts | 83 ++++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 src/mol-data/int/_spec/sorted-ranges.spec.ts create mode 100644 src/mol-data/int/sorted-ranges.ts diff --git a/src/mol-data/int/_spec/sorted-ranges.spec.ts b/src/mol-data/int/_spec/sorted-ranges.spec.ts new file mode 100644 index 000000000..307b3c08b --- /dev/null +++ b/src/mol-data/int/_spec/sorted-ranges.spec.ts @@ -0,0 +1,72 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import SortedRanges from '../sorted-ranges' +import OrderedSet from '../ordered-set'; +import SortedArray from '../sorted-array'; + +describe('rangesArray', () => { + function test(name: string, a: any, b: any) { + it(name, () => expect(a).toEqual(b)); + } + + function testIterator(name: string, ranges: SortedRanges, set: OrderedSet, expectedValues: { index: number[], start: number[], end: number[]}) { + it(`iterator, ${name}`, () => { + const rangesIt = SortedRanges.transientSegments(ranges, set) + const { index, start, end } = expectedValues + + let i = 0 + while (rangesIt.hasNext) { + const segment = rangesIt.move() + expect(segment.index).toBe(index[i]) + expect(segment.start).toBe(start[i]) + expect(segment.end).toBe(end[i]) + ++i + } + expect(i).toBe(index.length) + }) + } + + const a1234 = SortedRanges.ofSortedRanges([1, 2, 3, 4]); + const a1134 = SortedRanges.ofSortedRanges([1, 1, 3, 4]); + + test('size', SortedRanges.size(a1234), 4); + test('size', SortedRanges.size(a1134), 3); + + test('min/max', [SortedRanges.min(a1234), SortedRanges.max(a1234)], [1, 4]); + test('start/end', [SortedRanges.start(a1234), SortedRanges.end(a1234)], [1, 5]); + + testIterator('two ranges', + SortedRanges.ofSortedRanges([1, 2, 3, 4]), + OrderedSet.ofBounds(1, 4), + { index: [0, 1], start: [0, 2], end: [2, 4] } + ) + testIterator('first range', + SortedRanges.ofSortedRanges([1, 2, 3, 4]), + SortedArray.ofSortedArray([2]), + { index: [0], start: [0], end: [1] } + ) + testIterator('second range', + SortedRanges.ofSortedRanges([1, 2, 3, 4]), + SortedArray.ofSortedArray([4]), + { index: [1], start: [0], end: [1] } + ) + testIterator('set not in ranges', + SortedRanges.ofSortedRanges([1, 2, 3, 4]), + SortedArray.ofSortedArray([10]), + { index: [], start: [], end: [] } + ) + testIterator('set in second range and beyond', + SortedRanges.ofSortedRanges([1, 2, 3, 4]), + SortedArray.ofSortedArray([3, 10]), + { index: [1], start: [0], end: [2] } + ) + testIterator('length 1 range', + SortedRanges.ofSortedRanges([1, 1, 3, 4]), + SortedArray.ofSortedArray([0, 1, 10]), + { index: [0], start: [1], end: [2] } + ) +}); \ No newline at end of file diff --git a/src/mol-data/int/sorted-ranges.ts b/src/mol-data/int/sorted-ranges.ts new file mode 100644 index 000000000..59bbe3a0c --- /dev/null +++ b/src/mol-data/int/sorted-ranges.ts @@ -0,0 +1,83 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { Segmentation, OrderedSet, SortedArray, Interval } from '../int'; +import _Iterator from '../iterator'; + +/** Pairs of min and max indices of sorted, non-overlapping ranges */ +type SortedRanges<T extends number = number> = SortedArray<T> + +namespace SortedRanges { + export function ofSortedRanges<T extends number = number>(array: ArrayLike<T>) { return SortedArray.ofSortedArray<T>(array) } + export function start<T extends number = number>(ranges: SortedRanges<T>) { return ranges[0] } + export function end<T extends number = number>(ranges: SortedRanges<T>) { return ranges[ranges.length - 1] + 1 } + export function min<T extends number = number>(ranges: SortedRanges<T>) { return ranges[0] } + export function max<T extends number = number>(ranges: SortedRanges<T>) { return ranges[ranges.length - 1] } + export function size<T extends number = number>(ranges: SortedRanges<T>) { + let size = 0 + for(let i = 0, il = ranges.length; i < il; i += 2) { + size += ranges[i + 1] - ranges[i] + 1 + } + return size + } + + export function transientSegments<T extends number = number>(ranges: SortedRanges<T>, set: OrderedSet<T>) { + return new Iterator<T>(ranges, set) + } + + export class Iterator<T extends number = number> implements _Iterator<Segmentation.Segment<T>> { + private value: Segmentation.Segment<T> = { index: 0, start: 0 as T, end: 0 as T } + + private curIndex = 0 + private maxIndex = 0 + private interval: Interval<T> + private curMin: T = 0 as T + + hasNext: boolean = false; + + updateInterval() { + this.interval = Interval.ofRange(this.ranges[this.curIndex], this.ranges[this.curIndex + 1]) + } + + updateValue() { + this.value.index = this.curIndex / 2 + this.value.start = OrderedSet.findPredecessorIndex(this.set, this.ranges[this.curIndex]) as T + this.value.end = OrderedSet.findPredecessorIndex(this.set, this.ranges[this.curIndex + 1]) + 1 as T + } + + move() { + if (this.hasNext) { + this.updateValue() + while (this.curIndex <= this.maxIndex) { + this.curIndex += 2 + this.curMin = Interval.end(this.interval) + this.updateInterval() + if (Interval.min(this.interval) >= this.curMin && OrderedSet.areIntersecting(this.interval, this.set)) break + } + this.hasNext = this.curIndex <= this.maxIndex + } + return this.value; + } + + getRangeIndex(value: number) { + const index = SortedArray.findPredecessorIndex(this.ranges, value) + return (index % 2 === 1) ? index - 1 : index + } + + constructor(private ranges: SortedRanges<T>, private set: OrderedSet<T>) { + if (ranges.length) { + this.curIndex = this.getRangeIndex(OrderedSet.min(set)) + this.maxIndex = Math.min(ranges.length - 2, this.getRangeIndex(OrderedSet.max(set))) + this.curMin = this.ranges[this.curIndex] + this.updateInterval() + } + + this.hasNext = ranges.length > 0 && this.curIndex <= this.maxIndex + } + } +} + +export default SortedRanges \ No newline at end of file -- GitLab