From 40366b4d7bc11773f0272ce67c85c351221761a5 Mon Sep 17 00:00:00 2001 From: David Sehnal <david.sehnal@gmail.com> Date: Fri, 27 Oct 2017 23:22:48 +0200 Subject: [PATCH] segmentation & refactoring --- src/mol-base/_spec/collections.spec.ts | 289 ---------- .../collections/_spec/equiv-index.spec.ts | 18 + .../collections/_spec/int-tuple.spec.ts | 19 + .../collections/_spec/interval.spec.ts | 73 +++ .../collections/_spec/iterators.spec.ts | 26 + .../collections/_spec/linked-index.spec.ts | 50 ++ .../_spec/ordered-set.spec.ts | 22 +- .../collections/_spec/segmentation.spec.ts | 36 ++ src/mol-base/collections/_spec/sort.spec.ts | 90 ++++ .../collections/_spec/sorted-array.spec.ts | 50 ++ .../segmentation.ts} | 35 +- src/mol-base/collections/ordered-set.ts | 4 +- src/mol-base/collections/ordered-set/base.ts | 501 ------------------ src/mol-base/collections/segmentation.ts | 26 + src/perf-tests/sets.ts | 8 +- 15 files changed, 420 insertions(+), 827 deletions(-) delete mode 100644 src/mol-base/_spec/collections.spec.ts create mode 100644 src/mol-base/collections/_spec/equiv-index.spec.ts create mode 100644 src/mol-base/collections/_spec/int-tuple.spec.ts create mode 100644 src/mol-base/collections/_spec/interval.spec.ts create mode 100644 src/mol-base/collections/_spec/iterators.spec.ts create mode 100644 src/mol-base/collections/_spec/linked-index.spec.ts rename src/mol-base/{ => collections}/_spec/ordered-set.spec.ts (92%) create mode 100644 src/mol-base/collections/_spec/segmentation.spec.ts create mode 100644 src/mol-base/collections/_spec/sort.spec.ts create mode 100644 src/mol-base/collections/_spec/sorted-array.spec.ts rename src/mol-base/collections/{ordered-set/segment-iterator.ts => impl/segmentation.ts} (67%) delete mode 100644 src/mol-base/collections/ordered-set/base.ts create mode 100644 src/mol-base/collections/segmentation.ts diff --git a/src/mol-base/_spec/collections.spec.ts b/src/mol-base/_spec/collections.spec.ts deleted file mode 100644 index 56a7c6009..000000000 --- a/src/mol-base/_spec/collections.spec.ts +++ /dev/null @@ -1,289 +0,0 @@ -/** - * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ - -import Iterator from '../collections/iterator' -import IntTuple from '../collections/int-tuple' -import * as Sort from '../collections/sort' -import LinkedIndex from '../collections/linked-index' -import EquivalenceClasses from '../collections/equivalence-classes' -import Interval from '../collections/interval' -import SortedArray from '../collections/sorted-array' - -function iteratorToArray<T>(it: Iterator<T>): T[] { - const ret = []; - for (let v = it.move(); !it.done; v = it.move()) ret[ret.length] = v; - return ret; -} - -describe('basic iterators', () => { - function check<T>(name: string, iter: Iterator<T>, expected: T[]) { - it(name, () => { - expect(iteratorToArray(iter)).toEqual(expected); - }); - } - - check('empty', Iterator.Empty, []); - check('singleton', Iterator.Value(10), [10]); - check('array', Iterator.Array([1, 2, 3]), [1, 2, 3]); - check('range', Iterator.Range(0, 3), [0, 1, 2, 3]); -}); - -describe('int pair', () => { - it('works', () => { - for (let i = 0; i < 10; i++) { - for (let j = -10; j < 5; j++) { - const t = IntTuple.create(i, j); - expect(IntTuple.fst(t)).toBe(i); - expect(IntTuple.snd(t)).toBe(j); - } - } - }) -}) - -function shuffle<T>(data: T, len: number, clone: (s: T) => T, swap: Sort.Swapper = Sort.arraySwap) { - const a = clone(data); - for (let i = len - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)); - swap(a, i, j); - } - return a; -} - -function shuffleArray(data: any[]) { - return shuffle(data, data.length, t => [...t]); -} - -describe('qsort-array asc', () => { - const data0 = new Array(50); - for (let i = 0; i < data0.length; i++) data0[i] = i; - const data1 = [1, 1, 2, 2, 3, 3, 4, 4, 4, 6, 6, 6]; - - function test(name: string, data: any[], randomize: boolean) { - it(name, () => { - // [ 3, 1, 6, 4, 4, 6, 4, 2, 6, 1, 2, 3 ]; - if (randomize) { - for (let i = 0; i < 10; i++) { - expect(Sort.sortArray(shuffleArray(data))).toEqual(data); - } - } else { - expect(Sort.sortArray([...data])).toEqual(data); - } - }); - } - test('uniq', data0, false); - test('uniq shuffle', data0, true); - test('rep', data1, false); - test('rep shuffle', data1, true); -}) - -describe('qsort-array generic', () => { - const data0 = new Array(50); - for (let i = 0; i < data0.length; i++) data0[i] = i; - const data1 = [1, 1, 2, 2, 3, 3, 4, 4, 4, 6, 6, 6]; - - function test(name: string, data: any[], randomize: boolean) { - it(name, () => { - // [ 3, 1, 6, 4, 4, 6, 4, 2, 6, 1, 2, 3 ]; - if (randomize) { - for (let i = 0; i < 10; i++) { - expect(Sort.sort(shuffleArray(data), 0, data.length, Sort.arrayLess, Sort.arraySwap)).toEqual(data); - } - } else { - expect(Sort.sort([...data], 0, data.length, Sort.arrayLess, Sort.arraySwap)).toEqual(data); - } - }); - } - test('uniq', data0, false); - test('uniq shuffle', data0, true); - test('rep', data1, false); - test('rep shuffle', data1, true); -}) - -describe('qsort-dual array', () => { - const len = 3; - const data = { xs: [0, 1, 2], ys: ['x', 'y', 'z'] }; - - const cmp: Sort.Comparer<typeof data> = (data, i, j) => data.xs[i] - data.xs[j]; - const swap: Sort.Swapper<typeof data> = (data, i, j) => { Sort.arraySwap(data.xs, i, j); Sort.arraySwap(data.ys, i, j); } - const clone = (d: typeof data) => ({ xs: [...d.xs], ys: [...d.ys] }) - - function test(name: string, src: typeof data, randomize: boolean) { - it(name, () => { - // [ 3, 1, 6, 4, 4, 6, 4, 2, 6, 1, 2, 3 ]; - if (randomize) { - for (let i = 0; i < 10; i++) { - expect(Sort.sort(shuffle(src, len, clone, swap), 0, len, cmp, swap)).toEqual(data); - } - } else { - expect(Sort.sort(clone(src), 0, len, cmp, swap)).toEqual(data); - } - }); - } - test('sorted', data, false); - test('shuffled', data, true); -}) - -describe('interval', () => { - function testI(name: string, a: Interval, b: Interval) { - it(name, () => expect(Interval.areEqual(a, b)).toBe(true)); - } - - function test(name: string, a: any, b: any) { - it(name, () => expect(a).toEqual(b)); - } - - const e = Interval.Empty; - const r05 = Interval.ofRange(0, 5); - const se05 = Interval.ofBounds(0, 5); - - test('size', Interval.size(e), 0); - test('size', Interval.size(r05), 6); - test('size', Interval.size(se05), 5); - - test('min/max', [Interval.min(e), Interval.max(e)], [0, -1]); - test('min/max', [Interval.min(r05), Interval.max(r05)], [0, 5]); - test('min/max', [Interval.min(se05), Interval.max(se05)], [0, 4]); - - test('start/end', [Interval.start(e), Interval.end(e)], [0, 0]); - test('start/end', [Interval.start(r05), Interval.end(r05)], [0, 6]); - test('start/end', [Interval.start(se05), Interval.end(se05)], [0, 5]); - - test('has', Interval.has(e, 5), false); - test('has', Interval.has(r05, 5), true); - test('has', Interval.has(r05, 6), false); - test('has', Interval.has(r05, -1), false); - test('has', Interval.has(se05, 5), false); - test('has', Interval.has(se05, 4), true); - - test('indexOf', Interval.indexOf(e, 5), -1); - test('indexOf', Interval.indexOf(r05, 5), 5); - test('indexOf', Interval.indexOf(r05, 6), -1); - - test('getAt', Interval.getAt(r05, 5), 5); - - test('areEqual', Interval.areEqual(r05, se05), false); - test('areIntersecting1', Interval.areIntersecting(r05, se05), true); - test('areIntersecting2', Interval.areIntersecting(r05, e), false); - test('areIntersecting3', Interval.areIntersecting(e, r05), false); - test('areIntersecting4', Interval.areIntersecting(e, e), true); - - test('areIntersecting5', Interval.areIntersecting(Interval.ofRange(0, 5), Interval.ofRange(-4, 3)), true); - test('areIntersecting6', Interval.areIntersecting(Interval.ofRange(0, 5), Interval.ofRange(-4, -3)), false); - test('areIntersecting7', Interval.areIntersecting(Interval.ofRange(0, 5), Interval.ofRange(1, 2)), true); - test('areIntersecting8', Interval.areIntersecting(Interval.ofRange(0, 5), Interval.ofRange(3, 6)), true); - - test('isSubInterval', Interval.isSubInterval(Interval.ofRange(0, 5), Interval.ofRange(3, 6)), false); - test('isSubInterval', Interval.isSubInterval(Interval.ofRange(0, 5), Interval.ofRange(3, 5)), true); - - testI('intersect', Interval.intersect(Interval.ofRange(0, 5), Interval.ofRange(-4, 3)), Interval.ofRange(0, 3)); - testI('intersect1', Interval.intersect(Interval.ofRange(0, 5), Interval.ofRange(1, 3)), Interval.ofRange(1, 3)); - testI('intersect2', Interval.intersect(Interval.ofRange(0, 5), Interval.ofRange(3, 5)), Interval.ofRange(3, 5)); - testI('intersect3', Interval.intersect(Interval.ofRange(0, 5), Interval.ofRange(-4, -3)), Interval.Empty); - - test('predIndex1', Interval.findPredecessorIndex(r05, 5), 5); - test('predIndex2', Interval.findPredecessorIndex(r05, -1), 0); - test('predIndex3', Interval.findPredecessorIndex(r05, 6), 6); - test('predIndexInt', Interval.findPredecessorIndexInInterval(r05, 0, Interval.ofRange(2, 3)), 2); - test('predIndexInt1', Interval.findPredecessorIndexInInterval(r05, 4, Interval.ofRange(2, 3)), 4); - - testI('findRange', Interval.findRange(r05, 2, 3), Interval.ofRange(2, 3)); -}); - -describe('sortedArray', () => { - function testI(name: string, a: Interval, b: Interval) { - it(name, () => expect(Interval.areEqual(a, b)).toBe(true)); - } - - function test(name: string, a: any, b: any) { - it(name, () => expect(a).toEqual(b)); - } - - const a1234 = SortedArray.ofSortedArray([1, 2, 3, 4]); - const a2468 = SortedArray.ofSortedArray([2, 4, 6, 8]); - - test('size', SortedArray.size(a1234), 4); - - test('min/max', [SortedArray.min(a1234), SortedArray.max(a1234)], [1, 4]); - test('start/end', [SortedArray.start(a1234), SortedArray.end(a1234)], [1, 5]); - - test('has', SortedArray.has(a1234, 5), false); - test('has', SortedArray.has(a1234, 4), true); - - it('has-all', () => { - for (let i = 1; i <= 4; i++) expect(SortedArray.has(a1234, i)).toBe(true); - }); - - test('indexOf', SortedArray.indexOf(a2468, 5), -1); - test('indexOf', SortedArray.indexOf(a2468, 2), 0); - - test('getAt', SortedArray.getAt(a2468, 1), 4); - - test('areEqual', SortedArray.areEqual(a2468, a2468), true); - test('areEqual1', SortedArray.areEqual(a2468, SortedArray.create([4, 2, 8, 6])), true); - test('areEqual2', SortedArray.areEqual(a1234, a2468), false); - - test('predIndex1', SortedArray.findPredecessorIndex(a1234, 5), 4); - test('predIndex2', SortedArray.findPredecessorIndex(a1234, 2), 1); - test('predIndex3', SortedArray.findPredecessorIndex(a2468, 4), 1); - test('predIndex4', SortedArray.findPredecessorIndex(a2468, 3), 1); - test('predIndexInt', SortedArray.findPredecessorIndexInInterval(a1234, 0, Interval.ofRange(2, 3)), 2); - - testI('findRange', SortedArray.findRange(a2468, 2, 4), Interval.ofRange(0, 1)); -}); - -describe('linked-index', () => { - it('initial state', () => { - const index = LinkedIndex(2); - expect(index.head).toBe(0); - expect(index.has(0)).toBe(true); - expect(index.has(1)).toBe(true); - }); - - it('singleton', () => { - const index = LinkedIndex(1); - expect(index.head).toBe(0); - expect(index.has(0)).toBe(true); - index.remove(0); - expect(index.head).toBe(-1); - expect(index.has(0)).toBe(false); - }); - - it('remove 0', () => { - const index = LinkedIndex(2); - index.remove(0); - expect(index.head).toBe(1); - expect(index.has(0)).toBe(false); - expect(index.has(1)).toBe(true); - }); - - it('remove 1', () => { - const index = LinkedIndex(2); - index.remove(1); - expect(index.head).toBe(0); - expect(index.has(0)).toBe(true); - expect(index.has(1)).toBe(false); - }); - - it('remove 01', () => { - const index = LinkedIndex(2); - index.remove(0); - index.remove(1); - expect(index.head).toBe(-1); - expect(index.has(0)).toBe(false); - expect(index.has(1)).toBe(false); - }); -}); - -describe('equiv-classes', () => { - it('integer mod classes', () => { - const cls = EquivalenceClasses<number, number>(x => x % 2, (a, b) => (a - b) % 2 === 0); - for (let i = 0; i < 6; i++) cls.add(i, i); - - expect(cls.groups.length).toBe(2); - expect(cls.groups[0]).toEqual([0, 2, 4]); - expect(cls.groups[1]).toEqual([1, 3, 5]); - }); -}); \ No newline at end of file diff --git a/src/mol-base/collections/_spec/equiv-index.spec.ts b/src/mol-base/collections/_spec/equiv-index.spec.ts new file mode 100644 index 000000000..967bea3b0 --- /dev/null +++ b/src/mol-base/collections/_spec/equiv-index.spec.ts @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import EquivalenceClasses from '../equivalence-classes' + +describe('equiv-classes', () => { + it('integer mod classes', () => { + const cls = EquivalenceClasses<number, number>(x => x % 2, (a, b) => (a - b) % 2 === 0); + for (let i = 0; i < 6; i++) cls.add(i, i); + + expect(cls.groups.length).toBe(2); + expect(cls.groups[0]).toEqual([0, 2, 4]); + expect(cls.groups[1]).toEqual([1, 3, 5]); + }); +}); \ No newline at end of file diff --git a/src/mol-base/collections/_spec/int-tuple.spec.ts b/src/mol-base/collections/_spec/int-tuple.spec.ts new file mode 100644 index 000000000..acdfb01b6 --- /dev/null +++ b/src/mol-base/collections/_spec/int-tuple.spec.ts @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import IntTuple from '../int-tuple' + +describe('int pair', () => { + it('works', () => { + for (let i = 0; i < 10; i++) { + for (let j = -10; j < 5; j++) { + const t = IntTuple.create(i, j); + expect(IntTuple.fst(t)).toBe(i); + expect(IntTuple.snd(t)).toBe(j); + } + } + }) +}) \ No newline at end of file diff --git a/src/mol-base/collections/_spec/interval.spec.ts b/src/mol-base/collections/_spec/interval.spec.ts new file mode 100644 index 000000000..cda22df8e --- /dev/null +++ b/src/mol-base/collections/_spec/interval.spec.ts @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import Interval from '../interval' + +describe('interval', () => { + function testI(name: string, a: Interval, b: Interval) { + it(name, () => expect(Interval.areEqual(a, b)).toBe(true)); + } + + function test(name: string, a: any, b: any) { + it(name, () => expect(a).toEqual(b)); + } + + const e = Interval.Empty; + const r05 = Interval.ofRange(0, 5); + const se05 = Interval.ofBounds(0, 5); + + test('size', Interval.size(e), 0); + test('size', Interval.size(r05), 6); + test('size', Interval.size(se05), 5); + + test('min/max', [Interval.min(e), Interval.max(e)], [0, -1]); + test('min/max', [Interval.min(r05), Interval.max(r05)], [0, 5]); + test('min/max', [Interval.min(se05), Interval.max(se05)], [0, 4]); + + test('start/end', [Interval.start(e), Interval.end(e)], [0, 0]); + test('start/end', [Interval.start(r05), Interval.end(r05)], [0, 6]); + test('start/end', [Interval.start(se05), Interval.end(se05)], [0, 5]); + + test('has', Interval.has(e, 5), false); + test('has', Interval.has(r05, 5), true); + test('has', Interval.has(r05, 6), false); + test('has', Interval.has(r05, -1), false); + test('has', Interval.has(se05, 5), false); + test('has', Interval.has(se05, 4), true); + + test('indexOf', Interval.indexOf(e, 5), -1); + test('indexOf', Interval.indexOf(r05, 5), 5); + test('indexOf', Interval.indexOf(r05, 6), -1); + + test('getAt', Interval.getAt(r05, 5), 5); + + test('areEqual', Interval.areEqual(r05, se05), false); + test('areIntersecting1', Interval.areIntersecting(r05, se05), true); + test('areIntersecting2', Interval.areIntersecting(r05, e), false); + test('areIntersecting3', Interval.areIntersecting(e, r05), false); + test('areIntersecting4', Interval.areIntersecting(e, e), true); + + test('areIntersecting5', Interval.areIntersecting(Interval.ofRange(0, 5), Interval.ofRange(-4, 3)), true); + test('areIntersecting6', Interval.areIntersecting(Interval.ofRange(0, 5), Interval.ofRange(-4, -3)), false); + test('areIntersecting7', Interval.areIntersecting(Interval.ofRange(0, 5), Interval.ofRange(1, 2)), true); + test('areIntersecting8', Interval.areIntersecting(Interval.ofRange(0, 5), Interval.ofRange(3, 6)), true); + + test('isSubInterval', Interval.isSubInterval(Interval.ofRange(0, 5), Interval.ofRange(3, 6)), false); + test('isSubInterval', Interval.isSubInterval(Interval.ofRange(0, 5), Interval.ofRange(3, 5)), true); + + testI('intersect', Interval.intersect(Interval.ofRange(0, 5), Interval.ofRange(-4, 3)), Interval.ofRange(0, 3)); + testI('intersect1', Interval.intersect(Interval.ofRange(0, 5), Interval.ofRange(1, 3)), Interval.ofRange(1, 3)); + testI('intersect2', Interval.intersect(Interval.ofRange(0, 5), Interval.ofRange(3, 5)), Interval.ofRange(3, 5)); + testI('intersect3', Interval.intersect(Interval.ofRange(0, 5), Interval.ofRange(-4, -3)), Interval.Empty); + + test('predIndex1', Interval.findPredecessorIndex(r05, 5), 5); + test('predIndex2', Interval.findPredecessorIndex(r05, -1), 0); + test('predIndex3', Interval.findPredecessorIndex(r05, 6), 6); + test('predIndexInt', Interval.findPredecessorIndexInInterval(r05, 0, Interval.ofRange(2, 3)), 2); + test('predIndexInt1', Interval.findPredecessorIndexInInterval(r05, 4, Interval.ofRange(2, 3)), 4); + + testI('findRange', Interval.findRange(r05, 2, 3), Interval.ofRange(2, 3)); +}); \ No newline at end of file diff --git a/src/mol-base/collections/_spec/iterators.spec.ts b/src/mol-base/collections/_spec/iterators.spec.ts new file mode 100644 index 000000000..fba704b58 --- /dev/null +++ b/src/mol-base/collections/_spec/iterators.spec.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import Iterator from '../iterator' + +function iteratorToArray<T>(it: Iterator<T>): T[] { + const ret = []; + for (let v = it.move(); !it.done; v = it.move()) ret[ret.length] = v; + return ret; +} + +describe('basic iterators', () => { + function check<T>(name: string, iter: Iterator<T>, expected: T[]) { + it(name, () => { + expect(iteratorToArray(iter)).toEqual(expected); + }); + } + + check('empty', Iterator.Empty, []); + check('singleton', Iterator.Value(10), [10]); + check('array', Iterator.Array([1, 2, 3]), [1, 2, 3]); + check('range', Iterator.Range(0, 3), [0, 1, 2, 3]); +}); \ No newline at end of file diff --git a/src/mol-base/collections/_spec/linked-index.spec.ts b/src/mol-base/collections/_spec/linked-index.spec.ts new file mode 100644 index 000000000..9f811f2e9 --- /dev/null +++ b/src/mol-base/collections/_spec/linked-index.spec.ts @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import LinkedIndex from '../linked-index' + +describe('linked-index', () => { + it('initial state', () => { + const index = LinkedIndex(2); + expect(index.head).toBe(0); + expect(index.has(0)).toBe(true); + expect(index.has(1)).toBe(true); + }); + + it('singleton', () => { + const index = LinkedIndex(1); + expect(index.head).toBe(0); + expect(index.has(0)).toBe(true); + index.remove(0); + expect(index.head).toBe(-1); + expect(index.has(0)).toBe(false); + }); + + it('remove 0', () => { + const index = LinkedIndex(2); + index.remove(0); + expect(index.head).toBe(1); + expect(index.has(0)).toBe(false); + expect(index.has(1)).toBe(true); + }); + + it('remove 1', () => { + const index = LinkedIndex(2); + index.remove(1); + expect(index.head).toBe(0); + expect(index.has(0)).toBe(true); + expect(index.has(1)).toBe(false); + }); + + it('remove 01', () => { + const index = LinkedIndex(2); + index.remove(0); + index.remove(1); + expect(index.head).toBe(-1); + expect(index.has(0)).toBe(false); + expect(index.has(1)).toBe(false); + }); +}); \ No newline at end of file diff --git a/src/mol-base/_spec/ordered-set.spec.ts b/src/mol-base/collections/_spec/ordered-set.spec.ts similarity index 92% rename from src/mol-base/_spec/ordered-set.spec.ts rename to src/mol-base/collections/_spec/ordered-set.spec.ts index 11ff74e71..41b70f708 100644 --- a/src/mol-base/_spec/ordered-set.spec.ts +++ b/src/mol-base/collections/_spec/ordered-set.spec.ts @@ -4,8 +4,8 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import OrderedSet from '../collections/ordered-set' -import Interval from '../collections/interval' +import OrderedSet from '../ordered-set' +import Interval from '../interval' describe('ordered set', () => { function ordSetToArray(set: OrderedSet) { @@ -157,22 +157,4 @@ describe('ordered set', () => { 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/_spec/segmentation.spec.ts b/src/mol-base/collections/_spec/segmentation.spec.ts new file mode 100644 index 000000000..8cd02e248 --- /dev/null +++ b/src/mol-base/collections/_spec/segmentation.spec.ts @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import OrderedSet from '../ordered-set' +import Interval from '../interval' +import Segmentation from '../segmentation' +import SortedArray from '../sorted-array' + +describe('segments', () => { + const data = OrderedSet.ofSortedArray([4, 9, 10, 11, 14, 15, 16]); + const segs = Segmentation.create(SortedArray.ofSortedArray([0, 4, 10, 12, 13, 15, 25]), []) + + it('project', () => { + const p = Segmentation.projectValue(segs, data, 4); + expect(p).toBe(Interval.ofBounds(0, 2)) + }); + + it('iteration', () => { + const it = Segmentation.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.index]; + const v = OrderedSet.getAt(data, j); + if (!x) t[s.index] = [v]; + else x[x.length] = v; + } + } + + expect(t).toEqual({ 1: [4, 9], 2: [10, 11], 4: [14], 5: [15, 16] }); + }); +}); diff --git a/src/mol-base/collections/_spec/sort.spec.ts b/src/mol-base/collections/_spec/sort.spec.ts new file mode 100644 index 000000000..4b6773b76 --- /dev/null +++ b/src/mol-base/collections/_spec/sort.spec.ts @@ -0,0 +1,90 @@ +/** + * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import * as Sort from '../sort' + +function shuffle<T>(data: T, len: number, clone: (s: T) => T, swap: Sort.Swapper = Sort.arraySwap) { + const a = clone(data); + for (let i = len - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + swap(a, i, j); + } + return a; +} + +function shuffleArray(data: any[]) { + return shuffle(data, data.length, t => [...t]); +} + +describe('qsort-array asc', () => { + const data0 = new Array(50); + for (let i = 0; i < data0.length; i++) data0[i] = i; + const data1 = [1, 1, 2, 2, 3, 3, 4, 4, 4, 6, 6, 6]; + + function test(name: string, data: any[], randomize: boolean) { + it(name, () => { + // [ 3, 1, 6, 4, 4, 6, 4, 2, 6, 1, 2, 3 ]; + if (randomize) { + for (let i = 0; i < 10; i++) { + expect(Sort.sortArray(shuffleArray(data))).toEqual(data); + } + } else { + expect(Sort.sortArray([...data])).toEqual(data); + } + }); + } + test('uniq', data0, false); + test('uniq shuffle', data0, true); + test('rep', data1, false); + test('rep shuffle', data1, true); +}) + +describe('qsort-array generic', () => { + const data0 = new Array(50); + for (let i = 0; i < data0.length; i++) data0[i] = i; + const data1 = [1, 1, 2, 2, 3, 3, 4, 4, 4, 6, 6, 6]; + + function test(name: string, data: any[], randomize: boolean) { + it(name, () => { + // [ 3, 1, 6, 4, 4, 6, 4, 2, 6, 1, 2, 3 ]; + if (randomize) { + for (let i = 0; i < 10; i++) { + expect(Sort.sort(shuffleArray(data), 0, data.length, Sort.arrayLess, Sort.arraySwap)).toEqual(data); + } + } else { + expect(Sort.sort([...data], 0, data.length, Sort.arrayLess, Sort.arraySwap)).toEqual(data); + } + }); + } + test('uniq', data0, false); + test('uniq shuffle', data0, true); + test('rep', data1, false); + test('rep shuffle', data1, true); +}) + +describe('qsort-dual array', () => { + const len = 3; + const data = { xs: [0, 1, 2], ys: ['x', 'y', 'z'] }; + + const cmp: Sort.Comparer<typeof data> = (data, i, j) => data.xs[i] - data.xs[j]; + const swap: Sort.Swapper<typeof data> = (data, i, j) => { Sort.arraySwap(data.xs, i, j); Sort.arraySwap(data.ys, i, j); } + const clone = (d: typeof data) => ({ xs: [...d.xs], ys: [...d.ys] }) + + function test(name: string, src: typeof data, randomize: boolean) { + it(name, () => { + // [ 3, 1, 6, 4, 4, 6, 4, 2, 6, 1, 2, 3 ]; + if (randomize) { + for (let i = 0; i < 10; i++) { + expect(Sort.sort(shuffle(src, len, clone, swap), 0, len, cmp, swap)).toEqual(data); + } + } else { + expect(Sort.sort(clone(src), 0, len, cmp, swap)).toEqual(data); + } + }); + } + test('sorted', data, false); + test('shuffled', data, true); +}) \ No newline at end of file diff --git a/src/mol-base/collections/_spec/sorted-array.spec.ts b/src/mol-base/collections/_spec/sorted-array.spec.ts new file mode 100644 index 000000000..d0e3ab9e6 --- /dev/null +++ b/src/mol-base/collections/_spec/sorted-array.spec.ts @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import Interval from '../interval' +import SortedArray from '../sorted-array' + +describe('sortedArray', () => { + function testI(name: string, a: Interval, b: Interval) { + it(name, () => expect(Interval.areEqual(a, b)).toBe(true)); + } + + function test(name: string, a: any, b: any) { + it(name, () => expect(a).toEqual(b)); + } + + const a1234 = SortedArray.ofSortedArray([1, 2, 3, 4]); + const a2468 = SortedArray.ofSortedArray([2, 4, 6, 8]); + + test('size', SortedArray.size(a1234), 4); + + test('min/max', [SortedArray.min(a1234), SortedArray.max(a1234)], [1, 4]); + test('start/end', [SortedArray.start(a1234), SortedArray.end(a1234)], [1, 5]); + + test('has', SortedArray.has(a1234, 5), false); + test('has', SortedArray.has(a1234, 4), true); + + it('has-all', () => { + for (let i = 1; i <= 4; i++) expect(SortedArray.has(a1234, i)).toBe(true); + }); + + test('indexOf', SortedArray.indexOf(a2468, 5), -1); + test('indexOf', SortedArray.indexOf(a2468, 2), 0); + + test('getAt', SortedArray.getAt(a2468, 1), 4); + + test('areEqual', SortedArray.areEqual(a2468, a2468), true); + test('areEqual1', SortedArray.areEqual(a2468, SortedArray.create([4, 2, 8, 6])), true); + test('areEqual2', SortedArray.areEqual(a1234, a2468), false); + + test('predIndex1', SortedArray.findPredecessorIndex(a1234, 5), 4); + test('predIndex2', SortedArray.findPredecessorIndex(a1234, 2), 1); + test('predIndex3', SortedArray.findPredecessorIndex(a2468, 4), 1); + test('predIndex4', SortedArray.findPredecessorIndex(a2468, 3), 1); + test('predIndexInt', SortedArray.findPredecessorIndexInInterval(a1234, 0, Interval.ofRange(2, 3)), 2); + + testI('findRange', SortedArray.findRange(a2468, 2, 4), Interval.ofRange(0, 1)); +}); \ No newline at end of file diff --git a/src/mol-base/collections/ordered-set/segment-iterator.ts b/src/mol-base/collections/impl/segmentation.ts similarity index 67% rename from src/mol-base/collections/ordered-set/segment-iterator.ts rename to src/mol-base/collections/impl/segmentation.ts index 634f7d245..1fd21916e 100644 --- a/src/mol-base/collections/ordered-set/segment-iterator.ts +++ b/src/mol-base/collections/impl/segmentation.ts @@ -7,13 +7,30 @@ import Iterator from '../iterator' import OrderedSet from '../ordered-set' import Interval from '../interval' +import SortedArray from '../sorted-array' +import Segs from '../segmentation' -class SegmentIterator implements Iterator<{ segment: number, start: number, end: number }> { +type Segmentation = { segments: OrderedSet, segmentIndex: ArrayLike<number> } + +export function create(segments: SortedArray, segmentIndex: ArrayLike<number>): Segmentation { + return { segments, segmentIndex }; +} + +export function getSegment({ segmentIndex }: Segmentation, value: number) { + return segmentIndex[value]; +} + +export function projectValue({ segments }: Segmentation, set: OrderedSet, value: number): Interval { + const last = OrderedSet.max(segments); + const idx = value >= last ? -1 : OrderedSet.findPredecessorIndex(segments, value - 1); + return OrderedSet.findRange(set, OrderedSet.getAt(segments, idx), OrderedSet.getAt(segments, idx + 1) - 1); +} + +class SegmentIterator implements Iterator<Segs.Segment> { private segmentStart = 0; private segmentEnd = 0; - // private segmentRange = Interval.Empty; private setRange = Interval.Empty; - private value = { segment: 0, start: 0, end: 0 }; + private value: Segs.Segment = { index: 0, start: 0, end: 0 }; private last: number = 0; [Symbol.iterator]() { return new SegmentIterator(this.segments, this.set, this.inputRange); }; @@ -30,7 +47,7 @@ class SegmentIterator implements Iterator<{ segment: number, start: number, end: if (!this.updateValue()) { this.updateSegmentRange(); } else { - this.value.segment = this.segmentStart++; + this.value.index = this.segmentStart++; break; } } @@ -48,8 +65,6 @@ class SegmentIterator implements Iterator<{ segment: number, start: number, end: this.value.start = Interval.start(this.setRange); this.value.end = setEnd; this.setRange = Interval.ofBounds(setEnd, Interval.end(this.setRange)) - //this.setRange.start = setEnd; - //throw ''; return setEnd > this.value.start; } @@ -68,9 +83,7 @@ class SegmentIterator implements Iterator<{ segment: number, start: number, end: } } -function createIterator(segments: OrderedSet, set: OrderedSet, range?: Interval) { +export function segments(segs: Segmentation, 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 + return new SegmentIterator(segs.segments, set, int); +} \ No newline at end of file diff --git a/src/mol-base/collections/ordered-set.ts b/src/mol-base/collections/ordered-set.ts index 37b980658..1be957f36 100644 --- a/src/mol-base/collections/ordered-set.ts +++ b/src/mol-base/collections/ordered-set.ts @@ -36,10 +36,8 @@ namespace OrderedSet { 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; } -interface OrderedSet { '@type': 'int-ordered-set' } +interface OrderedSet { '@type': 'int-interval' | 'int-sorted-array' } export default OrderedSet \ No newline at end of file diff --git a/src/mol-base/collections/ordered-set/base.ts b/src/mol-base/collections/ordered-set/base.ts deleted file mode 100644 index b37bc56e1..000000000 --- a/src/mol-base/collections/ordered-set/base.ts +++ /dev/null @@ -1,501 +0,0 @@ -/** - * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. - * - * @author David Sehnal <david.sehnal@gmail.com> - */ - -import IntTuple from '../int-tuple' -import { hash3, hash4 } from '../hash-functions' - -type Range = IntTuple -type SortedArray = ArrayLike<number> -type OrderedSetImpl = Range | SortedArray - -export const Empty: OrderedSetImpl = IntTuple.create(0, -1) as any; -export function ofSingleton(value: number): OrderedSetImpl { return IntTuple.create(value, value) as any; } -export function ofRange(min: number, max: number): OrderedSetImpl { return max < min ? Empty : IntTuple.create(min, max) as any; } -/** It is the responsibility of the caller to ensure the array is sorted and contains unique values. */ -export function ofSortedArray(xs: SortedArray): 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 ofRange(xs[0], xs[xs.length - 1]); - return xs as any; -} - -export function size(set: OrderedSetImpl) { return typeof set === 'number' ? sizeR(set) : (set as SortedArray).length; } -export function has(set: OrderedSetImpl, x: number) { return typeof set === 'number' ? hasR(set, x) : hasA(set as SortedArray, x); } -export function indexOf(set: OrderedSetImpl, x: number) { return typeof set === 'number' ? indexOfR(set, x) : indexOfA(set as SortedArray, x); } -export function getAt(set: OrderedSetImpl, i: number) { return typeof set === 'number' ? elementAtR(set, i) : (set as SortedArray)[i]; } -export function minValue(set: OrderedSetImpl) { return typeof set === 'number' ? minR(set) : (set as SortedArray)[0]; } -export function maxValue(set: OrderedSetImpl) { return typeof set === 'number' ? maxR(set) : (set as SortedArray)[(set as SortedArray).length - 1]; } - -export function hashCode(set: OrderedSetImpl) { - // hash of tuple (size, min, max, mid) - const s = size(set); - if (!s) return 0; - if (s > 2) return hash4(s, getAt(set, 0), getAt(set, s - 1), getAt(set, s >> 1)); - return hash3(s, getAt(set, 0), getAt(set, s - 1)); -} -// TODO: possibly add more hash functions to allow for multilevel hashing. - -export function areEqual(a: OrderedSetImpl, b: OrderedSetImpl) { - if (typeof a === 'number') { - if (typeof b === 'number') return equalRR(a, b); - return false; - } else if (typeof b === 'number') return false; - return equalAA(a as SortedArray, b as SortedArray); -} - -export function areIntersecting(a: OrderedSetImpl, b: OrderedSetImpl) { - // if at least one is "range", they must now intersect - if (typeof a === 'number') { - if (typeof b === 'number') return equalRR(a, b) || areRangesIntersecting(a, b); - return areRangesIntersecting(a, b); - } - if (!areRangesIntersecting(a, b)) return false; - else if (typeof b === 'number') return false; - return areIntersectingAA(a as SortedArray, b as SortedArray); -} - -/** Check if the 2nd argument is a subset of the 1st */ -export function isSubset(set: OrderedSetImpl, toTest: OrderedSetImpl) { - if (!isRangeSubset(set, toTest)) return false; - const testSize = size(toTest); - if (typeof set === 'number' || !testSize) return true; - if (typeof toTest === 'number') return indexOf(set, maxR(toTest)) - indexOf(set, minR(toTest)) + 1 === testSize; - return isSubsetAA(set as SortedArray, toTest as SortedArray); -} - -export function getPredIndex(set: OrderedSetImpl, x: number) { - return typeof set === 'number' ? rangeSearchIndex(set, x) : binarySearchPredIndex(set as SortedArray, x); -} - -export function getPredIndexInRange(set: OrderedSetImpl, x: number, { start, end }: { start: number, end: number }) { - if (typeof set === 'number') { - const ret = rangeSearchIndex(set, x); - return ret <= start ? start : ret >= end ? end : ret; - } else return binarySearchPredIndexRange(set as SortedArray, x, start, end); -} - -export function getIntervalRange(set: OrderedSetImpl, min: number, max: number) { - const { start, end } = getStartEnd(set, min, max); - return { start, end }; -} - -export function union(a: OrderedSetImpl, b: OrderedSetImpl) { - if (typeof a === 'number') { - if (typeof b === 'number') return unionRR(a, b); - return unionAR(b as SortedArray, a); - } else if (typeof b === 'number') { - return unionAR(a as SortedArray, b); - } else return unionAA(a as SortedArray, b as SortedArray); -} - -export function intersect(a: OrderedSetImpl, b: OrderedSetImpl) { - if (typeof a === 'number') { - if (typeof b === 'number') return intersectRR(a, b); - return intersectAR(b as SortedArray, a); - } else if (typeof b === 'number') { - return intersectAR(a as SortedArray, b); - } else { - if (!areRangesIntersecting(a, b)) return Empty; - return intersectAA(a as SortedArray, b as SortedArray); - } -} - -export function subtract(a: OrderedSetImpl, b: OrderedSetImpl) { - if (!areRangesIntersecting(a, b)) return a; - - if (typeof a === 'number') { - if (typeof b === 'number') return substractRR(a, b); - return subtractRA(a, b as SortedArray); - } else if (typeof b === 'number') { - return subtractAR(a as SortedArray, b); - } else { - return subtractAA(a as SortedArray, b as SortedArray); - } -} - -const minR = IntTuple.fst -const maxR = IntTuple.snd -const equalRR = IntTuple.areEqual - -function sizeR(set: Range) { return maxR(set) - minR(set) + 1; } -function hasR(set: Range, x: number) { return x >= minR(set) && x <= maxR(set); } -function indexOfR(set: Range, x: number) { const m = minR(set); return x >= m && x <= maxR(set) ? x - m : -1; } -function elementAtR(set: Range, i: number) { return IntTuple.fst(set) + i; } - -function hasA(set: SortedArray, x: number) { return x >= set[0] && x <= set[set.length - 1] && binarySearch(set, x) >= 0; } -function indexOfA(set: SortedArray, x: number) { return x >= set[0] && x <= set[set.length - 1] ? binarySearch(set, x) : -1; } - -function binarySearch(xs: SortedArray, value: number) { - return binarySearchRange(xs, value, 0, xs.length); -} - -function binarySearchRange(xs: SortedArray, value: number, start: number, end: number) { - let min = start, max = end - 1; - while (min <= max) { - if (min + 11 > max) { - for (let i = min; i <= max; i++) { - if (value === xs[i]) return i; - } - return -1; - } - - const mid = (min + max) >> 1; - const v = xs[mid]; - if (value < v) max = mid - 1; - else if (value > v) min = mid + 1; - else return mid; - } - return -1; -} - -function binarySearchPredIndex(xs: SortedArray, value: number) { - return binarySearchPredIndexRange(xs, value, 0, xs.length); -} - -function binarySearchPredIndexRange(xs: SortedArray, value: number, start: number, end: number) { - let min = start, max = end - 1; - while (min < max) { - const mid = (min + max) >> 1; - const v = xs[mid]; - if (value < v) max = mid - 1; - else if (value > v) min = mid + 1; - else return mid; - } - if (min > max) return max + 1; - return xs[min] >= value ? min : min + 1; -} - -function rangeSearchIndex(r: Range, value: number) { - const min = minR(r); - if (value < min) return 0; - const max = maxR(r); - if (value > max) return max - min + 1; - return value - min; -} - -const _maxIntRangeRet = { startI: 0, startJ: 0, endI: 0, endJ: 0 }; -function getMaxIntersectionRange(xs: SortedArray, ys: SortedArray) { - _maxIntRangeRet.startI = binarySearchPredIndex(xs, ys[0]); - _maxIntRangeRet.startJ = binarySearchPredIndex(ys, xs[0]); - _maxIntRangeRet.endI = binarySearchPredIndex(xs, ys[ys.length - 1] + 1); - _maxIntRangeRet.endJ = binarySearchPredIndex(ys, xs[xs.length - 1] + 1); - - return _maxIntRangeRet; -} - -const _startEndRet = { start: 0, end: 0 }; -function getStartEnd(set: OrderedSetImpl, min: number, max: number) { - _startEndRet.start = getPredIndex(set, min); - _startEndRet.end = getPredIndex(set, max + 1); - return _startEndRet; -} - -function equalAA(a: SortedArray, b: SortedArray) { - if (a === b) return true; - const aSize = a.length; - if (aSize !== b.length || a[0] !== b[0] || a[aSize - 1] !== b[aSize - 1]) return false; - for (let i = 0; i < aSize; i++) { - if (a[i] !== b[i]) return false; - } - return true; -} - -function areIntersectingAA(xs: SortedArray, ys: SortedArray) { - if (xs === ys) return true; - - let { startI: i, startJ: j, endI, endJ } = getMaxIntersectionRange(xs, ys); - 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 isSubsetAA(a: SortedArray, b: SortedArray) { - if (a === b) return true; - - const lenB = b.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 = 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) { - return size(a) > 0 && size(b) > 0 && maxValue(a) >= minValue(b) && minValue(a) <= maxValue(b); -} - -function isRangeSubset(a: OrderedSetImpl, b: OrderedSetImpl) { - if (!size(a)) return size(b) === 0; - if (!size(b)) return true; - return minValue(a) <= minValue(b) && maxValue(a) >= maxValue(b); -} - -function unionRR(a: Range, b: Range) { - if (IntTuple.areEqual(a, b)) return a; - - const sizeA = sizeR(a), sizeB = sizeR(b); - if (!sizeA) return b; - if (!sizeB) return a; - const minA = minR(a), minB = minR(b); - if (areRangesIntersecting(a, b)) return ofRange(Math.min(minA, minB), Math.max(maxR(a), maxR(b))); - let lSize, lMin, rSize, rMin; - if (minR(a) < minR(b)) { 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 unionAR(a: SortedArray, b: Range) { - const bSize = size(b); - if (!bSize) return a; - // is the array fully contained in the range? - if (isRangeSubset(b, a)) return b; - - const min = minR(b), max = maxR(b); - const { start, end } = getStartEnd(a, min, max); - - const indices = new Int32Array(start + (a.length - end) + bSize); - let offset = 0; - for (let i = 0; i < start; i++) indices[offset++] = a[i]; - for (let i = min; i <= max; i++) indices[offset++] = i; - for (let i = end, _i = a.length; i < _i; i++) indices[offset] = a[i]; - - return ofSortedArray(indices); -} - -function unionAA(a: SortedArray, b: SortedArray) { - if (a === b) return a; - - 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 = 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 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 intersectRR(a: Range, b: Range) { - if (!areRangesIntersecting(a, b)) return Empty; - if (IntTuple.areEqual(a, b)) return a; - return ofRange(Math.max(minR(a), minR(b)), Math.min(maxR(a), maxR(b))); -} - -function intersectAR(a: SortedArray, r: Range) { - if (!size(r)) return Empty; - - const { start, end } = getStartEnd(a, minR(r), maxR(r)); - const resultSize = end - start; - if (!resultSize) return Empty; - - const indices = new Int32Array(resultSize); - let offset = 0; - for (let i = start; i < end; i++) { - indices[offset++] = a[i]; - } - return ofSortedArray(indices); -} - -function intersectAA(a: SortedArray, b: SortedArray) { - if (a === b) return a; - - 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 = 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 substractRR(a: Range, b: Range) { - if (IntTuple.areEqual(a, b)) return Empty; - - const minA = minR(a), maxA = maxR(a); - const minB = minR(b), maxB = maxR(b); - - if (maxA < minA || maxB < minB) return a; - // is A subset of B? ==> Empty - if (isRangeSubset(b, a)) return Empty; - if (isRangeSubset(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 ofRange(maxB + 1, maxB + r); - if (r <= 0) return 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); - } - // non intersecting ranges are handled by top-level substract. - // at this point, b either contains rA.fst or rA.snd, but not both. - if (minA < minB) return ofRange(minA, minB - 1); - return ofRange(maxB + 1, maxA); -} - -function subtractAR(a: SortedArray, b: Range) { - // is empty? - const min = minR(b), max = maxR(b); - if (max < min) return a; - - const { start, end } = getStartEnd(a, min, max); - const resultSize = a.length - (end - start); - // A is subset of B - if (resultSize <= 0) return Empty; - // No common elements - if (resultSize === a.length) return a; - - const ret = new Int32Array(resultSize); - let offset = 0; - for (let i = 0; i < start; i++) ret[offset++] = a[i]; - for (let i = end, _i = a.length; i < _i; i++) ret[offset++] = a[i]; - return ofSortedArray(ret); -} - -function subtractRA(a: Range, b: SortedArray) { - const min = minR(a), max = maxR(a); - - // is empty? - if (max < min) return a; - - const rSize = max - min + 1; - const { start, end } = getStartEnd(b, min, max); - 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 ret = new Int32Array(resultSize); - const li = b.length - 1; - const fst = b[Math.min(start, li)], last = b[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 (binarySearchRange(b, i, start, end) < 0) ret[offset++] = i; - } - for (let i = last + 1; i <= max; i++) ret[offset++] = i; - return ofSortedArray(ret); -} - -function subtractAA(a: SortedArray, b: SortedArray) { - if (a === b) return Empty; - - const lenA = a.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 = 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/segmentation.ts b/src/mol-base/collections/segmentation.ts new file mode 100644 index 000000000..a270e7a23 --- /dev/null +++ b/src/mol-base/collections/segmentation.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import Iterator from './iterator' +import Interval from './interval' +import OrderedSet from './ordered-set' +import SortedArray from './sorted-array' +import * as Impl from './impl/segmentation' + +namespace Segmentation { + export interface Segment { index: number, start: number, end: number } + + export const create: (segs: SortedArray, segIndex: ArrayLike<number>) => Segmentation = Impl.create as any; + + export const getSegment: (segs: Segmentation, value: number) => number = Impl.getSegment as any; + export const projectValue: (segs: Segmentation, set: OrderedSet, value: number) => Interval = Impl.projectValue as any; + + export const segments: (segs: Segmentation, set: OrderedSet, range?: Interval) => Iterator<Segment> = Impl.segments as any; +} + +interface Segmentation { '@type': 'segmentation' } + +export default Segmentation \ No newline at end of file diff --git a/src/perf-tests/sets.ts b/src/perf-tests/sets.ts index 1a22451b8..61ef46bee 100644 --- a/src/perf-tests/sets.ts +++ b/src/perf-tests/sets.ts @@ -2,6 +2,8 @@ import * as B from 'benchmark' import IntTuple from '../mol-base/collections/int-tuple' import OrdSet from '../mol-base/collections/ordered-set' import AtomSet from '../mol-data/atom-set' +import Segmentation from '../mol-base/collections/segmentation' +import SortedArray from '../mol-base/collections/sorted-array' export namespace Iteration { const U = 1000, V = 2500; @@ -269,12 +271,12 @@ export namespace Tuples { export function testSegments() { const data = OrdSet.ofSortedArray([4, 9, 10, 11, 14, 15, 16]); - const segs = OrdSet.ofSortedArray([0, 4, 10, 12, 13, 15, 25]); - const it = OrdSet.segments(segs, data); + const segs = Segmentation.create(SortedArray.ofSortedArray([0, 4, 10, 12, 13, 15, 25]), []); + const it = Segmentation.segments(segs, data); for (let s = it.move(); !it.done; s = it.move()) { for (let j = s.start; j < s.end; j++) { - console.log(`${s.segment}: ${OrdSet.getAt(data, j)}`); + console.log(`${s.index}: ${OrdSet.getAt(data, j)}`); } } } -- GitLab