From a3e14bf579b82fa5ceabed42b8b4d55f4666e459 Mon Sep 17 00:00:00 2001
From: Alexander Rose <alex.rose@rcsb.org>
Date: Fri, 26 Jul 2019 12:44:08 -0700
Subject: [PATCH] improved sorted-ranges and docs

---
 src/mol-data/int/impl/sorted-array.ts        |  5 ++
 src/mol-data/int/ordered-set.ts              |  5 ++
 src/mol-data/int/sorted-ranges.ts            | 81 ++++++++++++--------
 src/mol-model/structure/structure/element.ts |  2 +
 4 files changed, 60 insertions(+), 33 deletions(-)

diff --git a/src/mol-data/int/impl/sorted-array.ts b/src/mol-data/int/impl/sorted-array.ts
index faa32048f..8c81e7940 100644
--- a/src/mol-data/int/impl/sorted-array.ts
+++ b/src/mol-data/int/impl/sorted-array.ts
@@ -65,6 +65,11 @@ export function areEqual(a: Nums, b: Nums) {
     return true;
 }
 
+/**
+ * Returns 0 if `v` is smaller or equal the first element of `xs`
+ * Returns length of `xs` if `v` is bigger than the last element of `xs`
+ * Otherwise returns the first index where the value of `xs` is equal or bigger than `v`
+ */
 export function findPredecessorIndex(xs: Nums, v: number) {
     const len = xs.length;
     if (v <= xs[0]) return 0;
diff --git a/src/mol-data/int/ordered-set.ts b/src/mol-data/int/ordered-set.ts
index 3b7d03d39..d8e7f705d 100644
--- a/src/mol-data/int/ordered-set.ts
+++ b/src/mol-data/int/ordered-set.ts
@@ -41,6 +41,11 @@ namespace OrderedSet {
     /** Returns elements of `a` that are not in `b`, i.e `a` - `b` */
     export const subtract: <T extends number = number>(a: OrderedSet<T>, b: OrderedSet<T>) => OrderedSet<T> = Base.subtract as any;
 
+    /**
+     * Returns 0 if `x` is smaller or equal the first element of `set`
+     * Returns length of `set` if `x` is bigger than the last element of `set`
+     * Otherwise returns the first index where the value of `set` is equal or bigger than `x`
+     */
     export const findPredecessorIndex: <T extends number = number>(set: OrderedSet<T>, x: number) => number = Base.findPredecessorIndex as any;
     export const findPredecessorIndexInInterval: <T extends number = number>(set: OrderedSet<T>, x: T, range: Interval) => number = Base.findPredecessorIndexInInterval as any;
     export const findRange: <T extends number = number>(set: OrderedSet<T>, min: T, max: T) => Interval = Base.findRange as any;
diff --git a/src/mol-data/int/sorted-ranges.ts b/src/mol-data/int/sorted-ranges.ts
index 18b7c6b64..754946a5b 100644
--- a/src/mol-data/int/sorted-ranges.ts
+++ b/src/mol-data/int/sorted-ranges.ts
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -23,6 +23,46 @@ namespace SortedRanges {
         }
         return size
     }
+    export function count<T extends number = number>(ranges: SortedRanges<T>) { return ranges.length / 2 }
+
+    export function startAt<T extends number = number>(ranges: SortedRanges<T>, index: number) {
+        return ranges[index * 2]
+    }
+    export function endAt<T extends number = number>(ranges: SortedRanges<T>, index: number) {
+        return ranges[index * 2 + 1] + 1
+    }
+
+    export function minAt<T extends number = number>(ranges: SortedRanges<T>, index: number) {
+        return ranges[index * 2]
+    }
+    export function maxAt<T extends number = number>(ranges: SortedRanges<T>, index: number) {
+        return ranges[index * 2 + 1]
+    }
+
+    /** Returns if a value of `set` is included in `ranges` */
+    export function has<T extends number = number>(ranges: SortedRanges<T>, set: OrderedSet<T>) {
+        return firstIntersectionIndex(ranges, set) !== -1
+    }
+
+    /** Returns if a value of `set` is included in `ranges` from given index */
+    export function hasFrom<T extends number = number>(ranges: SortedRanges<T>, set: OrderedSet<T>, from: number) {
+        return firstIntersectionIndexFrom(ranges, set, from) !== -1
+    }
+
+    export function firstIntersectionIndex<T extends number = number>(ranges: SortedRanges<T>, set: OrderedSet<T>): number {
+        return firstIntersectionIndexFrom(ranges, set, 0)
+    }
+
+    export function firstIntersectionIndexFrom<T extends number = number>(ranges: SortedRanges<T>, set: OrderedSet<T>, from: number): number {
+        if (minAt(ranges, from) > OrderedSet.max(set) || max(ranges) < OrderedSet.min(set)) return -1
+
+        for (let i = from, il = count(ranges); i < il; ++i) {
+            const interval = Interval.ofRange(minAt(ranges, i), maxAt(ranges, i))
+            if (OrderedSet.areIntersecting(interval, set)) return i
+        }
+
+        return -1
+    }
 
     export function transientSegments<T extends number = number, I extends number = number>(ranges: SortedRanges<T>, set: OrderedSet<T>) {
         return new Iterator<T, I>(ranges, set)
@@ -32,52 +72,27 @@ namespace SortedRanges {
         private value: Segmentation.Segment<I> = { index: 0 as I, 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;
 
-        private updateInterval() {
-            this.interval = Interval.ofRange(this.ranges[this.curIndex], this.ranges[this.curIndex + 1])
-        }
-
         private updateValue() {
-            this.value.index = this.curIndex / 2 as I
-            this.value.start = OrderedSet.findPredecessorIndex(this.set, this.ranges[this.curIndex])
-            this.value.end = OrderedSet.findPredecessorIndex(this.set, this.ranges[this.curIndex + 1])
+            this.value.index = this.curIndex as I
+            this.value.start = OrderedSet.findPredecessorIndex(this.set, startAt(this.ranges, this.curIndex))
+            this.value.end = OrderedSet.findPredecessorIndex(this.set, endAt(this.ranges, this.curIndex))
         }
 
         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
+                this.curIndex = firstIntersectionIndexFrom(this.ranges, this.set, this.curIndex + 1)
+                this.hasNext = this.curIndex !== -1
             }
             return this.value;
         }
 
-        private 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>) {
-            const min = OrderedSet.min(set)
-            const max = OrderedSet.max(set)
-            const b = ranges.length > 0 && ranges[0] <= max && ranges[ranges.length - 1] >= min
-            if (b) {
-                this.curIndex = this.getRangeIndex(min)
-                this.maxIndex = Math.min(ranges.length - 2, this.getRangeIndex(max) + 2)
-                this.curMin = ranges[this.curIndex]
-                this.updateInterval()
-            }
-            this.hasNext = b && this.curIndex <= this.maxIndex
+            this.curIndex = firstIntersectionIndex(ranges, set)
+            this.hasNext = this.curIndex !== -1
         }
     }
 }
diff --git a/src/mol-model/structure/structure/element.ts b/src/mol-model/structure/structure/element.ts
index 80096d969..39ef7d4e0 100644
--- a/src/mol-model/structure/structure/element.ts
+++ b/src/mol-model/structure/structure/element.ts
@@ -166,6 +166,7 @@ namespace StructureElement {
             return Loci(structure, elements);
         }
 
+        /** Create union of `xs` and `ys` */
         export function union(xs: Loci, ys: Loci): Loci {
             if (xs.elements.length > ys.elements.length) return union(ys, xs);
             if (xs.elements.length === 0) return ys;
@@ -191,6 +192,7 @@ namespace StructureElement {
             return Loci(xs.structure, elements);
         }
 
+        /** Subtract `ys` from `xs` */
         export function subtract(xs: Loci, ys: Loci): Loci {
             const map = new Map<number, OrderedSet<UnitIndex>>();
             for (const e of ys.elements) map.set(e.unit.id, e.indices);
-- 
GitLab