Ver Fonte

Refactoring AtomSet (part 2)

David Sehnal há 7 anos atrás
pai
commit
60d02b1f6a

+ 3 - 1
src/mol-data/int/map.ts

@@ -6,12 +6,14 @@
 
 import { iterableToArray } from '../util'
 
+// TODO: rename to "linear map" and just do key value mapping from index?
+
 /** Immutable by convention IntMap */
 interface IntMap<T> {
     has(key: number): boolean,
     keys(): IterableIterator<number>,
     values(): IterableIterator<T>,
-    get(key: number): T | undefined,
+    get(key: number): T,
     readonly size: number
 }
 

+ 4 - 4
src/mol-data/util/equivalence-classes.ts

@@ -6,7 +6,7 @@
 
 class EquivalenceClassesImpl<K, V> {
     private id = 0;
-    private byHash: { [hash: number]: { id: number, keys: K[], value: V }[] } = Object.create(null);
+    private byHash = new Map<number, { id: number, keys: K[], value: V }[]>();
 
     readonly groups: K[][] = [];
 
@@ -19,8 +19,8 @@ class EquivalenceClassesImpl<K, V> {
 
     add(key: K, a: V) {
         const hash = this.getHash(a);
-        if (!!this.byHash[hash]) {
-            const groups = this.byHash[hash];
+        if (this.byHash.has(hash)) {
+            const groups = this.byHash.get(hash)!;
             for (let i = 0, _i = groups.length; i < _i; i++) {
                 const group = groups[i];
                 if (this.areEqual(a, group.value)) {
@@ -33,7 +33,7 @@ class EquivalenceClassesImpl<K, V> {
             return group.id;
         } else {
             const group = this.createGroup(key, a);
-            this.byHash[hash] = [group];
+            this.byHash.set(hash, [group]);
             return group.id;
         }
     }

+ 6 - 6
src/mol-data/util/hash-set.ts

@@ -12,12 +12,12 @@ interface SetLike<T> {
 
 class HashSetImpl<T> implements SetLike<T> {
     size: number = 0;
-    private byHash: { [hash: number]: T[] } = Object.create(null);
+    private byHash = new Map<number, T[]>();
 
     add(a: T) {
         const hash = this.getHash(a);
-        if (!!this.byHash[hash]) {
-            const xs = this.byHash[hash];
+        if (this.byHash.has(hash)) {
+            const xs = this.byHash.get(hash)!;
             for (let i = 0, _i = xs.length; i < _i; i++) {
                 if (this.areEqual(a, xs[i])) return false;
             }
@@ -25,7 +25,7 @@ class HashSetImpl<T> implements SetLike<T> {
             this.size++;
             return true;
         } else {
-            this.byHash[hash] = [a];
+            this.byHash.set(hash, [a]);
             this.size++;
             return true;
         }
@@ -33,8 +33,8 @@ class HashSetImpl<T> implements SetLike<T> {
 
     has(v: T) {
         const hash = this.getHash(v);
-        if (!this.byHash[hash]) return false;
-        const xs = this.byHash[hash];
+        if (!this.byHash.has(hash)) return false;
+        const xs = this.byHash.get(hash)!;
         for (let i = 0, _i = xs.length; i < _i; i++) {
             if (this.areEqual(v, xs[i])) return true;
         }

+ 7 - 7
src/mol-model/structure/_spec/atom-set.spec.ts

@@ -35,10 +35,10 @@ describe('atom set', () => {
     });
 
     it('multi', () => {
-        const set = AtomSet.create({
-            1: OrderedSet.ofSortedArray([4, 6, 7]),
-            3: OrderedSet.ofRange(0, 1),
-        });
+        const gen = AtomSet.Generator();
+        gen.add(1, OrderedSet.ofSortedArray([4, 6, 7]));
+        gen.add(3, OrderedSet.ofRange(0, 1));
+        const set = gen.getSet();
         const ret = [p(1, 4), p(1, 6), p(1, 7), p(3, 0), p(3, 1)];
         expect(AtomSet.atomCount(set)).toBe(ret.length);
         expect(setToPairs(set)).toEqual([p(1, 4), p(1, 6), p(1, 7), p(3, 0), p(3, 1)]);
@@ -52,16 +52,16 @@ describe('atom set', () => {
 
     it('element at / index of', () => {
         const control: Atom[] = [];
-        const sets = Object.create(null);
+        const gen = AtomSet.Generator();
         for (let i = 1; i < 10; i++) {
             const set = [];
             for (let j = 1; j < 7; j++) {
                 control[control.length] = p(i * i, j * j + 1);
                 set[set.length] = j * j + 1;
             }
-            sets[i * i] = OrderedSet.ofSortedArray(set);
+            gen.add(i * i, OrderedSet.ofSortedArray(set));
         }
-        const ms = AtomSet.create(sets);
+        const ms = gen.getSet();
         for (let i = 0; i < control.length; i++) {
             expect(Atom.areEqual(AtomSet.atomGetAt(ms, i), control[i])).toBe(true);
         }

+ 3 - 3
src/mol-model/structure/query/generators.ts

@@ -105,14 +105,14 @@ function atomGroupsSegmented({ entityTest, chainTest, residueTest, atomTest }: A
 
 class LinearGroupingBuilder {
     private builders: AtomSet.Builder[] = [];
-    private builderMap: { [key: string]: AtomSet.Builder } = Object.create(null);
+    private builderMap = new Map<string, AtomSet.Builder>();
 
     add(key: any, unit: number, atom: number) {
-        let b = this.builderMap[key];
+        let b = this.builderMap.get(key);
         if (!b) {
             b = AtomSet.LinearBuilder(this.structure.atoms);
             this.builders[this.builders.length] = b;
-            this.builderMap[key] = b;
+            this.builderMap.set(key, b);
         }
         b.add(unit, atom);
     }

+ 8 - 5
src/mol-model/structure/structure/atom/set.ts

@@ -7,13 +7,13 @@
 import { OrderedSet, SortedArray, Iterator } from 'mol-data/int'
 import Atom from '../atom'
 import * as Impl from './set/impl'
-import createBuilder, { Builder as AtomSetBuilder } from './set/builder'
+import * as Builders from './set/builder'
 
 /** A map-like representation of grouped atom set */
 namespace AtomSet {
     export const Empty: AtomSet = Impl.Empty as any;
 
-    export const create: (data: Atom | ArrayLike<Atom> | { [unitId: number]: OrderedSet }) => AtomSet = Impl.create as any;
+    export const create: (data: Atom | ArrayLike<Atom>) => AtomSet = Impl.create as any;
 
     export const unitCount: (set: AtomSet) => number = Impl.keyCount as any;
     export const unitIds: (set: AtomSet) => SortedArray = Impl.getKeys as any;
@@ -38,9 +38,12 @@ namespace AtomSet {
     export const intersect: (a: AtomSet, b: AtomSet) => AtomSet = Impl.intersect as any;
     export const subtract: (a: AtomSet, b: AtomSet) => AtomSet = Impl.subtract as any;
 
-    export type Builder = AtomSetBuilder
-    export function LinearBuilder(parent: AtomSet): Builder { return createBuilder(parent, true); }
-    export function UnsortedBuilder(parent: AtomSet): Builder { return createBuilder(parent, false); }
+    export type Builder = Builders.Builder
+    export const LinearBuilder = Builders.LinearBuilder
+    export const UnsortedBuilder = Builders.UnsortedBuilder
+
+    export interface Generator { add(unit: number, set: OrderedSet): void, getSet(): AtomSet }
+    export const Generator: () => Generator = Impl.Generator as any
 
     // TODO: bounding sphere
     // TODO: distance, areWithIn?

+ 17 - 13
src/mol-model/structure/structure/atom/set/builder.ts

@@ -6,21 +6,21 @@
 
 import AtomSet from '../set'
 import Atom from '../../atom'
-import { OrderedSet } from 'mol-data/int'
+import { OrderedSet, IntMap } from 'mol-data/int'
 import { sortArray } from 'mol-data/util/sort'
 
 export class Builder {
     private keys: number[] = [];
-    private units: number[][] = Object.create(null);
+    private units = IntMap.Mutable<number[]>();
     private currentUnit: number[] = [];
 
     atomCount = 0;
 
     add(u: number, a: number) {
-        const unit = this.units[u];
+        const unit = this.units.get(u);
         if (!!unit) { unit[unit.length] = a; }
         else {
-            this.units[u] = [a];
+            this.units.set(u, [a]);
             this.keys[this.keys.length] = u;
         }
         this.atomCount++;
@@ -31,40 +31,44 @@ export class Builder {
     commitUnit(u: number) {
         if (this.currentUnit.length === 0) return;
         this.keys[this.keys.length] = u;
-        this.units[u] = this.currentUnit;
+        this.units.set(u, this.currentUnit);
     }
 
     getSet(): AtomSet {
-        const sets: { [key: number]: OrderedSet } = Object.create(null);
+        const generator = AtomSet.Generator();
 
         let allEqual = this.keys.length === AtomSet.unitCount(this.parent);
 
         for (let i = 0, _i = this.keys.length; i < _i; i++) {
             const k = this.keys[i];
-            const unit = this.units[k];
+            const unit = this.units.get(k);
             const l = unit.length;
             if (!this.sorted && l > 1) sortArray(unit);
 
             const set = l === 1 ? OrderedSet.ofSingleton(unit[0]) : OrderedSet.ofSortedArray(unit);
             const parentSet = AtomSet.unitGetById(this.parent, k);
             if (OrderedSet.areEqual(set, parentSet)) {
-                sets[k] = parentSet;
+                generator.add(k, parentSet);
             } else {
-                sets[k] = set;
+                generator.add(k, set);
                 allEqual = false;
             }
         }
-        return allEqual ? this.parent : AtomSet.create(sets);
+        return allEqual ? this.parent : generator.getSet();
     }
 
     singleton(): Atom {
         const u = this.keys[0];
-        return Atom.create(u, this.units[u][0]);
+        return Atom.create(u, this.units.get(u)[0]);
     }
 
     constructor(private parent: AtomSet, private sorted: boolean) { }
 }
 
-export default function createBuilder(parent: AtomSet, sorted: boolean) {
-    return new Builder(parent, sorted);
+export function LinearBuilder(parent: AtomSet) {
+    return new Builder(parent, true);
+}
+
+export function UnsortedBuilder(parent: AtomSet) {
+    return new Builder(parent, false);
 }

+ 97 - 109
src/mol-model/structure/structure/atom/set/impl.ts

@@ -11,15 +11,14 @@ import Atom from '../../atom'
 
 /** Long and painful implementation starts here */
 
-export interface AtomSetElements { sets: IntMap<OrderedSet>, offsets: number[], hashCode: number, keys: SortedArray }
+export interface AtomSetElements { sets: IntMap<OrderedSet>, offsets: Int32Array, hashCode: number, keys: SortedArray }
 export type AtomSetImpl = Atom | AtomSetElements
 
-export const Empty: AtomSetImpl = { sets: IntMap.Empty, offsets: [0], hashCode: 0, keys: SortedArray.Empty };
+export const Empty: AtomSetImpl = { sets: IntMap.Empty, offsets: new Int32Array(1), hashCode: 0, keys: SortedArray.Empty };
 
-export function create(data: Atom | ArrayLike<Atom> | { [id: number]: OrderedSet }): AtomSetImpl {
+export function create(data: Atom | ArrayLike<Atom>): AtomSetImpl {
     if (typeof data === 'number' || Atom.is(data)) return data;
-    if (isArrayLike(data)) return ofAtoms(data);
-    return ofObject(data as { [id: number]: OrderedSet });
+    return ofAtoms(data);
 }
 
 export function isSingleton(set: AtomSetImpl) {
@@ -154,7 +153,7 @@ class ElementsIterator implements Iterator<Atom> {
             return false;
         }
         this.unit = this.elements.keys[this.setIndex];
-        this.currentSet = this.elements.sets.get(this.unit)!;
+        this.currentSet = this.elements.sets.get(this.unit);
         this.currentIndex = 0;
         this.currentSize = OrderedSet.size(this.currentSet);
         return true;
@@ -172,60 +171,57 @@ export function values(set: AtomSetImpl): Iterator<Atom> {
     return new ElementsIterator(set as AtomSetElements);
 }
 
-function isArrayLike(x: any): x is ArrayLike<Atom> {
-    return x && (typeof x.length === 'number' && (Array.isArray(x) || !!x.buffer));
-}
-
-function ofObject(data: { [id: number]: OrderedSet }) {
-    const keys = [];
+export class AtomSetGenerator {
+    private keys: number[] = [];
+    private sets = IntMap.Mutable<OrderedSet>();
 
-    const _keys = Object.keys(data);
-    for (let i = 0, _i = _keys.length; i < _i; i++) {
-        const k = +_keys[i];
-        if (OrderedSet.size(data[k]) > 0) keys[keys.length] = k;
+    add(unit: number, set: OrderedSet) {
+        if (OrderedSet.size(set) === 0) return;
+        this.keys[this.keys.length] = unit;
+        this.sets.set(unit, set);
     }
-    if (!keys.length) return Empty;
-    if (keys.length === 1) {
-        const set = data[keys[0]];
-        if (OrderedSet.size(set) === 1) return Atom.create(keys[0], OrderedSet.getAt(set, 0));
+
+    addUnion(unit: number, set: OrderedSet) {
+        if (OrderedSet.size(set) === 0) return;
+
+        if (this.sets.has(unit)) {
+            this.sets.set(unit, OrderedSet.union(this.sets.get(unit), set));
+        } else {
+            this.keys[this.keys.length] = unit;
+            this.sets.set(unit, set);
+        }
     }
-    return ofObject1(keys, data);
-}
 
-function ofObject1(keys: number[], data: { [id: number]: OrderedSet }) {
-    if (keys.length === 1) {
-        const k = keys[0];
-        const set = data[k];
-        if (OrderedSet.size(set) === 1) return Atom.create(k, OrderedSet.getAt(set, 0));
+    getSet(): AtomSetImpl {
+        return ofKeysAndSets(this.keys, this.sets);
     }
-    sortArray(keys);
-    return _createObjectOrdered(SortedArray.ofSortedArray(keys), data);
 }
 
-function ofObjectOrdered(keys: SortedArray, data: { [id: number]: OrderedSet }) {
-    if (keys.length === 1) {
-        const k = keys[0];
-        const set = data[k];
-        if (OrderedSet.size(set) === 1) return Atom.create(k, OrderedSet.getAt(set, 0));
-    }
-    return _createObjectOrdered(keys, data);
+export function Generator() {
+    return new AtomSetGenerator();
 }
 
-function _createObjectOrdered(keys: SortedArray, data: { [id: number]: OrderedSet }): AtomSetElements {
-    const sets = IntMap.Mutable<OrderedSet>();
-    const offsets = [0];
+function ofKeysAndSetsElemements(keys: number[], sets: IntMap<OrderedSet>): AtomSetElements {
+    sortArray(keys);
     let runningSize = 0;
+    const offsets = new Int32Array(keys.length + 1);
     for (let i = 0, _i = keys.length; i < _i; i++) {
-        const k = keys[i];
-        const set = data[k];
-        sets.set(k, set);
-        runningSize += OrderedSet.size(set);
-        offsets[offsets.length] = runningSize;
+        runningSize += OrderedSet.size(sets.get(keys[i]));
+        offsets[i + 1] = runningSize;
+    }
+    return { keys: SortedArray.ofSortedArray(keys), sets: IntMap.asImmutable(sets), offsets, hashCode: -1 };
+}
+
+
+function ofKeysAndSets(keys: number[], sets: IntMap<OrderedSet>) {
+    if (keys.length === 1) {
+        const set = sets.get(keys[0]);
+        if (OrderedSet.size(set) === 1) return Atom.create(keys[0], OrderedSet.getAt(set, 0));
     }
-    return { sets, keys, offsets, hashCode: - 1 };
+    return ofKeysAndSetsElemements(keys, sets);
 }
 
-function getUniqueElements(xs: number[]) {
+function getUniqueElements(xs: number[]): number[] {
     let count = 1;
     for (let i = 1, _i = xs.length; i < _i; i++) {
         if (xs[i - 1] !== xs[i]) count++;
@@ -239,7 +235,7 @@ function getUniqueElements(xs: number[]) {
     return ret;
 }
 
-function normalizeArray(xs: number[]) {
+function normalizeArray(xs: number[]): number[] {
     sortArray(xs);
     for (let i = 1, _i = xs.length; i < _i; i++) {
         if (xs[i - 1] === xs[i]) return getUniqueElements(xs);
@@ -249,23 +245,28 @@ function normalizeArray(xs: number[]) {
 
 function ofAtoms(xs: ArrayLike<Atom>) {
     if (xs.length === 0) return Empty;
-    const sets: { [key: number]: number[] } = Object.create(null);
+
+    const elements = IntMap.Mutable<number[]>();
+    const keys: number[] = [];
     for (let i = 0, _i = xs.length; i < _i; i++) {
         const x = xs[i];
         const u = Atom.unit(x), v = Atom.index(x);
-        const set = sets[u];
-        if (set) set[set.length] = v;
-        else sets[u] = [v];
+        if (elements.has(u)) {
+            const set = elements.get(u);
+            set[set.length] = v;
+        } else {
+            keys[keys.length] = u;
+            elements.set(u, [v]);
+        }
     }
-    const ret: { [key: number]: OrderedSet } = Object.create(null);
-    const keys = [];
-    const _keys = Object.keys(sets);
-    for (let i = 0, _i = _keys.length; i < _i; i++) {
-        const k = +_keys[i];
-        keys[keys.length] = k;
-        ret[k] = OrderedSet.ofSortedArray(normalizeArray(sets[k]));
+
+    const sets = IntMap.Mutable<OrderedSet>();
+    for (let i = 0, _i = keys.length; i < _i; i++) {
+        const k = keys[i];
+        sets.set(k, OrderedSet.ofSortedArray(normalizeArray(elements.get(k))));
     }
-    return ofObject1(keys, ret);
+
+    return ofKeysAndSets(keys, sets);
 }
 
 function getOffsetIndex(xs: ArrayLike<number>, value: number) {
@@ -288,7 +289,7 @@ function getAtE(set: AtomSetElements, i: number): Atom {
     const o = getOffsetIndex(offsets, i);
     if (o >= offsets.length - 1) return 0 as any;
     const k = keys[o];
-    const e = OrderedSet.getAt(set.sets.get(k)!, i - offsets[o]);
+    const e = OrderedSet.getAt(set.sets.get(k), i - offsets[o]);
     return Atom.create(k, e);
 }
 
@@ -297,7 +298,7 @@ function indexOfE(set: AtomSetElements, t: Atom) {
     const u = Atom.unit(t);
     const setIdx = SortedArray.indexOf(keys, u);
     if (setIdx < 0) return -1;
-    const o = OrderedSet.indexOf(set.sets.get(u)!, Atom.index(t));
+    const o = OrderedSet.indexOf(set.sets.get(u), Atom.index(t));
     if (o < 0) return -1;
     return set.offsets[setIdx] + o;
 }
@@ -308,7 +309,7 @@ function computeHash(set: AtomSetElements) {
     for (let i = 0, _i = keys.length; i < _i; i++) {
         const k = keys[i];
         hash = (31 * hash + k) | 0;
-        hash = (31 * hash + OrderedSet.hashCode(sets.get(k)!)) | 0;
+        hash = (31 * hash + OrderedSet.hashCode(sets.get(k))) | 0;
     }
     hash = (31 * hash + size(set)) | 0;
     hash = hash1(hash);
@@ -326,14 +327,14 @@ function areEqualEE(a: AtomSetElements, b: AtomSetElements) {
     const { sets: bSets } = b;
     for (let i = 0, _i = keys.length; i < _i; i++) {
         const k = keys[i];
-        if (!OrderedSet.areEqual(aSets.get(k)!, bSets.get(k)!)) return false;
+        if (!OrderedSet.areEqual(aSets.get(k), bSets.get(k))) return false;
     }
     return true;
 }
 
 function areIntersectingNE(a: Atom, b: AtomSetElements) {
     const u = Atom.unit(a);
-    return b.sets.has(u) && OrderedSet.has(b.sets.get(u)!, Atom.index(a));
+    return b.sets.has(u) && OrderedSet.has(b.sets.get(u), Atom.index(a));
 }
 
 function areIntersectingEE(a: AtomSetElements, b: AtomSetElements) {
@@ -354,7 +355,7 @@ function areIntersectingEE(a: AtomSetElements, b: AtomSetElements) {
 
 function intersectNE(a: Atom, b: AtomSetElements) {
     const u = Atom.unit(a);
-    return b.sets.has(u) && OrderedSet.has(b.sets.get(u)!, Atom.index(a)) ? a : Empty;
+    return b.sets.has(u) && OrderedSet.has(b.sets.get(u), Atom.index(a)) ? a : Empty;
 }
 
 function intersectEE(a: AtomSetElements, b: AtomSetElements) {
@@ -365,20 +366,16 @@ function intersectEE(a: AtomSetElements, b: AtomSetElements) {
     const r = SortedArray.findRange(keysA, SortedArray.min(keysB), SortedArray.max(keysB));
     const start = Interval.start(r), end = Interval.end(r);
 
-    const keys = [], ret = Object.create(null);
     const { sets: aSets } = a;
     const { sets: bSets } = b;
+    const generator = Generator();
     for (let i = start; i < end; i++) {
         const k = keysA[i];
         const bk = bSets.get(k);
         if (!bk) continue;
-        const intersection = OrderedSet.intersect(aSets.get(k)!, bk);
-        if (OrderedSet.size(intersection) > 0) {
-            keys[keys.length] = k;
-            ret[k] = intersection;
-        }
+        generator.add(k, OrderedSet.intersect(aSets.get(k), bk));
     }
-    return ofObjectOrdered(SortedArray.ofSortedArray(keys), ret);
+    return generator.getSet();
 }
 
 function subtractNE(a: Atom, b: AtomSetElements) {
@@ -390,20 +387,24 @@ function subtractEN(a: AtomSetElements, b: Atom): AtomSetImpl {
 
     const u = Atom.unit(b), v = Atom.index(b);
     const { sets: aSets } = a;
-    const set = aSets.get(u)!;
+    const set = aSets.get(u);
+
     if (OrderedSet.size(set) === 1) {
         // remove the entire unit.
-        throw 'nyi'
-        //return ofObjectOrdered(SortedArray.subtract(a.keys, SortedArray.ofSingleton(u)), a);
+        const generator = Generator();
+        for (let i = 0, _i = a.keys.length; i < _i; i++) {
+            const k = a.keys[i];
+            if (k !== u) generator.add(k, aSets.get(k))
+        }
+        return generator.getSet();
     } else {
-        const ret: { [key: number]: OrderedSet } = Object.create(null);
+        const generator = Generator();
         for (let i = 0, _i = a.keys.length; i < _i; i++) {
             const k = a.keys[i];
-            if (k === u) {
-                ret[k] = OrderedSet.subtract(set, OrderedSet.ofSingleton(v));
-            } else ret[k] = aSets.get(k)!;
+            if (k === u) generator.add(k, OrderedSet.subtract(set, OrderedSet.ofSingleton(v)))
+            else generator.add(k, aSets.get(k))
         }
-        return ofObjectOrdered(a.keys, ret);
+        return generator.getSet();
     }
 }
 
@@ -415,34 +416,28 @@ function subtractEE(a: AtomSetElements, b: AtomSetElements) {
     const r = SortedArray.findRange(keysA, SortedArray.min(keysB), SortedArray.max(keysB));
     const start = Interval.start(r), end = Interval.end(r);
 
-    const keys = [], ret = Object.create(null);
+    const generator = Generator();
     const { sets: aSets } = a;
     const { sets: bSets } = b;
     for (let i = 0; i < start; i++) {
         const k = keysA[i];
-        keys[keys.length] = k;
-        ret[k] = aSets.get(k);
+        generator.add(k, aSets.get(k));
     }
     for (let i = start; i < end; i++) {
         const k = keysA[i];
-        const ak = aSets.get(k)!, bk = bSets.get(k);
+        const ak = aSets.get(k), bk = bSets.get(k);
         if (!!bk) {
             const subtraction = OrderedSet.subtract(ak, bk);
-            if (OrderedSet.size(subtraction) > 0) {
-                keys[keys.length] = k;
-                ret[k] = subtraction;
-            }
+            generator.add(k, subtraction);
         } else {
-            keys[keys.length] = k;
-            ret[k] = ak;
+            generator.add(k, ak);
         }
     }
     for (let i = end, _i = keysA.length; i < _i; i++) {
         const k = keysA[i];
-        keys[keys.length] = k;
-        ret[k] = aSets.get(k);
+        generator.add(k, aSets.get(k));
     }
-    return ofObjectOrdered(SortedArray.ofSortedArray(keys), ret);
+    return generator.getSet();
 }
 
 function findUnion(sets: ArrayLike<AtomSetImpl>) {
@@ -453,16 +448,16 @@ function findUnion(sets: ArrayLike<AtomSetImpl>) {
     const eCount = { count: 0 };
     const ns = unionN(sets, eCount);
     if (!eCount.count) return ns;
-    const ret = Object.create(null);
+    const generator = Generator();
     for (let i = 0, _i = sets.length; i < _i; i++) {
         const s = sets[i];
-        if (typeof s !== 'number') unionInto(ret, s as AtomSetElements);
+        if (typeof s !== 'number') unionInto(generator, s as AtomSetElements);
     }
     if (size(ns as AtomSetImpl) > 0) {
-        if (typeof ns === 'number') unionIntoN(ret, ns as any);
-        else unionInto(ret, ns as AtomSetElements);
+        if (typeof ns === 'number') unionIntoN(generator, ns as any);
+        else unionInto(generator, ns as AtomSetElements);
     }
-    return ofObject(ret);
+    return generator.getSet();
 }
 
 function unionN(sets: ArrayLike<AtomSetImpl>, eCount: { count: number }) {
@@ -483,23 +478,16 @@ function unionN(sets: ArrayLike<AtomSetImpl>, eCount: { count: number }) {
     return ofAtoms(packed as any);
 }
 
-function unionInto(data: { [key: number]: OrderedSet }, a: AtomSetElements) {
+function unionInto(builder: AtomSetGenerator, a: AtomSetElements) {
     const keys = a.keys;
     const { sets: aSets } = a;
     for (let i = 0, _i = keys.length; i < _i; i++) {
         const k = keys[i];
-        const set = data[k];
-        if (set) data[k] = OrderedSet.union(set, aSets.get(k)!);
-        else data[k] = aSets.get(k)!;
+        builder.addUnion(k, aSets.get(k));
     }
 }
 
-function unionIntoN(data: { [key: number]: OrderedSet }, a: Atom) {
+function unionIntoN(builder: AtomSetGenerator, a: Atom) {
     const u = Atom.unit(a);
-    const set = data[u];
-    if (set) {
-        data[u] = OrderedSet.union(set, OrderedSet.ofSingleton(Atom.index(a)));
-    } else {
-        data[u] = OrderedSet.ofSingleton(Atom.index(a));
-    }
+    builder.addUnion(u, OrderedSet.ofSingleton(Atom.index(a)));
 }

+ 6 - 6
src/mol-model/structure/structure/structure.ts

@@ -37,7 +37,7 @@ namespace Structure {
         for (let c = 0; c < chains.count; c++) {
             const unit = Unit.create(model, SymmetryOperator.Default);
             builder.addUnit(unit);
-            builder.addAtoms(unit.id, OrderedSet.ofBounds(chains.segments[c], chains.segments[c + 1]));
+            builder.setAtoms(unit.id, OrderedSet.ofBounds(chains.segments[c], chains.segments[c + 1]));
         }
 
         return builder.getStructure();
@@ -46,20 +46,20 @@ namespace Structure {
     export interface Builder {
         add(unit: Unit, atoms: OrderedSet): void,
         addUnit(unit: Unit): void,
-        addAtoms(unitId: number, atoms: OrderedSet): void,
+        setAtoms(unitId: number, atoms: OrderedSet): void,
         getStructure(): Structure,
         readonly atomCount: number
     }
 
     class BuilderImpl implements Builder {
         private units = Object.create(null);
-        private atoms = Object.create(null);
+        private atoms = AtomSet.Generator();
         atomCount = 0;
 
-        add(unit: Unit, atoms: OrderedSet) { this.addUnit(unit); this.addAtoms(unit.id, atoms); }
+        add(unit: Unit, atoms: OrderedSet) { this.addUnit(unit); this.setAtoms(unit.id, atoms); }
         addUnit(unit: Unit) { this.units[unit.id] = unit; }
-        addAtoms(unitId: number, atoms: OrderedSet) { this.atoms[unitId] = atoms; this.atomCount += OrderedSet.size(atoms); }
-        getStructure(): Structure { return this.atomCount > 0 ? Structure.create(this.units, AtomSet.create(this.atoms)) : Empty; }
+        setAtoms(unitId: number, atoms: OrderedSet) { this.atoms.add(unitId, atoms); this.atomCount += OrderedSet.size(atoms); }
+        getStructure(): Structure { return this.atomCount > 0 ? Structure.create(this.units, this.atoms.getSet()) : Empty; }
     }
 
     export function Builder(): Builder { return new BuilderImpl(); }

+ 3 - 2
src/perf-tests/structure.ts

@@ -77,6 +77,7 @@ export namespace PropertyAccess {
             l.unit = units[unitIds[i]];
             const set = AtomSet.unitGetByIndex(atoms, i);
 
+
             for (let j = 0, _j = OrdSet.size(set); j < _j; j++) {
                 l.atom = OrdSet.getAt(set, j);
                 s += p(l);
@@ -274,8 +275,8 @@ export namespace PropertyAccess {
 
         // return;
 
-        console.log(baseline(models[0]));
-        console.log(sumProperty(structures[0], l => l.unit.model.conformation.atomId.value(l.atom)));
+        console.log('bs', baseline(models[0]));
+        console.log('sp', sumProperty(structures[0], l => l.unit.model.conformation.atomId.value(l.atom)));
         console.log(sumPropertySegmented(structures[0], l => l.unit.model.conformation.atomId.value(l.atom)));
 
         //console.log(sumPropertySegmentedMutable(structures[0], l => l.unit.model.conformation.atomId.value(l.atom)));