From 90077b5789784290b5de68c1712c599315a48b23 Mon Sep 17 00:00:00 2001 From: David Sehnal <david.sehnal@gmail.com> Date: Fri, 10 Nov 2017 12:43:53 +0100 Subject: [PATCH] mol-data refactoring, add IntMap --- src/mol-data/_spec/equiv-index.spec.ts | 2 +- src/mol-data/db/column.ts | 5 + src/mol-data/db/table.ts | 20 +++ src/mol-data/int.ts | 3 +- src/mol-data/int/map.ts | 45 +++++++ src/mol-data/util.ts | 13 +- src/mol-data/util/array.ts | 25 ++++ src/mol-data/util/chunked-array.ts | 2 +- src/mol-data/util/equivalence-classes.ts | 4 +- src/mol-data/util/grouping.ts | 26 ++-- src/mol-data/util/hash-set.ts | 6 +- src/mol-data/util/unique-array.ts | 2 +- src/mol-io/common/binary-cif/array-encoder.ts | 2 +- .../structure/model/properties/symmetry.ts | 7 +- src/mol-model/structure/query/selection.ts | 2 +- .../structure/structure/structure.ts | 2 +- src/perf-tests/chunked-array-vs-native.ts | 2 +- src/perf-tests/sets.ts | 115 +++++++++++++++++- 18 files changed, 245 insertions(+), 38 deletions(-) create mode 100644 src/mol-data/int/map.ts create mode 100644 src/mol-data/util/array.ts diff --git a/src/mol-data/_spec/equiv-index.spec.ts b/src/mol-data/_spec/equiv-index.spec.ts index 9083c0145..339022a96 100644 --- a/src/mol-data/_spec/equiv-index.spec.ts +++ b/src/mol-data/_spec/equiv-index.spec.ts @@ -4,7 +4,7 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import EquivalenceClasses from '../util/equivalence-classes' +import { EquivalenceClasses } from '../util' describe('equiv-classes', () => { it('integer mod classes', () => { diff --git a/src/mol-data/db/column.ts b/src/mol-data/db/column.ts index 54fe666de..adcd28cc2 100644 --- a/src/mol-data/db/column.ts +++ b/src/mol-data/db/column.ts @@ -80,6 +80,10 @@ namespace Column { valueKind?: (row: number) => ValueKind, } + export function is(v: any): v is Column<any> { + return !!v && !!(v as Column<any>).schema && !!(v as Column<any>).value; + } + export const enum ValueKind { Present = 0, NotPresent = 1, Unknown = 2 } export function Undefined<T extends Schema>(rowCount: number, schema: T): Column<T['T']> { @@ -306,6 +310,7 @@ function mapToArrayImpl<T, S>(c: Column<T>, f: (v: T) => S, ctor: Column.ArrayCt } function areColumnsEqual(a: Column<any>, b: Column<any>) { + if (a === b) return true; if (a.rowCount !== b.rowCount || a.isDefined !== b.isDefined || a.schema.valueType !== b.schema.valueType) return false; if (!!a['@array'] && !!b['@array']) return areArraysEqual(a, b); return areValuesEqual(a, b); diff --git a/src/mol-data/db/table.ts b/src/mol-data/db/table.ts index d01629bf1..6335e37ed 100644 --- a/src/mol-data/db/table.ts +++ b/src/mol-data/db/table.ts @@ -130,6 +130,26 @@ namespace Table { return true; } + + /** Allocate a new object with the given row values. */ + export function getRow<S extends Schema>(table: Table<S>, index: number) { + const row: Row<S> = Object.create(null); + const { _columns: cols } = table; + for (let i = 0; i < cols.length; i++) { + const c = cols[i]; + row[c] = table[c].value(index); + } + return row; + } + + export function getRows<S extends Schema>(table: Table<S>) { + const ret: Row<S>[] = []; + const { _rowCount: c } = table; + for (let i = 0; i < c; i++) { + ret[i] = getRow(table, i); + } + return ret; + } } export default Table \ No newline at end of file diff --git a/src/mol-data/int.ts b/src/mol-data/int.ts index 54c2795bb..24b005bd2 100644 --- a/src/mol-data/int.ts +++ b/src/mol-data/int.ts @@ -10,6 +10,7 @@ import Segmentation from './int/segmentation' import SortedArray from './int/sorted-array' import Tuple from './int/tuple' import LinkedIndex from './int/linked-index' +import IntMap from './int/map' import Iterator from './iterator' -export { Interval, OrderedSet, Segmentation, SortedArray, Tuple, LinkedIndex, Iterator } \ No newline at end of file +export { Interval, OrderedSet, Segmentation, SortedArray, Tuple, LinkedIndex, IntMap, Iterator } \ No newline at end of file diff --git a/src/mol-data/int/map.ts b/src/mol-data/int/map.ts new file mode 100644 index 000000000..1ef86a532 --- /dev/null +++ b/src/mol-data/int/map.ts @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { iterableToArray } from '../util' + +/** Immutable by convention IntMap */ +interface IntMap<T> { + keys(): IterableIterator<number>, + values(): IterableIterator<T>, + get(key: number): T, + readonly size: number +} + +namespace IntMap { + export interface Mutable<T> extends IntMap<T> { + set(key: number, value: T): void; + } + + export function keyArray<T>(map: IntMap<T>): number[] { + return iterableToArray(map.keys()); + } + + export function ofObj<T>(obj: { [key: number]: T }): IntMap<T> { + const keys = Object.keys(obj); + const ret = new Map<number, T>(); + for (let i = 0, _i = keys.length; i < _i; i++) { + const k = keys[i]; + ret.set(+k, obj[k as any]); + } + return ret as IntMap<T>; + } + + export function Mutable<T>(): Mutable<T> { + return new Map<number, T>() as Mutable<T>; + } + + export function asImmutable<T>(map: IntMap<T>): IntMap<T> { + return map; + } +} + +export default IntMap; \ No newline at end of file diff --git a/src/mol-data/util.ts b/src/mol-data/util.ts index ab0c73fcf..629b3b517 100644 --- a/src/mol-data/util.ts +++ b/src/mol-data/util.ts @@ -4,12 +4,11 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import ChunkedArray from './util/chunked-array' -import EquivalenceClasses from './util/chunked-array' -import HashSet from './util/hash-set' -import UniqueArray from './util/unique-array' - +export * from './util/chunked-array' +export * from './util/unique-array' +export * from './util/hash-set' +export * from './util/equivalence-classes' export * from './util/hash-functions' export * from './util/sort' - -export { ChunkedArray, EquivalenceClasses, HashSet, UniqueArray } \ No newline at end of file +export * from './util/grouping' +export * from './util/array' \ No newline at end of file diff --git a/src/mol-data/util/array.ts b/src/mol-data/util/array.ts new file mode 100644 index 000000000..664138327 --- /dev/null +++ b/src/mol-data/util/array.ts @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * from https://github.com/dsehnal/CIFTools.js + * @author David Sehnal <david.sehnal@gmail.com> + */ + +export function arrayFind<T>(array: ArrayLike<T>, f: (v: T) => boolean): T | undefined { + for (let i = 0, _i = array.length; i < _i; i++) { + if (f(array[i])) return array[i]; + } + return void 0; +} + +export function iterableToArray<T>(it: IterableIterator<T>): T[] { + if (Array.from) return Array.from(it); + + const ret = []; + while (true) { + const { done, value } = it.next(); + if (done) break; + ret[ret.length] = value; + } + return ret; +} \ No newline at end of file diff --git a/src/mol-data/util/chunked-array.ts b/src/mol-data/util/chunked-array.ts index 0664a4c12..f9b977571 100644 --- a/src/mol-data/util/chunked-array.ts +++ b/src/mol-data/util/chunked-array.ts @@ -134,4 +134,4 @@ namespace ChunkedArray { } } -export default ChunkedArray \ No newline at end of file +export { ChunkedArray } \ No newline at end of file diff --git a/src/mol-data/util/equivalence-classes.ts b/src/mol-data/util/equivalence-classes.ts index 936d39b29..cc5f4af62 100644 --- a/src/mol-data/util/equivalence-classes.ts +++ b/src/mol-data/util/equivalence-classes.ts @@ -41,8 +41,6 @@ class EquivalenceClassesImpl<K, V> { constructor(private getHash: (v: V) => any, private areEqual: (a: V, b: V) => boolean) { } } -function EquivalenceClasses<K, V>(getHash: (x: V) => any, areEqual: (a: V, b: V) => boolean) { +export function EquivalenceClasses<K, V>(getHash: (x: V) => any, areEqual: (a: V, b: V) => boolean) { return new EquivalenceClassesImpl<K, V>(getHash, areEqual); } - -export default EquivalenceClasses; \ No newline at end of file diff --git a/src/mol-data/util/grouping.ts b/src/mol-data/util/grouping.ts index 213f3cf54..71e9ce5a0 100644 --- a/src/mol-data/util/grouping.ts +++ b/src/mol-data/util/grouping.ts @@ -4,31 +4,34 @@ * @author David Sehnal <david.sehnal@gmail.com> */ +import { Column } from '../db' + export interface Grouping<V, K> { + map: Map<K, V[]>, keys: ReadonlyArray<K>, groups: ReadonlyArray<ReadonlyArray<V>> } class GroupingImpl<K, V> { - private byKey = new Map<K, V[]>(); + readonly map = new Map<K, V[]>(); readonly keys: K[] = []; readonly groups: V[][] = []; add(a: V) { const key = this.getKey(a) as any; - if (!!this.byKey.has(key)) { - const group = this.byKey.get(key)!; + if (!!this.map.has(key)) { + const group = this.map.get(key)!; group[group.length] = a; } else { const group = [a]; - this.byKey.set(key, group); + this.map.set(key, group); this.keys[this.keys.length] = key; this.groups[this.groups.length] = group; } } getGrouping(): Grouping<V, K> { - return { keys: this.keys, groups: this.groups }; + return { keys: this.keys, groups: this.groups, map: this.map }; } constructor(private getKey: (v: V) => K) { } @@ -38,10 +41,13 @@ export function Grouper<V, K>(getKey: (x: V) => K) { return new GroupingImpl<K, V>(getKey); } -function groupBy<V, K>(values: ArrayLike<V>, getKey: (x: V) => K) { +export function groupBy<V, K>(values: ArrayLike<V> | Column<V>, getKey: (x: V) => K) { const gs = Grouper(getKey); - for (let i = 0, _i = values.length; i < _i; i++) gs.add(values[i]); + if (Column.is(values)) { + const v = values.value; + for (let i = 0, _i = values.rowCount; i < _i; i++) gs.add(v(i)); + } else { + for (let i = 0, _i = values.length; i < _i; i++) gs.add(values[i]); + } return gs.getGrouping(); -} - -export default groupBy; \ No newline at end of file +} \ No newline at end of file diff --git a/src/mol-data/util/hash-set.ts b/src/mol-data/util/hash-set.ts index 297b8eeb6..0e41ad62f 100644 --- a/src/mol-data/util/hash-set.ts +++ b/src/mol-data/util/hash-set.ts @@ -45,8 +45,6 @@ class HashSetImpl<T> implements SetLike<T> { } // TODO: add implementations with multilevel hashing support? -function HashSet<T>(getHash: (v: T) => any, areEqual: (a: T, b: T) => boolean): SetLike<T> { +export function HashSet<T>(getHash: (v: T) => any, areEqual: (a: T, b: T) => boolean): SetLike<T> { return new HashSetImpl<T>(getHash, areEqual); -} - -export default HashSet; \ No newline at end of file +} \ No newline at end of file diff --git a/src/mol-data/util/unique-array.ts b/src/mol-data/util/unique-array.ts index f628baa96..5a5caa501 100644 --- a/src/mol-data/util/unique-array.ts +++ b/src/mol-data/util/unique-array.ts @@ -21,4 +21,4 @@ namespace UniqueArray { } } -export default UniqueArray \ No newline at end of file +export { UniqueArray } \ No newline at end of file diff --git a/src/mol-io/common/binary-cif/array-encoder.ts b/src/mol-io/common/binary-cif/array-encoder.ts index 344171b8a..67a9084ca 100644 --- a/src/mol-io/common/binary-cif/array-encoder.ts +++ b/src/mol-io/common/binary-cif/array-encoder.ts @@ -7,7 +7,7 @@ * @author Alexander Rose <alexander.rose@weirdbyte.de> */ -import ChunkedArray from 'mol-data/util/chunked-array' +import { ChunkedArray } from 'mol-data/util' import { Encoding, EncodedData } from './encoding' export interface ArrayEncoder { diff --git a/src/mol-model/structure/model/properties/symmetry.ts b/src/mol-model/structure/model/properties/symmetry.ts index 38303de0e..dfe599c2a 100644 --- a/src/mol-model/structure/model/properties/symmetry.ts +++ b/src/mol-model/structure/model/properties/symmetry.ts @@ -5,6 +5,7 @@ */ import SymmetryOperator from 'mol-math/geometry/symmetry-operator' +import { arrayFind } from 'mol-data/util' import { Query } from '../../query' import { Model } from '../../model' @@ -47,12 +48,8 @@ namespace Symmetry { export const Empty: Symmetry = { assemblies: [] }; export function findAssembly(model: Model, id: string): Assembly | undefined { - const { assemblies } = model.symmetry; const _id = id.toLocaleLowerCase(); - for (let i = 0; i < assemblies.length; i++) { - if (assemblies[i].id.toLowerCase() === _id) return assemblies[i]; - } - return void 0; + return arrayFind(model.symmetry.assemblies, a => a.id.toLowerCase() === _id); } } diff --git a/src/mol-model/structure/query/selection.ts b/src/mol-model/structure/query/selection.ts index 8504e4252..c1e0f9fb1 100644 --- a/src/mol-model/structure/query/selection.ts +++ b/src/mol-model/structure/query/selection.ts @@ -5,7 +5,7 @@ */ import Iterator from 'mol-data/iterator' -import HashSet from 'mol-data/util/hash-set' +import { HashSet } from 'mol-data/util' import { Structure, Atom, AtomSet } from '../structure' type Selection = diff --git a/src/mol-model/structure/structure/structure.ts b/src/mol-model/structure/structure/structure.ts index 317ea029f..8d5af1dcb 100644 --- a/src/mol-model/structure/structure/structure.ts +++ b/src/mol-model/structure/structure/structure.ts @@ -5,7 +5,7 @@ */ import { OrderedSet, Iterator } from 'mol-data/int' -import UniqueArray from 'mol-data/util/unique-array' +import { UniqueArray } from 'mol-data/util' import SymmetryOperator from 'mol-math/geometry/symmetry-operator' import { Model, Format } from '../model' import Unit from './unit' diff --git a/src/perf-tests/chunked-array-vs-native.ts b/src/perf-tests/chunked-array-vs-native.ts index 1b145425d..e7843412a 100644 --- a/src/perf-tests/chunked-array-vs-native.ts +++ b/src/perf-tests/chunked-array-vs-native.ts @@ -1,5 +1,5 @@ import * as B from 'benchmark' -import ChunkedArray from 'mol-data/util/chunked-array' +import { ChunkedArray } from 'mol-data/util' function testNative(size: number) { const xs = new Array(size); diff --git a/src/perf-tests/sets.ts b/src/perf-tests/sets.ts index 699c9037c..07bb5919a 100644 --- a/src/perf-tests/sets.ts +++ b/src/perf-tests/sets.ts @@ -331,7 +331,120 @@ export namespace ObjectVsMap { } } -ObjectVsMap.run(); +export namespace IntVsStringIndices { + type WithKeys<K> = { keys: K[], data: { [key: number]: number } } + type MapWithKeys = { keys: number[], map: Map<number, number> } + + function createCacheKeys(n: number): WithKeys<number> { + const data = Object.create(null), keys = []; + data.__ = void 0; + delete data.__; + for (let i = 1; i <= n; i++) { + const k = i * (i + 1); + keys[keys.length] = k; + data[k] = i + 1; + } + return { data, keys }; + } + + function createMapKeys(n: number): MapWithKeys { + const map = new Map<number, number>(), keys = []; + for (let i = 1; i <= n; i++) { + const k = i * (i + 1); + keys[keys.length] = k; + map.set(k, i + 1); + } + return { map, keys }; + } + + export function createInt(n: number) { + const ret = Object.create(null); + ret.__ = void 0; + delete ret.__; + for (let i = 1; i <= n; i++) ret[i * (i + 1)] = i + 1; + return ret; + } + + export function createStr(n: number) { + const ret = Object.create(null); + ret.__ = void 0; + delete ret.__; + for (let i = 1; i <= n; i++) ret['' + (i * (i + 1))] = i + 1; + return ret; + } + + export function createMap(n: number) { + const map = new Map<number, number>(); + for (let i = 1; i <= n; i++) map.set(i * (i + 1), i + 1); + return map; + } + + export function sumInt(xs: { [key: number]: number }) { + let s = 0; + const keys = Object.keys(xs); + for (let i = 0, _i = keys.length; i < _i; i++) s += xs[+keys[i]]; + return s; + } + + export function sumStr(xs: { [key: string]: number }) { + let s = 0; + const keys = Object.keys(xs); + for (let i = 0, _i = keys.length; i < _i; i++) s += xs[keys[i]]; + return s; + } + + export function sumMap(map: Map<number, number>) { + let s = 0; + const values = map.keys(); + while (true) { + const { done, value } = values.next(); + if (done) break; + s += value; + } + return s; + } + + export function sumCached(xs: WithKeys<number>) { + let s = 0; + const keys = xs.keys, data = xs.data; + for (let i = 0, _i = keys.length; i < _i; i++) s += data[keys[i]]; + return s; + } + + export function sumKeyMap(xs: MapWithKeys) { + let s = 0; + const keys = xs.keys, map = xs.map; + for (let i = 0, _i = keys.length; i < _i; i++) s += map.get(keys[i])!; + return s; + } + + export function run() { + const N = 1000; + //const int = createInt(N); + const map = createMap(N); + //const str = createStr(N); + const keys = createCacheKeys(N); + const keyMap = createMapKeys(N); + console.log(sumMap(map), sumCached(keys), sumKeyMap(keyMap)); + new B.Suite() + //.add('c int', () => createInt(N)) + .add('q map', () => sumMap(map)) + .add('c map', () => createMap(N)) + .add('c mk', () => createMapKeys(N)) + //.add('c str', () => createStr(N)) + .add('c cc', () => createCacheKeys(N)) + //.add('q int', () => sumInt(int)) + .add('q mk', () => sumKeyMap(keyMap)) + //.add('q str', () => sumStr(str)) + .add('q cc', () => sumCached(keys)) + .on('cycle', (e: any) => console.log(String(e.target))) + .run(); + } +} + +IntVsStringIndices.run(); + +//ObjectVsMap.run(); //testSegments(); -- GitLab