diff --git a/src/mol-base/collections/integer/_spec/sorted-array.spec.ts b/src/mol-base/collections/integer/_spec/sorted-array.spec.ts index e3b7295230038684a9c18abd7deb38a00f7c51cb..c4d2231e7dfbe0a3f69311c33957894ba59a7537 100644 --- a/src/mol-base/collections/integer/_spec/sorted-array.spec.ts +++ b/src/mol-base/collections/integer/_spec/sorted-array.spec.ts @@ -34,7 +34,7 @@ describe('sortedArray', () => { test('indexOf', SortedArray.indexOf(a2468, 5), -1); test('indexOf', SortedArray.indexOf(a2468, 2), 0); - test('getAt', SortedArray.getAt(a2468, 1), 4); + test('getAt', a2468[1], 4); test('areEqual', SortedArray.areEqual(a2468, a2468), true); test('areEqual1', SortedArray.areEqual(a2468, SortedArray.ofUnsortedArray([4, 2, 8, 6])), true); diff --git a/src/mol-base/collections/integer/impl/ordered-set.ts b/src/mol-base/collections/integer/impl/ordered-set.ts index a3c80de0ec11edab5d57c6a5f31d3a365f0a346e..dab3f4db5187941c510e61b943a501a2f1504179 100644 --- a/src/mol-base/collections/integer/impl/ordered-set.ts +++ b/src/mol-base/collections/integer/impl/ordered-set.ts @@ -26,7 +26,7 @@ export function ofSortedArray(xs: Nums): OrderedSetImpl { export function size(set: OrderedSetImpl) { return I.is(set) ? I.size(set) : S.size(set); } export function has(set: OrderedSetImpl, x: number) { return I.is(set) ? I.has(set, x) : S.has(set, x); } export function indexOf(set: OrderedSetImpl, x: number) { return I.is(set) ? I.indexOf(set, x) : S.indexOf(set, x); } -export function getAt(set: OrderedSetImpl, i: number) { return I.is(set) ? I.getAt(set, i) : S.getAt(set, i); } +export function getAt(set: OrderedSetImpl, i: number) { return I.is(set) ? I.getAt(set, i) : set[i]; } export function min(set: OrderedSetImpl) { return I.is(set) ? I.min(set) : S.min(set); } export function max(set: OrderedSetImpl) { return I.is(set) ? I.max(set) : S.max(set); } diff --git a/src/mol-base/collections/integer/impl/sorted-array.ts b/src/mol-base/collections/integer/impl/sorted-array.ts index a9bc8749e0b988c2687c062ad257a7210d7c3e85..6167a28fb199e0372f31969013073b334d5b11fa 100644 --- a/src/mol-base/collections/integer/impl/sorted-array.ts +++ b/src/mol-base/collections/integer/impl/sorted-array.ts @@ -13,10 +13,8 @@ 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; -} +export function ofSingleton(v: number) { return [v]; } +export function ofSortedArray(xs: Nums) { return xs; } export function ofUnsortedArray(xs: Nums) { sortArray(xs); return xs; } export function is(xs: any): xs is Nums { return xs && (Array.isArray(xs) || !!xs.buffer); } @@ -257,7 +255,7 @@ export function subtract(a: Nums, b: Nums) { 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) { +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)) { diff --git a/src/mol-base/collections/integer/sorted-array.ts b/src/mol-base/collections/integer/sorted-array.ts index a57460d98e72c5b46fd20dba141d3be77a889eab..4f38bf9a6703ee7c77b2817af86670f3673c8590 100644 --- a/src/mol-base/collections/integer/sorted-array.ts +++ b/src/mol-base/collections/integer/sorted-array.ts @@ -8,14 +8,16 @@ import * as Impl from './impl/sorted-array' import Interval from './interval' namespace SortedArray { + export const Empty: SortedArray = Impl.Empty as any; export const ofUnsortedArray: (xs: ArrayLike<number>) => SortedArray = Impl.ofUnsortedArray as any; + export const ofSingleton: (v: number) => SortedArray = Impl.ofSingleton as any; export const ofSortedArray: (xs: ArrayLike<number>) => SortedArray = Impl.ofSortedArray as any; export const is: (v: any) => v is Interval = Impl.is as any; export const has: (array: SortedArray, x: number) => boolean = Impl.has as any; export const indexOf: (array: SortedArray, x: number) => number = Impl.indexOf as any; export const indexOfInterval: (array: SortedArray, x: number, bounds: Interval) => number = Impl.indexOfInterval as any; - export const getAt: (array: SortedArray, i: number) => number = Impl.getAt as any; + //export const getAt: (array: SortedArray, i: number) => number = Impl.getAt as any; export const start: (array: SortedArray) => number = Impl.start as any; export const end: (array: SortedArray) => number = Impl.end as any; diff --git a/src/mol-data/structure/atom-set/base.ts b/src/mol-data/structure/atom-set/base.ts index a198b5189e91c05b2e67c3ea5d06a495c22c09f8..102fde8fedae4eb6062643e6be21e247b63748f3 100644 --- a/src/mol-data/structure/atom-set/base.ts +++ b/src/mol-data/structure/atom-set/base.ts @@ -5,6 +5,7 @@ */ import OrderedSet from '../../../mol-base/collections/integer/ordered-set' +import SortedArray from '../../../mol-base/collections/integer/sorted-array' import Iterator from '../../../mol-base/collections/iterator' import Interval from '../../../mol-base/collections/integer/interval' import { sortArray } from '../../../mol-base/collections/sort' @@ -13,10 +14,10 @@ import Atom from '../atom' /** Long and painful implementation starts here */ -export interface AtomSetElements { [id: number]: OrderedSet, offsets: number[], hashCode: number, keys: OrderedSet } +export interface AtomSetElements { [id: number]: OrderedSet, offsets: number[], hashCode: number, keys: SortedArray } export type AtomSetImpl = Atom | AtomSetElements -export const Empty: AtomSetImpl = { offsets: [0], hashCode: 0, keys: OrderedSet.Empty }; +export const Empty: AtomSetImpl = { offsets: [0], hashCode: 0, keys: SortedArray.Empty }; export function create(data: Atom | ArrayLike<Atom> | { [id: number]: OrderedSet }): AtomSetImpl { if (typeof data === 'number' || Atom.is(data)) return data; @@ -28,43 +29,42 @@ export function isSingleton(set: AtomSetImpl) { return typeof set === 'number'; } -export function getKeys(set: AtomSetImpl): OrderedSet { - if (typeof set === 'number') return OrderedSet.ofSingleton(set); +export function getKeys(set: AtomSetImpl): SortedArray { + if (typeof set === 'number') return SortedArray.ofSingleton(set); return (set as AtomSetElements).keys; } export function keyCount(set: AtomSetImpl): number { if (typeof set === 'number') return 1; - return OrderedSet.size((set as AtomSetElements).keys); + return (set as AtomSetElements).keys.length; } export function hasKey(set: AtomSetImpl, key: number): boolean { if (typeof set === 'number') return Atom.unit(set) === key; - return OrderedSet.has((set as AtomSetElements).keys, key); + return !!(set as AtomSetElements)[key] } export function getKey(set: AtomSetImpl, index: number): number { if (typeof set === 'number') return Atom.unit(set); - return OrderedSet.getAt((set as AtomSetElements).keys, index); + return (set as AtomSetElements).keys[index]; } export function hasAtom(set: AtomSetImpl, t: Atom): boolean { if (typeof set === 'number') return Atom.areEqual(t, set); - const unit = Atom.unit(t); - return OrderedSet.has((set as AtomSetElements).keys, unit) - ? OrderedSet.has((set as AtomSetElements)[unit], Atom.index(t)) : false; + const os = (set as AtomSetElements)[Atom.unit(t)]; + return !!os && OrderedSet.has(os, Atom.index(t)); } export function getByKey(set: AtomSetImpl, key: number): OrderedSet { if (typeof set === 'number') { return Atom.unit(set) === key ? OrderedSet.ofSingleton(Atom.index(set)) : OrderedSet.Empty; } - return OrderedSet.has((set as AtomSetElements).keys, key) ? (set as AtomSetElements)[key] : OrderedSet.Empty; + return (set as AtomSetElements)[key] || OrderedSet.Empty; } export function getByIndex(set: AtomSetImpl, index: number): OrderedSet { if (typeof set === 'number') return index === 0 ? OrderedSet.ofSingleton(Atom.index(set)) : OrderedSet.Empty; - const key = OrderedSet.getAt((set as AtomSetElements).keys, index); + const key = (set as AtomSetElements).keys[index]; return (set as AtomSetElements)[key] || OrderedSet.Empty; } @@ -156,7 +156,7 @@ class ElementsIterator implements Iterator<Atom> { this.hasNext = false; return false; } - this.unit = OrderedSet.getAt(this.elements.keys, this.setIndex); + this.unit = this.elements.keys[this.setIndex]; this.currentSet = this.elements[this.unit]; this.currentIndex = 0; this.currentSize = OrderedSet.size(this.currentSet); @@ -164,7 +164,7 @@ class ElementsIterator implements Iterator<Atom> { } constructor(private elements: AtomSetElements) { - this.keyCount = OrderedSet.size(elements.keys); + this.keyCount = elements.keys.length; this.hasNext = this.keyCount > 0; this.advance(); } @@ -200,25 +200,25 @@ function ofObject1(keys: number[], data: { [id: number]: OrderedSet }) { if (OrderedSet.size(set) === 1) return Atom.create(k, OrderedSet.getAt(set, 0)); } sortArray(keys); - return _createObjectOrdered(OrderedSet.ofSortedArray(keys), data); + return _createObjectOrdered(SortedArray.ofSortedArray(keys), data); } -function ofObjectOrdered(keys: OrderedSet, data: { [id: number]: OrderedSet }) { - if (OrderedSet.size(keys) === 1) { - const k = OrderedSet.getAt(keys, 0); +function ofObjectOrdered(keys: SortedArray, data: { [id: number]: OrderedSet }) { + if (keys.length === 1) { + const k = keys[0]; const set = data[k]; if (OrderedSet.size(set) === 1) return Atom.create(k, OrderedSet.getAt(set, 0)); } return _createObjectOrdered(keys, data); } -function _createObjectOrdered(keys: OrderedSet, data: { [id: number]: OrderedSet }) { +function _createObjectOrdered(keys: SortedArray, data: { [id: number]: OrderedSet }) { const ret: AtomSetElements = Object.create(null); ret.keys = keys; const offsets = [0]; let runningSize = 0; - for (let i = 0, _i = OrderedSet.size(keys); i < _i; i++) { - const k = OrderedSet.getAt(keys, i); + for (let i = 0, _i = keys.length; i < _i; i++) { + const k = keys[i]; const set = data[k]; ret[k] = set; runningSize += OrderedSet.size(set); @@ -290,7 +290,7 @@ function getAtE(set: AtomSetElements, i: number): Atom { const { offsets, keys } = set; const o = getOffsetIndex(offsets, i); if (o >= offsets.length - 1) return 0 as any; - const k = OrderedSet.getAt(keys, o); + const k = keys[o]; const e = OrderedSet.getAt(set[k], i - offsets[o]); return Atom.create(k, e); } @@ -298,7 +298,7 @@ function getAtE(set: AtomSetElements, i: number): Atom { function indexOfE(set: AtomSetElements, t: Atom) { const { keys } = set; const u = Atom.unit(t); - const setIdx = OrderedSet.indexOf(keys, u); + const setIdx = SortedArray.indexOf(keys, u); if (setIdx < 0) return -1; const o = OrderedSet.indexOf(set[u], Atom.index(t)); if (o < 0) return -1; @@ -308,8 +308,8 @@ function indexOfE(set: AtomSetElements, t: Atom) { function computeHash(set: AtomSetElements) { const { keys } = set; let hash = 23; - for (let i = 0, _i = OrderedSet.size(keys); i < _i; i++) { - const k = OrderedSet.getAt(keys, i); + for (let i = 0, _i = keys.length; i < _i; i++) { + const k = keys[i]; hash = (31 * hash + k) | 0; hash = (31 * hash + OrderedSet.hashCode(set[k])) | 0; } @@ -324,9 +324,9 @@ function areEqualEE(a: AtomSetElements, b: AtomSetElements) { if (size(a) !== size(a)) return false; const keys = a.keys; - if (!OrderedSet.areEqual(keys, b.keys)) return false; - for (let i = 0, _i = OrderedSet.size(keys); i < _i; i++) { - const k = OrderedSet.getAt(keys, i); + if (!SortedArray.areEqual(keys, b.keys)) return false; + for (let i = 0, _i = keys.length; i < _i; i++) { + const k = keys[i]; if (!OrderedSet.areEqual(a[k], b[k])) return false; } return true; @@ -334,66 +334,66 @@ function areEqualEE(a: AtomSetElements, b: AtomSetElements) { function areIntersectingNE(a: Atom, b: AtomSetElements) { const u = Atom.unit(a); - return OrderedSet.has(b.keys, u) && OrderedSet.has(b[u], Atom.index(a)); + return SortedArray.has(b.keys, u) && OrderedSet.has(b[u], Atom.index(a)); } function areIntersectingEE(a: AtomSetElements, b: AtomSetElements) { if (a === b) return true; const keysA = a.keys, keysB = b.keys; - if (!OrderedSet.areIntersecting(a.keys, b.keys)) return false; - const r = OrderedSet.findRange(keysA, OrderedSet.min(keysB), OrderedSet.max(keysB)); + if (!SortedArray.areIntersecting(a.keys, b.keys)) return false; + const r = SortedArray.findRange(keysA, SortedArray.min(keysB), SortedArray.max(keysB)); const start = Interval.start(r), end = Interval.end(r); for (let i = start; i < end; i++) { - const k = OrderedSet.getAt(keysA, i); - if (OrderedSet.has(keysB, k) && OrderedSet.areIntersecting(a[k], b[k])) return true; + const k = keysA[i]; + const ak = a[k], bk = b[k]; + if (!!ak && !!bk && OrderedSet.areIntersecting(ak, bk)) return true; } return false; } function intersectNE(a: Atom, b: AtomSetElements) { const u = Atom.unit(a); - return OrderedSet.has(b.keys, u) && OrderedSet.has(b[u], Atom.index(a)) ? a : Empty; + return !!b[u] && OrderedSet.has(b[u], Atom.index(a)) ? a : Empty; } function intersectEE(a: AtomSetElements, b: AtomSetElements) { if (a === b) return a; const keysA = a.keys, keysB = b.keys; - if (!OrderedSet.areIntersecting(a.keys, b.keys)) return Empty; - const r = OrderedSet.findRange(keysA, OrderedSet.min(keysB), OrderedSet.max(keysB)); + if (!SortedArray.areIntersecting(a.keys, b.keys)) return Empty; + const r = SortedArray.findRange(keysA, SortedArray.min(keysB), SortedArray.max(keysB)); const start = Interval.start(r), end = Interval.end(r); const keys = [], ret = Object.create(null); for (let i = start; i < end; i++) { - const k = OrderedSet.getAt(keysA, i); - if (OrderedSet.has(keysB, k)) { - const intersection = OrderedSet.intersect(a[k], b[k]); - if (OrderedSet.size(intersection) > 0) { - keys[keys.length] = k; - ret[k] = intersection; - } + const k = keysA[i]; + const bk = b[k]; + if (!bk) continue; + const intersection = OrderedSet.intersect(a[k], b[k]); + if (OrderedSet.size(intersection) > 0) { + keys[keys.length] = k; + ret[k] = intersection; } } - return ofObjectOrdered(OrderedSet.ofSortedArray(keys), ret); + return ofObjectOrdered(SortedArray.ofSortedArray(keys), ret); } function subtractNE(a: Atom, b: AtomSetElements) { - const u = Atom.unit(a); - return OrderedSet.has(b.keys, u) && OrderedSet.has(b[u], Atom.index(a)) ? Empty : a; + return hasAtom(b, a) ? Empty : a; } function subtractEN(a: AtomSetElements, b: Atom): AtomSetImpl { - const aKeys = a.keys; + if (!hasAtom(a, b)) return a; + const u = Atom.unit(b), v = Atom.index(b); - if (!OrderedSet.has(aKeys, u) || !OrderedSet.has(a[u], v)) return a; const set = a[u]; if (OrderedSet.size(set) === 1) { // remove the entire unit. - return ofObjectOrdered(OrderedSet.subtract(a.keys, OrderedSet.ofSingleton(u)), a); + return ofObjectOrdered(SortedArray.subtract(a.keys, SortedArray.ofSingleton(u)), a); } else { const ret: { [key: number]: OrderedSet } = Object.create(null); - for (let i = 0, _i = OrderedSet.size(a.keys); i < _i; i++) { - const k = OrderedSet.getAt(a.keys, i); + for (let i = 0, _i = a.keys.length; i < _i; i++) { + const k = a.keys[i]; if (k === u) { ret[k] = OrderedSet.subtract(set, OrderedSet.ofSingleton(v)); } else ret[k] = a[k]; @@ -406,20 +406,21 @@ function subtractEE(a: AtomSetElements, b: AtomSetElements) { if (a === b) return Empty; const keysA = a.keys, keysB = b.keys; - if (!OrderedSet.areIntersecting(a.keys, b.keys)) return Empty; - const r = OrderedSet.findRange(keysA, OrderedSet.min(keysB), OrderedSet.max(keysB)); + if (!SortedArray.areIntersecting(keysA, keysB)) return a; + const r = SortedArray.findRange(keysA, SortedArray.min(keysB), SortedArray.max(keysB)); const start = Interval.start(r), end = Interval.end(r); const keys = [], ret = Object.create(null); for (let i = 0; i < start; i++) { - const k = OrderedSet.getAt(keysA, i); + const k = keysA[i]; keys[keys.length] = k; ret[k] = a[k]; } for (let i = start; i < end; i++) { - const k = OrderedSet.getAt(keysA, i); - if (OrderedSet.has(keysB, k)) { - const subtraction = OrderedSet.subtract(a[k], b[k]); + const k = keysA[i]; + const ak = a[k], bk = b[k]; + if (!!bk) { + const subtraction = OrderedSet.subtract(ak, bk); if (OrderedSet.size(subtraction) > 0) { keys[keys.length] = k; ret[k] = subtraction; @@ -429,12 +430,12 @@ function subtractEE(a: AtomSetElements, b: AtomSetElements) { ret[k] = a[k]; } } - for (let i = end, _i = OrderedSet.size(keysA); i < _i; i++) { - const k = OrderedSet.getAt(keysA, i); + for (let i = end, _i = keysA.length; i < _i; i++) { + const k = keysA[i]; keys[keys.length] = k; ret[k] = a[k]; } - return ofObjectOrdered(OrderedSet.ofSortedArray(keys), ret); + return ofObjectOrdered(SortedArray.ofSortedArray(keys), ret); } function findUnion(sets: ArrayLike<AtomSetImpl>) { @@ -477,8 +478,8 @@ function unionN(sets: ArrayLike<AtomSetImpl>, eCount: { count: number }) { function unionInto(data: { [key: number]: OrderedSet }, a: AtomSetElements) { const keys = a.keys; - for (let i = 0, _i = OrderedSet.size(keys); i < _i; i++) { - const k = OrderedSet.getAt(keys, i); + for (let i = 0, _i = keys.length; i < _i; i++) { + const k = keys[i]; const set = data[k]; if (set) data[k] = OrderedSet.union(set, a[k]); else data[k] = a[k];