diff --git a/src/mol-base/collections/integer/impl/ordered-set.ts b/src/mol-base/collections/integer/impl/ordered-set.ts index 050a41ca0feca76be263318a5845da8a0a5eb008..a3c80de0ec11edab5d57c6a5f31d3a365f0a346e 100644 --- a/src/mol-base/collections/integer/impl/ordered-set.ts +++ b/src/mol-base/collections/integer/impl/ordered-set.ts @@ -46,7 +46,7 @@ export function areIntersecting(a: OrderedSetImpl, b: OrderedSetImpl) { if (I.is(b)) return I.areIntersecting(a, b); return areIntersectingSI(b, a); } else if (I.is(b)) return areIntersectingSI(a, b); - return areIntersectingSS(a, b); + return S.areIntersecting(a, b); } /** Check if the 2nd argument is a subset of the 1st */ @@ -55,7 +55,7 @@ export function isSubset(a: OrderedSetImpl, b: OrderedSetImpl) { if (I.is(b)) return I.isSubInterval(a, b); return isSubsetIS(a, b); } else if (I.is(b)) return isSubsetSI(a, b); - return isSubsetSS(a, b); + return S.isSubset(a, b); } export function findPredecessorIndex(set: OrderedSetImpl, x: number) { @@ -75,7 +75,7 @@ export function union(a: OrderedSetImpl, b: OrderedSetImpl) { if (I.is(b)) return unionII(a, b); return unionSI(b, a); } else if (I.is(b)) return unionSI(a, b); - return unionSS(a, b); + return ofSortedArray(S.union(a, b)); } export function intersect(a: OrderedSetImpl, b: OrderedSetImpl) { @@ -83,7 +83,7 @@ export function intersect(a: OrderedSetImpl, b: OrderedSetImpl) { if (I.is(b)) return I.intersect(a, b); return intersectSI(b, a); } else if (I.is(b)) return intersectSI(a, b); - return intersectSS(a, b); + return ofSortedArray(S.intersect(a, b)); } export function subtract(a: OrderedSetImpl, b: OrderedSetImpl) { @@ -91,26 +91,7 @@ export function subtract(a: OrderedSetImpl, b: OrderedSetImpl) { if (I.is(b)) return subtractII(a, b); return subtractIS(a, b); } else if (I.is(b)) return subtractSI(a, b); - return subtractSS(a, b); -} - -const _maxIntRangeRet = { startI: 0, startJ: 0, endI: 0, endJ: 0 }; -// for small sets, just gets the whole range, for large sets does a bunch of binary searches -function getSuitableIntersectionRange(a: S, b: S) { - const la = a.length, lb = b.length; - const ratio = la / lb; - if (ratio <= 0.5 || ratio >= 2 || (la >= 128 && lb >= 128)) { - _maxIntRangeRet.startI = S.findPredecessorIndex(a, S.start(b)); - _maxIntRangeRet.startJ = S.findPredecessorIndex(b, S.start(a)); - _maxIntRangeRet.endI = S.findPredecessorIndex(a, S.end(b)); - _maxIntRangeRet.endJ = S.findPredecessorIndex(b, S.end(a)); - } else { - _maxIntRangeRet.startI = 0; - _maxIntRangeRet.startJ = 0; - _maxIntRangeRet.endI = la; - _maxIntRangeRet.endJ = lb; - } - return _maxIntRangeRet; + return ofSortedArray(S.subtract(a, b)); } function areEqualIS(a: I, b: S) { return I.size(a) === S.size(b) && I.start(a) === S.start(b) && I.end(a) === S.end(b); } @@ -119,19 +100,6 @@ function areIntersectingSI(a: S, b: I) { return areRangesIntersecting(a, b); } -function areIntersectingSS(a: S, b: S) { - if (a === b) return true; - - let { startI: i, startJ: j, endI, endJ } = getSuitableIntersectionRange(a, b); - while (i < endI && j < endJ) { - const x = a[i], y = b[j]; - if (x < y) { i++; } - else if (x > y) { j++; } - else return true; - } - return false; -} - function isSubsetSI(a: S, b: I) { const minB = I.min(b), maxB = I.max(b); if (maxB - minB + 1 === 0) return true; @@ -148,24 +116,6 @@ function isSubsetIS(a: I, b: S) { return minB >= minA && maxA <= maxB; } -function isSubsetSS(a: S, b: S) { - if (a === b) return true; - - const lenB = b.length; - let { startI: i, startJ: j, endI, endJ } = getSuitableIntersectionRange(a, b); - // must be able to advance by lenB elements - if (endJ - j < lenB || endI - i < lenB) return false; - - let equal = 0; - while (i < endI && j < endJ) { - const x = a[i], y = b[j]; - if (x < y) { i++; } - else if (x > y) { j++; } - else { i++; j++; equal++; } - } - return equal === lenB; -} - function areRangesIntersecting(a: OrderedSetImpl, b: OrderedSetImpl) { const sa = size(a), sb = size(b); if (sa === 0 && sb === 0) return true; @@ -213,56 +163,6 @@ function unionSI(a: S, b: I) { return ofSortedArray(indices); } -function unionSS(a: S, b: S) { - if (a === b) return a; - - const { startI: sI, startJ: sJ, endI, endJ } = getSuitableIntersectionRange(a, b); - let i = sI, j = sJ; - let commonCount = 0; - while (i < endI && j < endJ) { - const x = a[i], y = b[j]; - if (x < y) { i++; } - else if (x > y) { j++; } - else { i++; j++; commonCount++; } - } - - const lenA = a.length, lenB = b.length; - // A === B || B is subset of A ==> A - if ((commonCount === lenA && commonCount === lenB) || commonCount === lenB) return a; - // A is subset of B ===> B - if (commonCount === lenA) return b; - - const resultSize = lenA + lenB - commonCount; - const l = Math.min(a[0], b[0]), r = Math.max(a[lenA - 1], b[lenB - 1]); - // is this just a range? - if (resultSize === r - l + 1) { - return I.ofRange(l, r); - } - - const indices = new Int32Array(lenA + lenB - commonCount); - let offset = 0; - - // insert the "prefixes" - for (let k = 0; k < sI; k++) indices[offset++] = a[k]; - for (let k = 0; k < sJ; k++) indices[offset++] = b[k]; - - // insert the common part - i = sI; - j = sJ; - while (i < endI && j < endJ) { - const x = a[i], y = b[j]; - if (x < y) { indices[offset++] = x; i++; } - else if (x > y) { indices[offset++] = y; j++; } - else { indices[offset++] = x; i++; j++; } - } - - // insert the "tail" - for (; i < lenA; i++) indices[offset++] = a[i]; - for (; j < lenB; j++) indices[offset++] = b[j]; - - return ofSortedArray(indices); -} - function intersectSI(a: S, b: I) { if (!I.size(b)) return Empty; @@ -279,41 +179,6 @@ function intersectSI(a: S, b: I) { return ofSortedArray(indices); } -function intersectSS(a: S, b: S) { - if (a === b) return a; - - const { startI: sI, startJ: sJ, endI, endJ } = getSuitableIntersectionRange(a, b); - let i = sI, j = sJ; - let commonCount = 0; - while (i < endI && j < endJ) { - const x = a[i], y = b[j]; - if (x < y) { i++; } - else if (x > y) { j++; } - else { i++; j++; commonCount++; } - } - - const lenA = a.length, lenB = b.length; - // no common elements - if (!commonCount) return Empty; - // A === B || B is subset of A ==> B - if ((commonCount === lenA && commonCount === lenB) || commonCount === lenB) return b; - // A is subset of B ==> A - if (commonCount === lenA) return a; - - const indices = new Int32Array(commonCount); - let offset = 0; - i = sI; - j = sJ; - while (i < endI && j < endJ) { - const x = a[i], y = b[j]; - if (x < y) { i++; } - else if (x > y) { j++; } - else { indices[offset++] = x; i++; j++; } - } - - return ofSortedArray(indices); -} - function subtractII(a: I, b: I) { if (I.areEqual(a, b)) return Empty; if (!I.areIntersecting(a, b)) return a; @@ -387,44 +252,4 @@ function subtractIS(a: I, b: S) { } for (let i = last + 1; i <= max; i++) ret[offset++] = i; return ofSortedArray(ret); -} - -function subtractSS(a: S, b: S) { - if (a === b) return Empty; - - const lenA = a.length; - const { startI: sI, startJ: sJ, endI, endJ } = getSuitableIntersectionRange(a, b); - let i = sI, j = sJ; - let commonCount = 0; - while (i < endI && j < endJ) { - const x = a[i], y = b[j]; - if (x < y) { i++; } - else if (x > y) { j++; } - else { i++; j++; commonCount++; } - } - - // A isnt intersecting B ===> A - if (!commonCount) return a; - // A === B || A is subset of B ===> Empty - if (commonCount >= lenA) return Empty; - - const indices = new Int32Array(lenA - commonCount); - let offset = 0; - - // insert the "prefix" - for (let k = 0; k < sI; k++) indices[offset++] = a[k]; - - i = sI; - j = sJ; - while (i < endI && j < endJ) { - const x = a[i], y = b[j]; - if (x < y) { indices[offset++] = x; i++; } - else if (x > y) { j++; } - else { i++; j++; } - } - - // insert the "tail" - for (; i < lenA; i++) indices[offset++] = a[i]; - - return ofSortedArray(indices); } \ No newline at end of file diff --git a/src/mol-base/collections/integer/impl/sorted-array.ts b/src/mol-base/collections/integer/impl/sorted-array.ts index 1a6351b2532745a36bd5a740ebc5b309550a2339..a9bc8749e0b988c2687c062ad257a7210d7c3e85 100644 --- a/src/mol-base/collections/integer/impl/sorted-array.ts +++ b/src/mol-base/collections/integer/impl/sorted-array.ts @@ -10,6 +10,9 @@ import Interval from '../interval' type Nums = ArrayLike<number> + +export const Empty: Nums = [] + export function ofSortedArray(xs: Nums) { if (xs.length < 1) throw new Error('Sorted arrays must be non-empty.'); return xs; @@ -101,4 +104,172 @@ function binarySearchPredIndexRange(xs: Nums, value: number, start: number, end: } if (min > max) return max + 1; return xs[min] >= value ? min : min + 1; +} + +export function areIntersecting(a: Nums, b: Nums) { + if (a === b) return true; + + let { startI: i, startJ: j, endI, endJ } = getSuitableIntersectionRange(a, b); + while (i < endI && j < endJ) { + const x = a[i], y = b[j]; + if (x < y) { i++; } + else if (x > y) { j++; } + else return true; + } + return false; +} + +export function isSubset(a: Nums, b: Nums) { + if (a === b) return true; + + const lenB = b.length; + let { startI: i, startJ: j, endI, endJ } = getSuitableIntersectionRange(a, b); + // must be able to advance by lenB elements + if (endJ - j < lenB || endI - i < lenB) return false; + + let equal = 0; + while (i < endI && j < endJ) { + const x = a[i], y = b[j]; + if (x < y) { i++; } + else if (x > y) { j++; } + else { i++; j++; equal++; } + } + return equal === lenB; +} + +export function union(a: Nums, b: Nums) { + if (a === b) return a; + + const { startI: sI, startJ: sJ, endI, endJ } = getSuitableIntersectionRange(a, b); + let i = sI, j = sJ; + let commonCount = 0; + while (i < endI && j < endJ) { + const x = a[i], y = b[j]; + if (x < y) { i++; } + else if (x > y) { j++; } + else { i++; j++; commonCount++; } + } + + const lenA = a.length, lenB = b.length; + // A === B || B is subset of A ==> A + if ((commonCount === lenA && commonCount === lenB) || commonCount === lenB) return a; + // A is subset of B ===> B + if (commonCount === lenA) return b; + + const indices = new Int32Array(lenA + lenB - commonCount); + let offset = 0; + + // insert the "prefixes" + for (let k = 0; k < sI; k++) indices[offset++] = a[k]; + for (let k = 0; k < sJ; k++) indices[offset++] = b[k]; + + // insert the common part + i = sI; + j = sJ; + while (i < endI && j < endJ) { + const x = a[i], y = b[j]; + if (x < y) { indices[offset++] = x; i++; } + else if (x > y) { indices[offset++] = y; j++; } + else { indices[offset++] = x; i++; j++; } + } + + // insert the "tail" + for (; i < lenA; i++) indices[offset++] = a[i]; + for (; j < lenB; j++) indices[offset++] = b[j]; + + return ofSortedArray(indices); +} + +export function intersect(a: Nums, b: Nums) { + if (a === b) return a; + + const { startI: sI, startJ: sJ, endI, endJ } = getSuitableIntersectionRange(a, b); + let i = sI, j = sJ; + let commonCount = 0; + while (i < endI && j < endJ) { + const x = a[i], y = b[j]; + if (x < y) { i++; } + else if (x > y) { j++; } + else { i++; j++; commonCount++; } + } + + const lenA = a.length, lenB = b.length; + // no common elements + if (!commonCount) return Empty; + // A === B || B is subset of A ==> B + if ((commonCount === lenA && commonCount === lenB) || commonCount === lenB) return b; + // A is subset of B ==> A + if (commonCount === lenA) return a; + + const indices = new Int32Array(commonCount); + let offset = 0; + i = sI; + j = sJ; + while (i < endI && j < endJ) { + const x = a[i], y = b[j]; + if (x < y) { i++; } + else if (x > y) { j++; } + else { indices[offset++] = x; i++; j++; } + } + + return ofSortedArray(indices); +} + +export function subtract(a: Nums, b: Nums) { + if (a === b) return Empty; + + const lenA = a.length; + const { startI: sI, startJ: sJ, endI, endJ } = getSuitableIntersectionRange(a, b); + let i = sI, j = sJ; + let commonCount = 0; + while (i < endI && j < endJ) { + const x = a[i], y = b[j]; + if (x < y) { i++; } + else if (x > y) { j++; } + else { i++; j++; commonCount++; } + } + + // A isnt intersecting B ===> A + if (!commonCount) return a; + // A === B || A is subset of B ===> Empty + if (commonCount >= lenA) return Empty; + + const indices = new Int32Array(lenA - commonCount); + let offset = 0; + + // insert the "prefix" + for (let k = 0; k < sI; k++) indices[offset++] = a[k]; + + i = sI; + j = sJ; + while (i < endI && j < endJ) { + const x = a[i], y = b[j]; + if (x < y) { indices[offset++] = x; i++; } + else if (x > y) { j++; } + else { i++; j++; } + } + + // insert the "tail" + for (; i < lenA; i++) indices[offset++] = a[i]; + + return ofSortedArray(indices); +} + +const _maxIntRangeRet = { startI: 0, startJ: 0, endI: 0, endJ: 0 }; +// for small sets, just gets the whole range, for large sets does a bunch of binary searches +export function getSuitableIntersectionRange(a: Nums, b: Nums) { + const la = a.length, lb = b.length; + const ratio = la / lb; + if (ratio <= 0.5 || ratio >= 2 || (la >= 128 && lb >= 128)) { + _maxIntRangeRet.startI = findPredecessorIndex(a, start(b)); + _maxIntRangeRet.startJ = findPredecessorIndex(b, start(a)); + _maxIntRangeRet.endI = findPredecessorIndex(a, end(b)); + _maxIntRangeRet.endJ = findPredecessorIndex(b, end(a)); + } else { + _maxIntRangeRet.startI = 0; + _maxIntRangeRet.startJ = 0; + _maxIntRangeRet.endI = la; + _maxIntRangeRet.endJ = lb; + } + return _maxIntRangeRet; } \ No newline at end of file diff --git a/src/mol-base/collections/integer/sorted-array.ts b/src/mol-base/collections/integer/sorted-array.ts index 1a95711d7f1bde939caa546b5760b44ecd093fc4..a57460d98e72c5b46fd20dba141d3be77a889eab 100644 --- a/src/mol-base/collections/integer/sorted-array.ts +++ b/src/mol-base/collections/integer/sorted-array.ts @@ -25,12 +25,18 @@ namespace SortedArray { export const hashCode: (array: SortedArray) => number = Impl.hashCode as any; export const areEqual: (a: SortedArray, b: SortedArray) => boolean = Impl.areEqual as any; + export const areIntersecting: (a: SortedArray, b: SortedArray) => boolean = Impl.areIntersecting as any; + export const isSubset: (a: SortedArray, b: SortedArray) => boolean = Impl.isSubset as any; + + export const union: (a: SortedArray, b: SortedArray) => SortedArray = Impl.union as any; + export const intersect: (a: SortedArray, b: SortedArray) => SortedArray = Impl.intersect as any; + export const subtract: (a: SortedArray, b: SortedArray) => SortedArray = Impl.subtract as any; export const findPredecessorIndex: (array: SortedArray, x: number) => number = Impl.findPredecessorIndex as any; export const findPredecessorIndexInInterval: (array: SortedArray, x: number, bounds: Interval) => number = Impl.findPredecessorIndexInInterval as any; export const findRange: (array: SortedArray, min: number, max: number) => Interval = Impl.findRange as any; } -interface SortedArray extends ReadonlyArray<number> { '@type': 'int-sorted-array' } +interface SortedArray extends ArrayLike<number> { '@type': 'int-sorted-array' } export default SortedArray \ No newline at end of file