diff --git a/src/mol-base/_spec/collections.spec.ts b/src/mol-base/_spec/collections.spec.ts index 89e956cd8266b505cb6dae62471818c3d9b3834c..56a7c6009f0780982b4ec38c658af0c5a3c4c94c 100644 --- a/src/mol-base/_spec/collections.spec.ts +++ b/src/mol-base/_spec/collections.spec.ts @@ -7,7 +7,6 @@ import Iterator from '../collections/iterator' import IntTuple from '../collections/int-tuple' import * as Sort from '../collections/sort' -import OrderedSet from '../collections/ordered-set' import LinkedIndex from '../collections/linked-index' import EquivalenceClasses from '../collections/equivalence-classes' import Interval from '../collections/interval' @@ -235,175 +234,6 @@ describe('sortedArray', () => { testI('findRange', SortedArray.findRange(a2468, 2, 4), Interval.ofRange(0, 1)); }); -describe('ordered set', () => { - function ordSetToArray(set: OrderedSet) { - const ret = []; - for (let i = 0, _i = OrderedSet.size(set); i < _i; i++) ret.push(OrderedSet.getAt(set, i)); - return ret; - } - - function testEq(name: string, set: OrderedSet, expected: number[]) { - it(name, () => { - // copy the arrays to ensure "compatibility" between typed and native arrays - expect(Array.prototype.slice.call(ordSetToArray(set))).toEqual(Array.prototype.slice.call(expected)); - }); - } - - const empty = OrderedSet.Empty; - const singleton10 = OrderedSet.ofSingleton(10); - const range1_4 = OrderedSet.ofRange(1, 4); - const arr136 = OrderedSet.ofSortedArray([1, 3, 6]); - - testEq('empty', empty, []); - testEq('singleton', singleton10, [10]); - testEq('range', range1_4, [1, 2, 3, 4]); - testEq('sorted array', arr136, [1, 3, 6]); - - it('equality', () => { - expect(OrderedSet.areEqual(empty, singleton10)).toBe(false); - expect(OrderedSet.areEqual(singleton10, singleton10)).toBe(true); - expect(OrderedSet.areEqual(range1_4, singleton10)).toBe(false); - expect(OrderedSet.areEqual(arr136, OrderedSet.ofSortedArray([1, 3, 6]))).toBe(true); - expect(OrderedSet.areEqual(arr136, OrderedSet.ofSortedArray([1, 4, 6]))).toBe(false); - }); - - it('areIntersecting', () => { - expect(OrderedSet.areIntersecting(range1_4, arr136)).toBe(true); - expect(OrderedSet.areIntersecting(empty, empty)).toBe(true); - expect(OrderedSet.areIntersecting(empty, singleton10)).toBe(false); - expect(OrderedSet.areIntersecting(empty, range1_4)).toBe(false); - expect(OrderedSet.areIntersecting(empty, arr136)).toBe(false); - }); - - it('isSubset', () => { - expect(OrderedSet.isSubset(singleton10, empty)).toBe(true); - expect(OrderedSet.isSubset(range1_4, empty)).toBe(true); - expect(OrderedSet.isSubset(arr136, empty)).toBe(true); - expect(OrderedSet.isSubset(empty, empty)).toBe(true); - expect(OrderedSet.isSubset(empty, singleton10)).toBe(false); - expect(OrderedSet.isSubset(empty, range1_4)).toBe(false); - expect(OrderedSet.isSubset(empty, arr136)).toBe(false); - - expect(OrderedSet.isSubset(singleton10, range1_4)).toBe(false); - expect(OrderedSet.isSubset(range1_4, OrderedSet.ofRange(2, 3))).toBe(true); - expect(OrderedSet.isSubset(arr136, range1_4)).toBe(false); - expect(OrderedSet.isSubset(arr136, arr136)).toBe(true); - expect(OrderedSet.isSubset(arr136, OrderedSet.ofSortedArray([1, 3]))).toBe(true); - expect(OrderedSet.isSubset(arr136, OrderedSet.ofSortedArray([1, 3, 7]))).toBe(false); - expect(OrderedSet.isSubset(OrderedSet.ofSortedArray([0, 1, 2, 3, 7, 10]), OrderedSet.ofSortedArray([1, 3, 7]))).toBe(true); - expect(OrderedSet.isSubset(arr136, OrderedSet.ofSortedArray([1, 3, 10, 45]))).toBe(false); - expect(OrderedSet.isSubset(arr136, OrderedSet.ofSortedArray([12, 13, 16]))).toBe(false); - }); - - it('access/membership', () => { - expect(OrderedSet.has(empty, 10)).toBe(false); - expect(OrderedSet.indexOf(empty, 10)).toBe(-1); - - expect(OrderedSet.has(singleton10, 10)).toBe(true); - expect(OrderedSet.has(singleton10, 11)).toBe(false); - expect(OrderedSet.indexOf(singleton10, 10)).toBe(0); - expect(OrderedSet.indexOf(singleton10, 11)).toBe(-1); - - expect(OrderedSet.has(range1_4, 4)).toBe(true); - expect(OrderedSet.has(range1_4, 5)).toBe(false); - expect(OrderedSet.indexOf(range1_4, 4)).toBe(3); - expect(OrderedSet.indexOf(range1_4, 11)).toBe(-1); - - expect(OrderedSet.has(arr136, 3)).toBe(true); - expect(OrderedSet.has(arr136, 4)).toBe(false); - expect(OrderedSet.indexOf(arr136, 3)).toBe(1); - expect(OrderedSet.indexOf(arr136, 11)).toBe(-1); - }); - - it('interval range', () => { - expect(OrderedSet.findIntervalRange(empty, 9, 11)).toEqual({ start: 0, end: 0 }); - expect(OrderedSet.findIntervalRange(empty, -9, -6)).toEqual({ start: 0, end: 0 }); - expect(OrderedSet.findIntervalRange(singleton10, 9, 11)).toEqual({ start: 0, end: 1 }); - expect(OrderedSet.findIntervalRange(range1_4, 2, 3)).toEqual({ start: 1, end: 3 }); - expect(OrderedSet.findIntervalRange(range1_4, -10, 2)).toEqual({ start: 0, end: 2 }); - expect(OrderedSet.findIntervalRange(range1_4, -10, 20)).toEqual({ start: 0, end: 4 }); - expect(OrderedSet.findIntervalRange(range1_4, 3, 20)).toEqual({ start: 2, end: 4 }); - expect(OrderedSet.findIntervalRange(arr136, 0, 1)).toEqual({ start: 0, end: 1 }); - expect(OrderedSet.findIntervalRange(arr136, 0, 3)).toEqual({ start: 0, end: 2 }); - expect(OrderedSet.findIntervalRange(arr136, 0, 4)).toEqual({ start: 0, end: 2 }); - expect(OrderedSet.findIntervalRange(arr136, 2, 4)).toEqual({ start: 1, end: 2 }); - expect(OrderedSet.findIntervalRange(arr136, 2, 7)).toEqual({ start: 1, end: 3 }); - }) - - testEq('union ES', OrderedSet.union(empty, singleton10), [10]); - testEq('union ER', OrderedSet.union(empty, range1_4), [1, 2, 3, 4]); - testEq('union EA', OrderedSet.union(empty, arr136), [1, 3, 6]); - testEq('union SS', OrderedSet.union(singleton10, OrderedSet.ofSingleton(16)), [10, 16]); - testEq('union SR', OrderedSet.union(range1_4, singleton10), [1, 2, 3, 4, 10]); - testEq('union SA', OrderedSet.union(arr136, singleton10), [1, 3, 6, 10]); - testEq('union SA1', OrderedSet.union(arr136, OrderedSet.ofSingleton(3)), [1, 3, 6]); - testEq('union RR', OrderedSet.union(range1_4, range1_4), [1, 2, 3, 4]); - testEq('union RR1', OrderedSet.union(range1_4, OrderedSet.ofRange(6, 7)), [1, 2, 3, 4, 6, 7]); - testEq('union RR2', OrderedSet.union(range1_4, OrderedSet.ofRange(3, 5)), [1, 2, 3, 4, 5]); - testEq('union RA', OrderedSet.union(range1_4, arr136), [1, 2, 3, 4, 6]); - testEq('union AA', OrderedSet.union(arr136, OrderedSet.ofSortedArray([2, 4, 6, 7])), [1, 2, 3, 4, 6, 7]); - testEq('union AA1', OrderedSet.union(arr136, OrderedSet.ofSortedArray([2, 3, 4, 6, 7])), [1, 2, 3, 4, 6, 7]); - testEq('union AA2', OrderedSet.union(arr136, OrderedSet.ofSortedArray([2, 4, 5, 6, 7])), [1, 2, 3, 4, 5, 6, 7]); - testEq('union AA3', OrderedSet.union(OrderedSet.ofSortedArray([1, 3]), OrderedSet.ofSortedArray([2, 4])), [1, 2, 3, 4]); - testEq('union AA4', OrderedSet.union(OrderedSet.ofSortedArray([1, 3]), OrderedSet.ofSortedArray([1, 3, 4])), [1, 3, 4]); - testEq('union AA5', OrderedSet.union(OrderedSet.ofSortedArray([1, 3, 4]), OrderedSet.ofSortedArray([1, 3])), [1, 3, 4]); - it('union AA6', () => expect(OrderedSet.union(arr136, OrderedSet.ofSortedArray([1, 3, 6]))).toBe(arr136)); - - testEq('intersect ES', OrderedSet.intersect(empty, singleton10), []); - testEq('intersect ER', OrderedSet.intersect(empty, range1_4), []); - testEq('intersect EA', OrderedSet.intersect(empty, arr136), []); - testEq('intersect SS', OrderedSet.intersect(singleton10, OrderedSet.ofSingleton(16)), []); - testEq('intersect SS1', OrderedSet.intersect(singleton10, singleton10), [10]); - testEq('intersect SR', OrderedSet.intersect(range1_4, singleton10), []); - testEq('intersect RR', OrderedSet.intersect(range1_4, range1_4), [1, 2, 3, 4]); - testEq('intersect RR2', OrderedSet.intersect(range1_4, OrderedSet.ofRange(3, 5)), [3, 4]); - testEq('intersect RA', OrderedSet.intersect(range1_4, arr136), [1, 3]); - testEq('intersect AA', OrderedSet.intersect(arr136, OrderedSet.ofSortedArray([2, 3, 4, 6, 7])), [3, 6]); - it('intersect AA1', () => expect(OrderedSet.union(arr136, OrderedSet.ofSortedArray([1, 3, 6]))).toBe(arr136)); - - testEq('subtract ES', OrderedSet.subtract(empty, singleton10), []); - testEq('subtract ER', OrderedSet.subtract(empty, range1_4), []); - testEq('subtract EA', OrderedSet.subtract(empty, arr136), []); - testEq('subtract SS', OrderedSet.subtract(singleton10, OrderedSet.ofSingleton(16)), [10]); - testEq('subtract SS1', OrderedSet.subtract(singleton10, singleton10), []); - testEq('subtract SR', OrderedSet.subtract(range1_4, singleton10), [1, 2, 3, 4]); - testEq('subtract SR1', OrderedSet.subtract(range1_4, OrderedSet.ofSingleton(4)), [1, 2, 3]); - testEq('subtract SR2', OrderedSet.subtract(range1_4, OrderedSet.ofSingleton(3)), [1, 2, 4]); - testEq('subtract RR', OrderedSet.subtract(range1_4, range1_4), []); - testEq('subtract RR1', OrderedSet.subtract(range1_4, OrderedSet.ofRange(3, 5)), [1, 2]); - - testEq('subtract RA', OrderedSet.subtract(range1_4, arr136), [2, 4]); - testEq('subtract RA1', OrderedSet.subtract(range1_4, OrderedSet.ofSortedArray([0, 1, 2, 3, 4, 7])), []); - testEq('subtract RA2', OrderedSet.subtract(range1_4, OrderedSet.ofSortedArray([0, 2, 3])), [1, 4]); - - testEq('subtract AR', OrderedSet.subtract(arr136, range1_4), [6]); - testEq('subtract AR1', OrderedSet.subtract(arr136, OrderedSet.ofRange(0, 10)), []); - testEq('subtract AR1', OrderedSet.subtract(arr136, OrderedSet.ofRange(2, 10)), [1]); - - testEq('subtract AA', OrderedSet.subtract(arr136, arr136), []); - testEq('subtract AA1', OrderedSet.subtract(arr136, OrderedSet.ofSortedArray([2, 3, 4, 6, 7])), [1]); - testEq('subtract AA2', OrderedSet.subtract(arr136, OrderedSet.ofSortedArray([0, 1, 6])), [3]); - - it('segments', () => { - const data = OrderedSet.ofSortedArray([4, 9, 10, 11, 14, 15, 16]); - const segs = OrderedSet.ofSortedArray([0, 4, 10, 12, 13, 15, 25]); - const it = OrderedSet.segments(segs, data); - - const t = Object.create(null); - for (let s = it.move(); !it.done; s = it.move()) { - for (let j = s.start; j < s.end; j++) { - const x = t[s.segment]; - const v = OrderedSet.getAt(data, j); - if (!x) t[s.segment] = [v]; - else x[x.length] = v; - } - } - - expect(t).toEqual({ 1: [4, 9], 2: [10, 11], 4: [14], 5: [15, 16] }); - }); -}); - - describe('linked-index', () => { it('initial state', () => { const index = LinkedIndex(2); diff --git a/src/mol-base/_spec/ordered-set.spec.ts b/src/mol-base/_spec/ordered-set.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..11ff74e711206d4254959de1e109af0dc4e5accf --- /dev/null +++ b/src/mol-base/_spec/ordered-set.spec.ts @@ -0,0 +1,178 @@ +/** + * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import OrderedSet from '../collections/ordered-set' +import Interval from '../collections/interval' + +describe('ordered set', () => { + function ordSetToArray(set: OrderedSet) { + const ret = []; + for (let i = 0, _i = OrderedSet.size(set); i < _i; i++) ret.push(OrderedSet.getAt(set, i)); + return ret; + } + + function testEq(name: string, set: OrderedSet, expected: number[]) { + it(name, () => { + // copy the arrays to ensure "compatibility" between typed and native arrays + expect(Array.prototype.slice.call(ordSetToArray(set))).toEqual(Array.prototype.slice.call(expected)); + }); + } + + const empty = OrderedSet.Empty; + const singleton10 = OrderedSet.ofSingleton(10); + const range1_4 = OrderedSet.ofRange(1, 4); + const arr136 = OrderedSet.ofSortedArray([1, 3, 6]); + + const iB = (s: number, e: number) => Interval.ofBounds(s, e); + + testEq('empty', empty, []); + testEq('singleton', singleton10, [10]); + testEq('range', range1_4, [1, 2, 3, 4]); + testEq('sorted array', arr136, [1, 3, 6]); + + it('equality', () => { + expect(OrderedSet.areEqual(empty, singleton10)).toBe(false); + expect(OrderedSet.areEqual(singleton10, singleton10)).toBe(true); + expect(OrderedSet.areEqual(range1_4, singleton10)).toBe(false); + expect(OrderedSet.areEqual(arr136, OrderedSet.ofSortedArray([1, 3, 6]))).toBe(true); + expect(OrderedSet.areEqual(arr136, OrderedSet.ofSortedArray([1, 4, 6]))).toBe(false); + }); + + it('areIntersecting', () => { + expect(OrderedSet.areIntersecting(range1_4, arr136)).toBe(true); + expect(OrderedSet.areIntersecting(empty, empty)).toBe(true); + expect(OrderedSet.areIntersecting(empty, singleton10)).toBe(false); + expect(OrderedSet.areIntersecting(empty, range1_4)).toBe(false); + expect(OrderedSet.areIntersecting(empty, arr136)).toBe(false); + }); + + it('isSubset', () => { + expect(OrderedSet.isSubset(singleton10, empty)).toBe(true); + expect(OrderedSet.isSubset(range1_4, empty)).toBe(true); + expect(OrderedSet.isSubset(arr136, empty)).toBe(true); + expect(OrderedSet.isSubset(empty, empty)).toBe(true); + expect(OrderedSet.isSubset(empty, singleton10)).toBe(false); + expect(OrderedSet.isSubset(empty, range1_4)).toBe(false); + expect(OrderedSet.isSubset(empty, arr136)).toBe(false); + + expect(OrderedSet.isSubset(singleton10, range1_4)).toBe(false); + expect(OrderedSet.isSubset(range1_4, OrderedSet.ofRange(2, 3))).toBe(true); + expect(OrderedSet.isSubset(arr136, range1_4)).toBe(false); + expect(OrderedSet.isSubset(arr136, arr136)).toBe(true); + expect(OrderedSet.isSubset(arr136, OrderedSet.ofSortedArray([1, 3]))).toBe(true); + expect(OrderedSet.isSubset(arr136, OrderedSet.ofSortedArray([1, 3, 7]))).toBe(false); + expect(OrderedSet.isSubset(OrderedSet.ofSortedArray([0, 1, 2, 3, 7, 10]), OrderedSet.ofSortedArray([1, 3, 7]))).toBe(true); + expect(OrderedSet.isSubset(arr136, OrderedSet.ofSortedArray([1, 3, 10, 45]))).toBe(false); + expect(OrderedSet.isSubset(arr136, OrderedSet.ofSortedArray([12, 13, 16]))).toBe(false); + }); + + it('access/membership', () => { + expect(OrderedSet.has(empty, 10)).toBe(false); + expect(OrderedSet.indexOf(empty, 10)).toBe(-1); + + expect(OrderedSet.has(singleton10, 10)).toBe(true); + expect(OrderedSet.has(singleton10, 11)).toBe(false); + expect(OrderedSet.indexOf(singleton10, 10)).toBe(0); + expect(OrderedSet.indexOf(singleton10, 11)).toBe(-1); + + expect(OrderedSet.has(range1_4, 4)).toBe(true); + expect(OrderedSet.has(range1_4, 5)).toBe(false); + expect(OrderedSet.indexOf(range1_4, 4)).toBe(3); + expect(OrderedSet.indexOf(range1_4, 11)).toBe(-1); + + expect(OrderedSet.has(arr136, 3)).toBe(true); + expect(OrderedSet.has(arr136, 4)).toBe(false); + expect(OrderedSet.indexOf(arr136, 3)).toBe(1); + expect(OrderedSet.indexOf(arr136, 11)).toBe(-1); + }); + + it('interval range', () => { + expect(OrderedSet.findRange(empty, 9, 11)).toEqual(iB(0, 0)); + expect(OrderedSet.findRange(empty, -9, -6)).toEqual(iB(0, 0)); + expect(OrderedSet.findRange(singleton10, 9, 11)).toEqual(iB(0, 1)); + expect(OrderedSet.findRange(range1_4, 2, 3)).toEqual(iB(1, 3)); + expect(OrderedSet.findRange(range1_4, -10, 2)).toEqual(iB(0, 2)); + expect(OrderedSet.findRange(range1_4, -10, 20)).toEqual(iB(0, 4)); + expect(OrderedSet.findRange(range1_4, 3, 20)).toEqual(iB(2, 4)); + expect(OrderedSet.findRange(arr136, 0, 1)).toEqual(iB(0, 1)); + expect(OrderedSet.findRange(arr136, 0, 3)).toEqual(iB(0, 2)); + expect(OrderedSet.findRange(arr136, 0, 4)).toEqual(iB(0, 2)); + expect(OrderedSet.findRange(arr136, 2, 4)).toEqual(iB(1, 2)); + expect(OrderedSet.findRange(arr136, 2, 7)).toEqual(iB(1, 3)); + }) + + testEq('union ES', OrderedSet.union(empty, singleton10), [10]); + testEq('union ER', OrderedSet.union(empty, range1_4), [1, 2, 3, 4]); + testEq('union EA', OrderedSet.union(empty, arr136), [1, 3, 6]); + testEq('union SS', OrderedSet.union(singleton10, OrderedSet.ofSingleton(16)), [10, 16]); + testEq('union SR', OrderedSet.union(range1_4, singleton10), [1, 2, 3, 4, 10]); + testEq('union SA', OrderedSet.union(arr136, singleton10), [1, 3, 6, 10]); + testEq('union SA1', OrderedSet.union(arr136, OrderedSet.ofSingleton(3)), [1, 3, 6]); + testEq('union RR', OrderedSet.union(range1_4, range1_4), [1, 2, 3, 4]); + testEq('union RR1', OrderedSet.union(range1_4, OrderedSet.ofRange(6, 7)), [1, 2, 3, 4, 6, 7]); + testEq('union RR2', OrderedSet.union(range1_4, OrderedSet.ofRange(3, 5)), [1, 2, 3, 4, 5]); + testEq('union RA', OrderedSet.union(range1_4, arr136), [1, 2, 3, 4, 6]); + testEq('union AA', OrderedSet.union(arr136, OrderedSet.ofSortedArray([2, 4, 6, 7])), [1, 2, 3, 4, 6, 7]); + testEq('union AA1', OrderedSet.union(arr136, OrderedSet.ofSortedArray([2, 3, 4, 6, 7])), [1, 2, 3, 4, 6, 7]); + testEq('union AA2', OrderedSet.union(arr136, OrderedSet.ofSortedArray([2, 4, 5, 6, 7])), [1, 2, 3, 4, 5, 6, 7]); + testEq('union AA3', OrderedSet.union(OrderedSet.ofSortedArray([1, 3]), OrderedSet.ofSortedArray([2, 4])), [1, 2, 3, 4]); + testEq('union AA4', OrderedSet.union(OrderedSet.ofSortedArray([1, 3]), OrderedSet.ofSortedArray([1, 3, 4])), [1, 3, 4]); + testEq('union AA5', OrderedSet.union(OrderedSet.ofSortedArray([1, 3, 4]), OrderedSet.ofSortedArray([1, 3])), [1, 3, 4]); + it('union AA6', () => expect(OrderedSet.union(arr136, OrderedSet.ofSortedArray([1, 3, 6]))).toBe(arr136)); + + testEq('intersect ES', OrderedSet.intersect(empty, singleton10), []); + testEq('intersect ER', OrderedSet.intersect(empty, range1_4), []); + testEq('intersect EA', OrderedSet.intersect(empty, arr136), []); + testEq('intersect SS', OrderedSet.intersect(singleton10, OrderedSet.ofSingleton(16)), []); + testEq('intersect SS1', OrderedSet.intersect(singleton10, singleton10), [10]); + testEq('intersect SR', OrderedSet.intersect(range1_4, singleton10), []); + testEq('intersect RR', OrderedSet.intersect(range1_4, range1_4), [1, 2, 3, 4]); + testEq('intersect RR2', OrderedSet.intersect(range1_4, OrderedSet.ofRange(3, 5)), [3, 4]); + testEq('intersect RA', OrderedSet.intersect(range1_4, arr136), [1, 3]); + testEq('intersect AA', OrderedSet.intersect(arr136, OrderedSet.ofSortedArray([2, 3, 4, 6, 7])), [3, 6]); + it('intersect AA1', () => expect(OrderedSet.union(arr136, OrderedSet.ofSortedArray([1, 3, 6]))).toBe(arr136)); + + testEq('subtract ES', OrderedSet.subtract(empty, singleton10), []); + testEq('subtract ER', OrderedSet.subtract(empty, range1_4), []); + testEq('subtract EA', OrderedSet.subtract(empty, arr136), []); + testEq('subtract SS', OrderedSet.subtract(singleton10, OrderedSet.ofSingleton(16)), [10]); + testEq('subtract SS1', OrderedSet.subtract(singleton10, singleton10), []); + testEq('subtract SR', OrderedSet.subtract(range1_4, singleton10), [1, 2, 3, 4]); + testEq('subtract SR1', OrderedSet.subtract(range1_4, OrderedSet.ofSingleton(4)), [1, 2, 3]); + testEq('subtract SR2', OrderedSet.subtract(range1_4, OrderedSet.ofSingleton(3)), [1, 2, 4]); + testEq('subtract RR', OrderedSet.subtract(range1_4, range1_4), []); + testEq('subtract RR1', OrderedSet.subtract(range1_4, OrderedSet.ofRange(3, 5)), [1, 2]); + + testEq('subtract RA', OrderedSet.subtract(range1_4, arr136), [2, 4]); + testEq('subtract RA1', OrderedSet.subtract(range1_4, OrderedSet.ofSortedArray([0, 1, 2, 3, 4, 7])), []); + testEq('subtract RA2', OrderedSet.subtract(range1_4, OrderedSet.ofSortedArray([0, 2, 3])), [1, 4]); + + testEq('subtract AR', OrderedSet.subtract(arr136, range1_4), [6]); + testEq('subtract AR1', OrderedSet.subtract(arr136, OrderedSet.ofRange(0, 10)), []); + testEq('subtract AR1', OrderedSet.subtract(arr136, OrderedSet.ofRange(2, 10)), [1]); + + testEq('subtract AA', OrderedSet.subtract(arr136, arr136), []); + testEq('subtract AA1', OrderedSet.subtract(arr136, OrderedSet.ofSortedArray([2, 3, 4, 6, 7])), [1]); + testEq('subtract AA2', OrderedSet.subtract(arr136, OrderedSet.ofSortedArray([0, 1, 6])), [3]); + + it('segments', () => { + const data = OrderedSet.ofSortedArray([4, 9, 10, 11, 14, 15, 16]); + const segs = OrderedSet.ofSortedArray([0, 4, 10, 12, 13, 15, 25]); + const it = OrderedSet.segments(segs, data); + + const t = Object.create(null); + for (let s = it.move(); !it.done; s = it.move()) { + for (let j = s.start; j < s.end; j++) { + const x = t[s.segment]; + const v = OrderedSet.getAt(data, j); + if (!x) t[s.segment] = [v]; + else x[x.length] = v; + } + } + + expect(t).toEqual({ 1: [4, 9], 2: [10, 11], 4: [14], 5: [15, 16] }); + }); +}); \ No newline at end of file diff --git a/src/mol-base/collections/impl/interval.ts b/src/mol-base/collections/impl/interval.ts index d0290c22915b6942727b2d5cb32b487aec261789..6f810a8f1e39dddc356b57ab54dde24b05a58d13 100644 --- a/src/mol-base/collections/impl/interval.ts +++ b/src/mol-base/collections/impl/interval.ts @@ -7,8 +7,8 @@ import IntTuple from '../int-tuple' export const Empty = IntTuple.Zero; -export function ofRange(min: number, max: number) { return max < min ? Empty : IntTuple.create(min, max + 1); } -export function ofBounds(min: number, max: number) { return max <= min ? Empty : IntTuple.create(min, max); } +export function ofRange(min: number, max: number) { return max < min ? IntTuple.create(min, min) : IntTuple.create(min, max + 1); } +export function ofBounds(min: number, max: number) { return max <= min ? IntTuple.create(min, min) : IntTuple.create(min, max); } export const is = IntTuple.is; export const start = IntTuple.fst; diff --git a/src/mol-base/collections/impl/ordered-set.ts b/src/mol-base/collections/impl/ordered-set.ts new file mode 100644 index 0000000000000000000000000000000000000000..4dfeb269106756bd2e1b82849fad6ad9e4669d3c --- /dev/null +++ b/src/mol-base/collections/impl/ordered-set.ts @@ -0,0 +1,429 @@ +/** + * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import S from '../sorted-array' +import I from '../interval' + +type OrderedSetImpl = I | S +type Nums = ArrayLike<number> + +export const Empty: OrderedSetImpl = I.Empty; + +export const ofSingleton = I.ofSingleton +export const ofRange = I.ofRange + +export function ofSortedArray(xs: Nums): OrderedSetImpl { + if (!xs.length) return Empty; + // check if the array is just a range + if (xs[xs.length - 1] - xs[0] + 1 === xs.length) return I.ofRange(xs[0], xs[xs.length - 1]); + return xs as any; +} + +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 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); } + +export function hashCode(set: OrderedSetImpl) { return I.is(set) ? I.hashCode(set) : S.hashCode(set); } +// TODO: possibly add more hash functions to allow for multilevel hashing. + +export function areEqual(a: OrderedSetImpl, b: OrderedSetImpl) { + if (I.is(a)) { + if (I.is(b)) return I.areEqual(a, b); + return areEqualIS(a, b); + } else if (I.is(b)) return areEqualIS(b, a); + return S.areEqual(a, b); +} + +export function areIntersecting(a: OrderedSetImpl, b: OrderedSetImpl) { + if (I.is(a)) { + 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); +} + +/** Check if the 2nd argument is a subset of the 1st */ +export function isSubset(a: OrderedSetImpl, b: OrderedSetImpl) { + if (I.is(a)) { + 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); +} + +export function findPredecessorIndex(set: OrderedSetImpl, x: number) { + return I.is(set) ? I.findPredecessorIndex(set, x) : S.findPredecessorIndex(set, x); +} + +export function findPredecessorIndexInInterval(set: OrderedSetImpl, x: number, bounds: I) { + return I.is(set) ? I.findPredecessorIndexInInterval(set, x, bounds) : S.findPredecessorIndexInInterval(set, x, bounds); +} + +export function findRange(set: OrderedSetImpl, min: number, max: number) { + return I.is(set) ? I.findRange(set, min, max) : S.findRange(set, min, max); +} + +export function union(a: OrderedSetImpl, b: OrderedSetImpl) { + if (I.is(a)) { + 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); +} + +export function intersect(a: OrderedSetImpl, b: OrderedSetImpl) { + if (I.is(a)) { + 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); +} + +export function subtract(a: OrderedSetImpl, b: OrderedSetImpl) { + if (I.is(a)) { + 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 }; +function getMaxIntersectionRange(a: S, b: S) { + _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)); + return _maxIntRangeRet; +} + +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); } + +function areIntersectingSI(a: S, b: I) { + return areRangesIntersecting(a, b); +} + +function areIntersectingSS(a: S, b: S) { + if (a === b) return true; + + const xs = S.values(a), ys = S.values(b); + let { startI: i, startJ: j, endI, endJ } = getMaxIntersectionRange(a, b); + while (i < endI && j < endJ) { + const x = xs[i], y = ys[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; + const minA = S.min(a), maxA = S.max(a); + if (minB < minA || maxB > maxA) return false; + const r = S.findRange(a, minB, maxB); + return I.size(r) === I.size(b); +} + +function isSubsetIS(a: I, b: S) { + const minA = I.min(a), maxA = I.max(a); + if (maxA - minA + 1 === 0) return false; + const minB = S.min(b), maxB = S.max(b); + return minB >= minA && maxA <= maxB; +} + +function isSubsetSS(a: S, b: S) { + if (a === b) return true; + + const xs = S.values(a), ys = S.values(b); + const lenB = ys.length; + let { startI: i, startJ: j, endI, endJ } = getMaxIntersectionRange(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 = xs[i], y = ys[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; + return sa > 0 && sb > 0 && max(a) >= min(b) && min(a) <= max(b); +} + +function isRangeSubset(a: OrderedSetImpl, b: OrderedSetImpl) { + if (!size(a)) return size(b) === 0; + if (!size(b)) return true; + return min(a) <= min(b) && max(a) >= max(b); +} + +function unionII(a: I, b: I) { + if (I.areEqual(a, b)) return a; + + const sizeA = I.size(a), sizeB = I.size(b); + if (!sizeA) return b; + if (!sizeB) return a; + const minA = I.min(a), minB = I.min(b); + if (areRangesIntersecting(a, b)) return I.ofRange(Math.min(minA, minB), Math.max(I.max(a), I.max(b))); + let lSize, lMin, rSize, rMin; + if (minA < minB) { lSize = sizeA; lMin = minA; rSize = sizeB; rMin = minB; } + else { lSize = sizeB; lMin = minB; rSize = sizeA; rMin = minA; } + const arr = new Int32Array(sizeA + sizeB); + for (let i = 0; i < lSize; i++) arr[i] = i + lMin; + for (let i = 0; i < rSize; i++) arr[i + lSize] = i + rMin; + return ofSortedArray(arr); +} + +function unionSI(a: S, b: I) { + const bSize = I.size(b); + if (!bSize) return a; + // is the array fully contained in the range? + if (isRangeSubset(b, a)) return b; + + const min = I.min(b), max = I.max(b); + const r = S.findRange(a, min, max); + const start = I.start(r), end = I.end(r); + const xs = S.values(a); + const indices = new Int32Array(start + (xs.length - end) + bSize); + let offset = 0; + for (let i = 0; i < start; i++) indices[offset++] = xs[i]; + for (let i = min; i <= max; i++) indices[offset++] = i; + for (let i = end, _i = xs.length; i < _i; i++) indices[offset] = xs[i]; + + return ofSortedArray(indices); +} + +function unionSS(a: S, b: S) { + if (a === b) return a; + + const xs = S.values(a), ys = S.values(b); + const { startI: sI, startJ: sJ, endI, endJ } = getMaxIntersectionRange(a, b); + let i = sI, j = sJ; + let commonCount = 0; + while (i < endI && j < endJ) { + const x = xs[i], y = ys[j]; + if (x < y) { i++; } + else if (x > y) { j++; } + else { i++; j++; commonCount++; } + } + + const lenA = xs.length, lenB = ys.length; + // A === B || B is subset of A ==> A + if ((commonCount === lenA && commonCount === lenB) || commonCount === lenB) return xs; + // A is subset of B ===> B + if (commonCount === lenA) return ys; + + const resultSize = lenA + lenB - commonCount; + const l = Math.min(xs[0], ys[0]), r = Math.max(xs[lenA - 1], ys[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++] = xs[k]; + for (let k = 0; k < sJ; k++) indices[offset++] = ys[k]; + + // insert the common part + i = sI; + j = sJ; + while (i < endI && j < endJ) { + const x = xs[i], y = ys[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++] = xs[i]; + for (; j < lenB; j++) indices[offset++] = ys[j]; + + return ofSortedArray(indices); +} + +function intersectSI(a: S, b: I) { + if (!I.size(b)) return Empty; + + const r = S.findRange(a, I.min(b), I.max(b)); + const start = I.start(r), end = I.end(r); + const resultSize = end - start; + if (!resultSize) return Empty; + + const xs = S.values(a); + const indices = new Int32Array(resultSize); + let offset = 0; + for (let i = start; i < end; i++) { + indices[offset++] = xs[i]; + } + return ofSortedArray(indices); +} + +function intersectSS(a: S, b: S) { + if (a === b) return a; + + const xs = S.values(a), ys = S.values(b); + const { startI: sI, startJ: sJ, endI, endJ } = getMaxIntersectionRange(a, b); + let i = sI, j = sJ; + let commonCount = 0; + while (i < endI && j < endJ) { + const x = xs[i], y = ys[j]; + if (x < y) { i++; } + else if (x > y) { j++; } + else { i++; j++; commonCount++; } + } + + const lenA = xs.length, lenB = ys.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 = xs[i], y = ys[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; + + const minA = I.min(a), maxA = I.max(a); + const minB = I.min(b), maxB = I.max(b); + + if (maxA < minA || maxB < minB) return a; + // is A subset of B? ==> Empty + if (I.isSubInterval(b, a)) return Empty; + if (I.isSubInterval(a, b)) { + // this splits the interval into two, gotta represent it as a set. + const l = minB - minA, r = maxA - maxB; + if (l <= 0) return I.ofRange(maxB + 1, maxB + r); + if (r <= 0) return I.ofRange(minA, minA + l - 1); + const ret = new Int32Array(l + r); + let offset = 0; + for (let i = 0; i < l; i++) ret[offset++] = minA + i; + for (let i = 1; i <= r; i++) ret[offset++] = maxB + i; + return ofSortedArray(ret); + } + if (minA < minB) return I.ofRange(minA, minB - 1); + return I.ofRange(maxB + 1, maxA); +} + +function subtractSI(a: S, b: I) { + const min = I.min(b), max = I.max(b); + // is empty? + if (max < min) return a; + + const xs = S.values(a); + const r = S.findRange(a, min, max); + const start = I.start(r), end = I.end(r); + const resultSize = xs.length - (end - start); + // A is subset of B + if (resultSize <= 0) return Empty; + // No common elements + if (resultSize === xs.length) return xs; + + const ret = new Int32Array(resultSize); + let offset = 0; + for (let i = 0; i < start; i++) ret[offset++] = xs[i]; + for (let i = end, _i = xs.length; i < _i; i++) ret[offset++] = xs[i]; + return ofSortedArray(ret); +} + +function subtractIS(a: I, b: S) { + const min = I.min(a), max = I.max(a); + + // is empty? + if (max < min) return a; + + const rSize = max - min + 1; + const interval = S.findRange(b, min, max); + const start = I.start(interval), end = I.end(interval); + const commonCount = end - start; + + // No common elements. + if (commonCount === 0) return a; + + const resultSize = rSize - commonCount; + // A is subset of B + if (resultSize <= 0) return Empty; + + const xs = S.values(b); + const ret = new Int32Array(resultSize); + const li = xs.length - 1; + const fst = xs[Math.min(start, li)], last = xs[Math.min(end, li)]; + let offset = 0; + for (let i = min; i < fst; i++) ret[offset++] = i; + for (let i = fst; i <= last; i++) { + if (S.indexOfInterval(b, i, interval) < 0) ret[offset++] = i; + } + 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 xs = S.values(a), ys = S.values(b); + const lenA = xs.length; + + const { startI: sI, startJ: sJ, endI, endJ } = getMaxIntersectionRange(a, b); + let i = sI, j = sJ; + let commonCount = 0; + while (i < endI && j < endJ) { + const x = xs[i], y = ys[j]; + if (x < y) { i++; } + else if (x > y) { j++; } + else { i++; j++; commonCount++; } + } + + // A isnt intersecting B ===> A + if (!commonCount) return xs; + // 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++] = xs[k]; + + i = sI; + j = sJ; + while (i < endI && j < endJ) { + const x = xs[i], y = ys[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++] = xs[i]; + + return ofSortedArray(indices); +} \ No newline at end of file diff --git a/src/mol-base/collections/impl/sorted-array.ts b/src/mol-base/collections/impl/sorted-array.ts index 134b1dbf3dd05afafec623a76fadf887e11059e0..376a4bf19705f382a2b695cda57709179f05ec39 100644 --- a/src/mol-base/collections/impl/sorted-array.ts +++ b/src/mol-base/collections/impl/sorted-array.ts @@ -34,6 +34,11 @@ export function indexOf(xs: Nums, v: number) { const l = xs.length; return l === 0 ? -1 : xs[0] <= v && v <= xs[l - 1] ? binarySearchRange(xs, v, 0, l) : -1; } +export function indexOfInterval(xs: Nums, v: number, bounds: Interval) { + const l = xs.length; + const s = Interval.start(bounds), e = Interval.end(bounds); + return l === 0 || e <= s ? -1 : xs[s] <= v && v <= xs[e - 1] ? binarySearchRange(xs, v, s, e) : -1; +} export function has(xs: Nums, v: number) { return indexOf(xs, v) >= 0; } export function getAt(xs: Nums, i: number) { return xs[i]; } diff --git a/src/mol-base/collections/interval.ts b/src/mol-base/collections/interval.ts index 5736efc0d973341eb2ad3eca6818f613f770b1e6..522d5eb7332f390df8937da8fde72a8e31614d42 100644 --- a/src/mol-base/collections/interval.ts +++ b/src/mol-base/collections/interval.ts @@ -9,6 +9,7 @@ import * as Impl from './impl/interval' namespace Interval { export const Empty: Interval = Impl.Empty as any; + export const ofSingleton: (value: number) => Interval = (v) => Impl.ofRange(v, v) as any; /** Create interval [min, max] */ export const ofRange: (min: number, max: number) => Interval = Impl.ofRange as any; /** Create interval [min, max) */ diff --git a/src/mol-base/collections/ordered-set.ts b/src/mol-base/collections/ordered-set.ts index c1a697a91713661d1af08c3659c16f70d9cd72e8..37b9806580ca9f53573b2e0271dd32413974c2f8 100644 --- a/src/mol-base/collections/ordered-set.ts +++ b/src/mol-base/collections/ordered-set.ts @@ -4,14 +4,12 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import * as Base from './ordered-set/base' +//import * as Base from './ordered-set/base' +import * as Base from './impl/ordered-set' +import Interval from './interval' import SegmentIterator from './ordered-set/segment-iterator' namespace OrderedSet { - /** Range of indices to be iterated as start <= i < end. */ - export interface IndexRange { start: number, end: number } - export function IndexRange(start?: number, end?: number): IndexRange { return { start: start || 0, end: end || 0 }; } - export const Empty: OrderedSet = Base.Empty as any; export const ofSingleton: (value: number) => OrderedSet = Base.ofSingleton as any; export const ofRange: (min: number, max: number) => OrderedSet = Base.ofRange as any; @@ -22,8 +20,8 @@ namespace OrderedSet { export const indexOf: (set: OrderedSet, x: number) => number = Base.indexOf as any; export const getAt: (set: OrderedSet, i: number) => number = Base.getAt as any; - export const min: (set: OrderedSet) => number = Base.minValue as any; - export const max: (set: OrderedSet) => number = Base.maxValue as any; + export const min: (set: OrderedSet) => number = Base.min as any; + export const max: (set: OrderedSet) => number = Base.max as any; export const size: (set: OrderedSet) => number = Base.size as any; export const hashCode: (set: OrderedSet) => number = Base.hashCode as any; @@ -35,9 +33,9 @@ namespace OrderedSet { export const intersect: (a: OrderedSet, b: OrderedSet) => OrderedSet = Base.intersect as any; export const subtract: (a: OrderedSet, b: OrderedSet) => OrderedSet = Base.subtract as any; - export const findPredecessorIndex: (set: OrderedSet, x: number) => number = Base.getPredIndex as any; - export const findPredecessorIndexInRange: (set: OrderedSet, x: number, range: IndexRange) => number = Base.getPredIndexInRange as any; - export const findIntervalRange: (set: OrderedSet, min: number, max: number) => IndexRange = Base.getIntervalRange as any; + export const findPredecessorIndex: (set: OrderedSet, x: number) => number = Base.findPredecessorIndex as any; + export const findPredecessorIndexInRange: (set: OrderedSet, x: number, range: Interval) => number = Base.findPredecessorIndexInInterval as any; + export const findRange: (set: OrderedSet, min: number, max: number) => Interval = Base.findRange as any; export const segments = SegmentIterator; } diff --git a/src/mol-base/collections/ordered-set/segment-iterator.ts b/src/mol-base/collections/ordered-set/segment-iterator.ts index f5c7a712a2205339426d6fdc94be722269f8b0d6..634f7d245e7b6ba9d476b2322d7f53d4f3900a49 100644 --- a/src/mol-base/collections/ordered-set/segment-iterator.ts +++ b/src/mol-base/collections/ordered-set/segment-iterator.ts @@ -6,14 +6,17 @@ import Iterator from '../iterator' import OrderedSet from '../ordered-set' +import Interval from '../interval' -class SegmentIterator implements Iterator<{ segment: number } & OrderedSet.IndexRange> { - private segmentRange = OrderedSet.IndexRange(); - private setRange = OrderedSet.IndexRange(); +class SegmentIterator implements Iterator<{ segment: number, start: number, end: number }> { + private segmentStart = 0; + private segmentEnd = 0; + // private segmentRange = Interval.Empty; + private setRange = Interval.Empty; private value = { segment: 0, start: 0, end: 0 }; private last: number = 0; - [Symbol.iterator]() { return new SegmentIterator(this.segments, this.set, this.start, this.end); }; + [Symbol.iterator]() { return new SegmentIterator(this.segments, this.set, this.inputRange); }; done: boolean = false; next() { @@ -22,12 +25,12 @@ class SegmentIterator implements Iterator<{ segment: number } & OrderedSet.Index } move() { - this.done = this.segmentRange.end <= this.segmentRange.start; + this.done = this.segmentEnd <= this.segmentStart; while (!this.done) { if (!this.updateValue()) { this.updateSegmentRange(); } else { - this.value.segment = this.segmentRange.start++; + this.value.segment = this.segmentStart++; break; } } @@ -40,33 +43,34 @@ class SegmentIterator implements Iterator<{ segment: number } & OrderedSet.Index } private updateValue() { - const segmentEnd = OrderedSet.getAt(this.segments, this.segmentRange.start + 1); + const segmentEnd = OrderedSet.getAt(this.segments, this.segmentStart + 1); const setEnd = OrderedSet.findPredecessorIndexInRange(this.set, segmentEnd, this.setRange); - this.value.start = this.setRange.start; + this.value.start = Interval.start(this.setRange); this.value.end = setEnd; - this.setRange.start = setEnd; + this.setRange = Interval.ofBounds(setEnd, Interval.end(this.setRange)) + //this.setRange.start = setEnd; + //throw ''; return setEnd > this.value.start; } private updateSegmentRange() { - const min = OrderedSet.getAt(this.set, this.setRange.start), max = OrderedSet.getAt(this.set, this.setRange.end - 1); - this.segmentRange.start = this.getSegmentIndex(min); - this.segmentRange.end = this.getSegmentIndex(max) + 1; - this.done = this.segmentRange.end <= this.segmentRange.start; + const min = OrderedSet.getAt(this.set, Interval.min(this.setRange)); + const max = OrderedSet.getAt(this.set, Interval.max(this.setRange)); + this.segmentStart = this.getSegmentIndex(min); + this.segmentEnd = this.getSegmentIndex(max) + 1; + this.done = this.segmentEnd <= this.segmentStart; } - constructor(private segments: OrderedSet, private set: OrderedSet, private start: number, private end: number) { + constructor(private segments: OrderedSet, private set: OrderedSet, private inputRange: Interval) { this.last = OrderedSet.max(segments); - this.setRange.start = start; - this.setRange.end = end; + this.setRange = inputRange; this.updateSegmentRange(); } } -function createIterator(segments: OrderedSet, set: OrderedSet, range?: OrderedSet.IndexRange) { - const start = !!range ? range.start : 0; - const end = !!range ? range.end : OrderedSet.size(set); - return new SegmentIterator(segments, set, start, end); +function createIterator(segments: OrderedSet, set: OrderedSet, range?: Interval) { + const int = typeof range !== 'undefined' ? range : Interval.ofBounds(0, OrderedSet.size(set)); + return new SegmentIterator(segments, set, int); } export default createIterator \ No newline at end of file diff --git a/src/mol-base/collections/sorted-array.ts b/src/mol-base/collections/sorted-array.ts index 11c5c31abb79d483fa9cbf204ab8133c71522585..a98b9a0f9680141fea690e31c9b0172ec062acea 100644 --- a/src/mol-base/collections/sorted-array.ts +++ b/src/mol-base/collections/sorted-array.ts @@ -14,22 +14,25 @@ namespace SortedArray { 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: (interval: SortedArray, x: number) => boolean = Impl.has as any; - export const indexOf: (interval: SortedArray, x: number) => number = Impl.indexOf as any; - export const getAt: (interval: SortedArray, i: number) => number = Impl.getAt as any; + export const values: (array: SortedArray) => ArrayLike<number> = (xs) => xs as any; - export const start: (interval: SortedArray) => number = Impl.start as any; - export const end: (interval: SortedArray) => number = Impl.end as any; - export const min: (interval: SortedArray) => number = Impl.min as any; - export const max: (interval: SortedArray) => number = Impl.max as any; - export const size: (interval: SortedArray) => number = Impl.size as any; - export const hashCode: (interval: SortedArray) => number = Impl.hashCode 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 start: (array: SortedArray) => number = Impl.start as any; + export const end: (array: SortedArray) => number = Impl.end as any; + export const min: (array: SortedArray) => number = Impl.min as any; + export const max: (array: SortedArray) => number = Impl.max as any; + export const size: (array: SortedArray) => number = Impl.size as any; + export const hashCode: (array: SortedArray) => number = Impl.hashCode as any; export const areEqual: (a: SortedArray, b: SortedArray) => boolean = Impl.areEqual as any; - export const findPredecessorIndex: (interval: SortedArray, x: number) => number = Impl.findPredecessorIndex as any; - export const findPredecessorIndexInInterval: (interval: SortedArray, x: number, bounds: Interval) => number = Impl.findPredecessorIndexInInterval as any; - export const findRange: (interval: SortedArray, min: number, max: number) => Interval = Impl.findRange 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 { '@type': 'int-sorted-array' } diff --git a/src/mol-data/atom-set/base.ts b/src/mol-data/atom-set/base.ts index ea1aff390ac057e3fcacd0633f0972012e5d8aa9..c5105a51e8440a8b65c8fb93e39ec45724a5e343 100644 --- a/src/mol-data/atom-set/base.ts +++ b/src/mol-data/atom-set/base.ts @@ -7,6 +7,7 @@ import OrderedSet from '../../mol-base/collections/ordered-set' import Iterator from '../../mol-base/collections/iterator' import IntTuple from '../../mol-base/collections/int-tuple' +import Interval from '../../mol-base/collections/interval' import { sortArray } from '../../mol-base/collections/sort' import { hash1 } from '../../mol-base/collections/hash-functions' @@ -345,7 +346,8 @@ 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 { start, end } = OrderedSet.findIntervalRange(keysA, OrderedSet.min(keysB), OrderedSet.max(keysB)); + const r = OrderedSet.findRange(keysA, OrderedSet.min(keysB), OrderedSet.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; @@ -363,7 +365,8 @@ function intersectEE(a: AtomSetElements, b: AtomSetElements) { const keysA = a.keys, keysB = b.keys; if (!OrderedSet.areIntersecting(a.keys, b.keys)) return Empty; - const { start, end } = OrderedSet.findIntervalRange(keysA, OrderedSet.min(keysB), OrderedSet.max(keysB)); + const r = OrderedSet.findRange(keysA, OrderedSet.min(keysB), OrderedSet.max(keysB)); + const start = Interval.start(r), end = Interval.end(r); const keys = [], ret = Object.create(null); for (let i = start; i < end; i++) { @@ -409,7 +412,8 @@ function subtractEE(a: AtomSetElements, b: AtomSetElements) { const keysA = a.keys, keysB = b.keys; if (!OrderedSet.areIntersecting(a.keys, b.keys)) return Empty; - const { start, end } = OrderedSet.findIntervalRange(keysA, OrderedSet.min(keysB), OrderedSet.max(keysB)); + const r = OrderedSet.findRange(keysA, OrderedSet.min(keysB), OrderedSet.max(keysB)); + const start = Interval.start(r), end = Interval.end(r); const keys = [], ret = Object.create(null); for (let i = 0; i < start; i++) { diff --git a/src/perf-tests/sets.ts b/src/perf-tests/sets.ts index 4930c3e84826bcea4508daa695e64b4203d441f5..1a22451b85e60c0a090b00040a3253f4544e961e 100644 --- a/src/perf-tests/sets.ts +++ b/src/perf-tests/sets.ts @@ -279,7 +279,9 @@ export function testSegments() { } } -Tuples.run(); +testSegments(); + +//Tuples.run(); // interface AA { kind: 'a' } // //interface BB { kind: 'b' }