diff --git a/src/mol-base/collections/_spec/table.spec.ts b/src/mol-base/collections/_spec/table.spec.ts index 81d1733cc8e5293c86fd9f0628f9114e22b127a0..dd5243a817380e8b95e398ea63c15ea9708164b8 100644 --- a/src/mol-base/collections/_spec/table.spec.ts +++ b/src/mol-base/collections/_spec/table.spec.ts @@ -4,7 +4,7 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import Column, { isTypedArray } from '../column' +import Column, { ColumnHelpers } from '../column' import Table from '../table' describe('column', () => { @@ -32,7 +32,7 @@ describe('column', () => { it('typed', () => { expect(typedWindow.value(0)).toBe(2); expect(typedWindow.rowCount).toBe(2); - expect(isTypedArray(typedWindow.toArray())).toBe(true); + expect(ColumnHelpers.isTypedArray(typedWindow.toArray())).toBe(true); }); it('numStr', () => { diff --git a/src/mol-base/collections/column.ts b/src/mol-base/collections/column.ts index 4ae3ad6f6179aa7cd6b11de1997640a05292a77b..2137da5cf9c22d23e71f09209430e7c191c05058 100644 --- a/src/mol-base/collections/column.ts +++ b/src/mol-base/collections/column.ts @@ -89,6 +89,11 @@ namespace Column { return columnPermutation(column, indices, checkIndentity); } + /** A map of the 1st occurence of each value. */ + export function createFirstIndexMap<T>(column: Column<T>) { + return createFirstIndexMapOfColumn(column); + } + /** Makes the column backned by an array. Useful for columns that accessed often. */ export function asArrayColumn<T>(c: Column<T>, array?: ToArrayParams['array']): Column<T> { if (c['@array']) return c; @@ -99,6 +104,15 @@ namespace Column { export default Column; +function createFirstIndexMapOfColumn<T>(c: Column<T>): Map<T, number> | undefined { + const map = new Map<T, number>(); + for (let i = 0, _i = c.rowCount; i < _i; i++) { + const v = c.value(i); + if (!map.has(v)) return map.set(c.value(i), i); + } + return map; +} + function constColumn<T extends Column.Type>(v: T['T'], rowCount: number, type: T, valueKind: Column.ValueKind): Column<T['T']> { const value: Column<T['T']>['value'] = row => v; return { @@ -109,7 +123,7 @@ function constColumn<T extends Column.Type>(v: T['T'], rowCount: number, type: T value, valueKind: row => valueKind, toArray: params => { - const { array } = createArray(rowCount, params); + const { array } = ColumnHelpers.createArray(rowCount, params); for (let i = 0, _i = array.length; i < _i; i++) array[i] = v; return array; }, @@ -126,7 +140,7 @@ function lambdaColumn<T extends Column.Type>({ value, valueKind, rowCount, type value, valueKind: valueKind ? valueKind : row => Column.ValueKind.Present, toArray: params => { - const { array, start } = createArray(rowCount, params); + const { array, start } = ColumnHelpers.createArray(rowCount, params); for (let i = 0, _i = array.length; i < _i; i++) array[i] = value(i + start); return array; }, @@ -140,7 +154,7 @@ function arrayColumn<T extends Column.Type>({ array, type, valueKind }: Column.A ? row => { const v = array[row]; return typeof v === 'string' ? v : '' + v; } : row => array[row]; - const isTyped = isTypedArray(array); + const isTyped = ColumnHelpers.isTypedArray(array); return { '@type': type, '@array': array, @@ -150,7 +164,7 @@ function arrayColumn<T extends Column.Type>({ array, type, valueKind }: Column.A valueKind: valueKind ? valueKind : row => Column.ValueKind.Present, toArray: type.kind === 'str' ? params => { - const { start, end } = getArrayBounds(rowCount, params); + const { start, end } = ColumnHelpers.getArrayBounds(rowCount, params); const ret = new (params && typeof params.array !== 'undefined' ? params.array : (array as any).constructor)(end - start) as any; for (let i = 0, _i = end - start; i < _i; i++) { const v = array[start + i]; @@ -159,9 +173,9 @@ function arrayColumn<T extends Column.Type>({ array, type, valueKind }: Column.A return ret; } : isTyped - ? params => typedArrayWindow(array, params) as any as ReadonlyArray<T> + ? params => ColumnHelpers.typedArrayWindow(array, params) as any as ReadonlyArray<T> : params => { - const { start, end } = getArrayBounds(rowCount, params); + const { start, end } = ColumnHelpers.getArrayBounds(rowCount, params); if (start === 0 && end === array.length) return array as ReadonlyArray<T['T']>; const ret = new (params && typeof params.array !== 'undefined' ? params.array : (array as any).constructor)(end - start) as any; for (let i = 0, _i = end - start; i < _i; i++) ret[i] = array[start + i]; @@ -173,12 +187,12 @@ function arrayColumn<T extends Column.Type>({ array, type, valueKind }: Column.A function windowColumn<T>(column: Column<T>, start: number, end: number) { if (!column.isDefined) return Column.Undefined(end - start, column['@type']); - if (column['@array'] && isTypedArray(column['@array'])) return windowTyped(column, start, end); + if (column['@array'] && ColumnHelpers.isTypedArray(column['@array'])) return windowTyped(column, start, end); return windowFull(column, start, end); } function windowTyped<T>(c: Column<T>, start: number, end: number): Column<T> { - const array = typedArrayWindow(c['@array'], { start, end }); + const array = ColumnHelpers.typedArrayWindow(c['@array'], { start, end }); return arrayColumn({ array, type: c['@type'], valueKind: c.valueKind }) as any; } @@ -194,7 +208,7 @@ function windowFull<T>(c: Column<T>, start: number, end: number): Column<T> { value, valueKind: start === 0 ? vk : row => vk(row + start), toArray: params => { - const { array } = createArray(rowCount, params); + const { array } = ColumnHelpers.createArray(rowCount, params); for (let i = 0, _i = array.length; i < _i; i++) array[i] = v(i + start); return array; }, @@ -202,21 +216,27 @@ function windowFull<T>(c: Column<T>, start: number, end: number): Column<T> { }; } +function isIdentity(map: ArrayLike<number>) { + for (let i = 0, _i = map.length; i < _i; i++) { + if (map[i] !== i) return false; + } + return true; +} + function columnPermutation<T>(c: Column<T>, map: ArrayLike<number>, checkIdentity: boolean): Column<T> { if (!c.isDefined) return c; - if (checkIdentity) { - let isIdentity = true; - for (let i = 0, _i = map.length; i < _i; i++) { - if (map[i] !== i) { - isIdentity = false; - break; - } - } - if (isIdentity) return c; - } + if (checkIdentity && isIdentity(map)) return c; + if (!c['@array']) return permutationArray(c, map); return permutationFull(c, map); } +function permutationArray<T>(c: Column<T>, map: ArrayLike<number>): Column<T> { + const array = c['@array']!; + const ret = new (array as any).constructor(c.rowCount); + for (let i = 0, _i = c.rowCount; i < _i; i++) ret[i] = array[map[i]]; + return arrayColumn({ array: ret, type: c['@type'], valueKind: c.valueKind }); +} + function permutationFull<T>(c: Column<T>, map: ArrayLike<number>): Column<T> { const v = c.value, vk = c.valueKind, ave = c.areValuesEqual; const value: Column<T>['value'] = row => v(map[row]); @@ -229,7 +249,7 @@ function permutationFull<T>(c: Column<T>, map: ArrayLike<number>): Column<T> { value, valueKind: row => vk(map[row]), toArray: params => { - const { array } = createArray(rowCount, params); + const { array } = ColumnHelpers.createArray(rowCount, params); for (let i = 0, _i = array.length; i < _i; i++) array[i] = v(map[i]); return array; }, @@ -237,39 +257,37 @@ function permutationFull<T>(c: Column<T>, map: ArrayLike<number>): Column<T> { }; } -/** A helped function for Column.toArray */ -export function getArrayBounds(rowCount: number, params?: Column.ToArrayParams) { - const start = params && typeof params.start !== 'undefined' ? Math.max(Math.min(params.start, rowCount - 1), 0) : 0; - const end = params && typeof params.end !== 'undefined' ? Math.min(params.end, rowCount) : rowCount; - return { start, end }; -} +export namespace ColumnHelpers { + export function getArrayBounds(rowCount: number, params?: Column.ToArrayParams) { + const start = params && typeof params.start !== 'undefined' ? Math.max(Math.min(params.start, rowCount - 1), 0) : 0; + const end = params && typeof params.end !== 'undefined' ? Math.min(params.end, rowCount) : rowCount; + return { start, end }; + } -/** A helped function for Column.toArray */ -export function createArray(rowCount: number, params?: Column.ToArrayParams) { - const c = params && typeof params.array !== 'undefined' ? params.array : Array; - const { start, end } = getArrayBounds(rowCount, params); - return { array: new c(end - start) as any[], start, end }; -} + export function createArray(rowCount: number, params?: Column.ToArrayParams) { + const c = params && typeof params.array !== 'undefined' ? params.array : Array; + const { start, end } = getArrayBounds(rowCount, params); + return { array: new c(end - start) as any[], start, end }; + } -/** A helped function for Column.toArray */ -export function fillArrayValues(value: (row: number) => any, target: any[], start: number) { - for (let i = 0, _e = target.length; i < _e; i++) target[i] = value(start + i); - return target; -} + export function fillArrayValues(value: (row: number) => any, target: any[], start: number) { + for (let i = 0, _e = target.length; i < _e; i++) target[i] = value(start + i); + return target; + } -/** A helped function for Column.toArray */ -export function createAndFillArray(rowCount: number, value: (row: number) => any, params?: Column.ToArrayParams) { - const { array, start } = createArray(rowCount, params); - return fillArrayValues(value, array, start); -} + export function createAndFillArray(rowCount: number, value: (row: number) => any, params?: Column.ToArrayParams) { + const { array, start } = createArray(rowCount, params); + return fillArrayValues(value, array, start); + } -export function isTypedArray(data: any): boolean { - return !!data.buffer && typeof data.byteLength === 'number' && typeof data.BYTES_PER_ELEMENT === 'number'; -} + export function isTypedArray(data: any): boolean { + return !!data.buffer && typeof data.byteLength === 'number' && typeof data.BYTES_PER_ELEMENT === 'number'; + } -export function typedArrayWindow(data: any, params?: Column.ToArrayParams): ReadonlyArray<number> { - const { constructor, buffer, length, byteOffset, BYTES_PER_ELEMENT } = data; - const { start, end } = getArrayBounds(length, params); - if (start === 0 && end === length) return data; - return new constructor(buffer, byteOffset + BYTES_PER_ELEMENT * start, Math.min(length, end - start)); + export function typedArrayWindow(data: any, params?: Column.ToArrayParams): ReadonlyArray<number> { + const { constructor, buffer, length, byteOffset, BYTES_PER_ELEMENT } = data; + const { start, end } = getArrayBounds(length, params); + if (start === 0 && end === length) return data; + return new constructor(buffer, byteOffset + BYTES_PER_ELEMENT * start, Math.min(length, end - start)); + } } \ No newline at end of file diff --git a/src/mol-io/reader/_spec/column.spec.ts b/src/mol-io/reader/_spec/column.spec.ts index 7ce9499de04be3eb1ca50cc9bfa18f3b291cd007..aa6723a3d1dad0fca4b2135e8316ecabe1ec9962 100644 --- a/src/mol-io/reader/_spec/column.spec.ts +++ b/src/mol-io/reader/_spec/column.spec.ts @@ -7,7 +7,7 @@ import FixedColumn from '../common/text/column/fixed' import TokenColumn from '../common/text/column/token' -import Column, { typedArrayWindow } from '../../../mol-base/collections/column' +import Column, { ColumnHelpers } from '../../../mol-base/collections/column' const lines = [ '1.123 abc', @@ -64,8 +64,8 @@ describe('token text column', () => { describe('binary column', () => { it('window works', () => { const xs = new Float64Array([1, 2, 3, 4]); - const w1 = typedArrayWindow(xs, { start: 1 }); - const w2 = typedArrayWindow(xs, { start: 2, end: 4 }); + const w1 = ColumnHelpers.typedArrayWindow(xs, { start: 1 }); + const w2 = ColumnHelpers.typedArrayWindow(xs, { start: 2, end: 4 }); expect(w1.length).toBe(3); for (let i = 0; i < w1.length; i++) expect(w1[i]).toBe(xs[i + 1]); diff --git a/src/mol-io/reader/cif/binary/field.ts b/src/mol-io/reader/cif/binary/field.ts index 1fc47f4a65ce91f06ba752e7097323aef7f36f7e..5280f57bec8bc72d16ed9b03e36cfbb2d798088b 100644 --- a/src/mol-io/reader/cif/binary/field.ts +++ b/src/mol-io/reader/cif/binary/field.ts @@ -4,7 +4,7 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import Column, { isTypedArray, createAndFillArray, typedArrayWindow } from '../../../../mol-base/collections/column' +import Column, { ColumnHelpers } from '../../../../mol-base/collections/column' import * as Data from '../data-model' import { EncodedColumn } from './encoding' import decode from './decoder' @@ -13,7 +13,7 @@ import { parseInt as fastParseInt, parseFloat as fastParseFloat } from '../../co export default function Field(column: EncodedColumn): Data.Field { const mask = column.mask ? decode(column.mask) as number[] : void 0; const data = decode(column.data); - const isNumeric = isTypedArray(data); + const isNumeric = ColumnHelpers.isTypedArray(data); const str: Data.Field['str'] = isNumeric ? mask @@ -46,13 +46,12 @@ export default function Field(column: EncodedColumn): Data.Field { float, valueKind, areValuesEqual: (rowA, rowB) => data[rowA] === data[rowB], - stringEquals: (row, v) => str(row) === v, - toStringArray: params => createAndFillArray(rowCount, str, params), + toStringArray: params => ColumnHelpers.createAndFillArray(rowCount, str, params), toIntArray: isNumeric - ? params => typedArrayWindow(data, params) - : params => createAndFillArray(rowCount, int, params), + ? params => ColumnHelpers.typedArrayWindow(data, params) + : params => ColumnHelpers.createAndFillArray(rowCount, int, params), toFloatArray: isNumeric - ? params => typedArrayWindow(data, params) - : params => createAndFillArray(rowCount, float, params) + ? params => ColumnHelpers.typedArrayWindow(data, params) + : params => ColumnHelpers.createAndFillArray(rowCount, float, params) }; } \ No newline at end of file diff --git a/src/mol-io/reader/cif/binary/parser.ts b/src/mol-io/reader/cif/binary/parser.ts index 45f826f591a91447ca24ec96bcbc9447c73150e8..a2e86866215ad3e6cde054d8632feae197f7ccb1 100644 --- a/src/mol-io/reader/cif/binary/parser.ts +++ b/src/mol-io/reader/cif/binary/parser.ts @@ -25,7 +25,7 @@ function Category(data: Encoding.EncodedCategory): Data.Category { rowCount: data.rowCount, getField(name) { const col = map[name]; - return col ? Field(col) : Data.DefaultUndefinedField(data.rowCount); + return col ? Field(col) : void 0; } } } diff --git a/src/mol-io/reader/cif/data-model.ts b/src/mol-io/reader/cif/data-model.ts index 2de58e8ad0682bff11e9281e9c64bb4e75cbd431..d353c534a9b4e573282cc8bf0dcf6b71bf7718a5 100644 --- a/src/mol-io/reader/cif/data-model.ts +++ b/src/mol-io/reader/cif/data-model.ts @@ -4,7 +4,7 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import Column, { createArray } from '../../../mol-base/collections/column' +import Column from '../../../mol-base/collections/column' export interface File { readonly name?: string, @@ -68,32 +68,12 @@ export interface Field { valueKind(row: number): Column.ValueKind, areValuesEqual(rowA: number, rowB: number): boolean, - stringEquals(row: number, value: string): boolean, toStringArray(params?: Column.ToArrayParams): ReadonlyArray<string>, toIntArray(params?: Column.ToArrayParams): ReadonlyArray<number>, toFloatArray(params?: Column.ToArrayParams): ReadonlyArray<number> } -export function DefaultUndefinedField(rowCount: number): Field { - return { - '@array': void 0, - isDefined: false, - rowCount, - str: row => '', - int: row => 0, - float: row => 0, - - valueKind: row => Column.ValueKind.NotPresent, - areValuesEqual: (rowA, rowB) => true, - stringEquals: (row, value) => value === null, - - toStringArray: (p) => createArray(rowCount, p).array, - toIntArray: (p) => createArray(rowCount, p).array, - toFloatArray: (p) => createArray(rowCount, p).array - }; -} - export function getMatrix(category: Category, field: string, rows: number, cols: number, row: number) { const ret: number[][] = []; for (let i = 0; i < rows; i++) { diff --git a/src/mol-io/reader/cif/schema.ts b/src/mol-io/reader/cif/schema.ts index 113ed90849f66613882cfa45e440054c3dd66243..67c11c14a44bf5914be3ca710fc27eb39bf8c9b3 100644 --- a/src/mol-io/reader/cif/schema.ts +++ b/src/mol-io/reader/cif/schema.ts @@ -5,7 +5,7 @@ */ import * as Data from './data-model' -import Column, { createAndFillArray } from '../../../mol-base/collections/column' +import Column, { ColumnHelpers } from '../../../mol-base/collections/column' import Table from '../../../mol-base/collections/table' export function toTypedFrame<Schema extends FrameSchema, Frame extends TypedFrame<Schema> = TypedFrame<Schema>>(schema: Schema, frame: Data.Frame): Frame { @@ -34,12 +34,12 @@ function getColumnCtor(t: Column.Type): ColumnCtor { case 'vector': return (f, c, k) => { const dim = t.dim; const value = (row: number) => Data.getVector(c, k, dim, row); - return createColumn(t, f, value, params => createAndFillArray(f.rowCount, value, params)); + return createColumn(t, f, value, params => ColumnHelpers.createAndFillArray(f.rowCount, value, params)); } case 'matrix': return (f, c, k) => { const rows = t.rows, cols = t.cols; const value = (row: number) => Data.getMatrix(c, k, rows, cols, row); - return createColumn(t, f, value, params => createAndFillArray(f.rowCount, value, params)); + return createColumn(t, f, value, params => ColumnHelpers.createAndFillArray(f.rowCount, value, params)); } } } diff --git a/src/mol-io/reader/cif/text/field.ts b/src/mol-io/reader/cif/text/field.ts index 17454c0d791461a5b81926ed4d30a6b1c48f6930..86ba415c3e46504212d436f8620809f2eaa0f052 100644 --- a/src/mol-io/reader/cif/text/field.ts +++ b/src/mol-io/reader/cif/text/field.ts @@ -4,7 +4,7 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import Column, { createAndFillArray } from '../../../../mol-base/collections/column' +import Column, { ColumnHelpers } from '../../../../mol-base/collections/column' import * as TokenColumn from '../../common/text/column/token' import { Tokens } from '../../common/text/tokenizer' import * as Data from '../data-model' @@ -45,19 +45,8 @@ export default function CifTextField(tokens: Tokens, rowCount: number): Data.Fie float, valueKind, areValuesEqual: TokenColumn.areValuesEqualProvider(tokens), - stringEquals: (row, v) => { - const s = indices[2 * row]; - const value = v || ''; - if (!value && valueKind(row) !== Column.ValueKind.Present) return true; - const len = value.length; - if (len !== indices[2 * row + 1] - s) return false; - for (let i = 0; i < len; i++) { - if (data.charCodeAt(i + s) !== value.charCodeAt(i)) return false; - } - return true; - }, - toStringArray: params => createAndFillArray(rowCount, str, params), - toIntArray: params => createAndFillArray(rowCount, int, params), - toFloatArray: params => createAndFillArray(rowCount, float, params) + toStringArray: params => ColumnHelpers.createAndFillArray(rowCount, str, params), + toIntArray: params => ColumnHelpers.createAndFillArray(rowCount, int, params), + toFloatArray: params => ColumnHelpers.createAndFillArray(rowCount, float, params) } } \ No newline at end of file diff --git a/src/mol-io/reader/common/text/column/fixed.ts b/src/mol-io/reader/common/text/column/fixed.ts index 0dceba7acf5b17cd989cae04f5ad43a427bb608e..06a2a89d9ab2fd711afb9a4f7376c3179879f519 100644 --- a/src/mol-io/reader/common/text/column/fixed.ts +++ b/src/mol-io/reader/common/text/column/fixed.ts @@ -4,7 +4,7 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import Column, { createAndFillArray } from '../../../../../mol-base/collections/column' +import Column, { ColumnHelpers } from '../../../../../mol-base/collections/column' import { trimStr, Tokens } from '../tokenizer' import { parseIntSkipLeadingWhitespace, parseFloatSkipLeadingWhitespace } from '../number-parser' @@ -40,7 +40,7 @@ export function FixedColumn<T extends Column.Type>(lines: Tokens, offset: number rowCount, value, valueKind: row => Column.ValueKind.Present, - toArray: params => createAndFillArray(rowCount, value, params), + toArray: params => ColumnHelpers.createAndFillArray(rowCount, value, params), areValuesEqual: (rowA, rowB) => value(rowA) === value(rowB) }; } \ No newline at end of file diff --git a/src/mol-io/reader/common/text/column/token.ts b/src/mol-io/reader/common/text/column/token.ts index 89e6ac6ff60690d1fd8cac6194decd1cc08d8064..6284121aad3b6fae7f85fd73565ead8fc78e92bf 100644 --- a/src/mol-io/reader/common/text/column/token.ts +++ b/src/mol-io/reader/common/text/column/token.ts @@ -4,7 +4,7 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import Column, { createAndFillArray } from '../../../../../mol-base/collections/column' +import Column, { ColumnHelpers } from '../../../../../mol-base/collections/column' import { Tokens } from '../tokenizer' import { parseInt as fastParseInt, parseFloat as fastParseFloat } from '../number-parser' @@ -32,7 +32,7 @@ export function TokenColumn<T extends Column.Type>(tokens: Tokens, type: T): Col rowCount, value, valueKind: row => Column.ValueKind.Present, - toArray: params => createAndFillArray(rowCount, value, params), + toArray: params => ColumnHelpers.createAndFillArray(rowCount, value, params), areValuesEqual: areValuesEqualProvider(tokens) }; }