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