diff --git a/src/mol-base/collections/database.ts b/src/mol-base/collections/database.ts new file mode 100644 index 0000000000000000000000000000000000000000..520d690cd79e29d6465bdda28bc78d79e9559935 --- /dev/null +++ b/src/mol-base/collections/database.ts @@ -0,0 +1,12 @@ +/** + * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import Database from './database/database' +import Table from './database/table' +import Column from './database/column' +import * as ColumnHelpers from './database/column-helpers' + +export { Database, Table, Column, ColumnHelpers } \ No newline at end of file diff --git a/src/mol-base/collections/database/_spec/table.spec.ts b/src/mol-base/collections/database/_spec/table.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..c39c73e4fd591258ef1157d1cedc6ff58e2af732 --- /dev/null +++ b/src/mol-base/collections/database/_spec/table.spec.ts @@ -0,0 +1,122 @@ +/** + * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import * as ColumnHelpers from '../column-helpers' +import Column from '../column' +import Table from '../table' + +describe('column', () => { + const cc = Column.ofConst(10, 2, Column.Type.int); + const arr = Column.ofArray({ array: [1, 2, 3, 4], type: Column.Type.int }); + const arrWindow = Column.window(arr, 1, 3); + + const typed = Column.ofArray({ array: new Int32Array([1, 2, 3, 4]), type: Column.Type.int }); + const typedWindow = Column.window(typed, 1, 3); + + const numStr = Column.ofArray({ array: [1, 2] as any, type: Column.Type.str }); + + it('constant', () => { + expect(cc.rowCount).toBe(2); + expect(cc.value(0)).toBe(10); + }); + + it('arr', () => { + expect(arr.rowCount).toBe(4); + expect(arr.value(1)).toBe(2); + expect(arrWindow.value(0)).toBe(2); + expect(arrWindow.rowCount).toBe(2); + }); + + it('typed', () => { + expect(typedWindow.value(0)).toBe(2); + expect(typedWindow.rowCount).toBe(2); + expect(ColumnHelpers.isTypedArray(typedWindow.toArray())).toBe(true); + }); + + it('numStr', () => { + expect(numStr.value(0)).toBe('1'); + expect(numStr.toArray()).toEqual(['1', '2']); + }); + + it('view', () => { + expect(Column.view(arr, [1, 0, 3, 2]).toArray()).toEqual([2, 1, 4, 3]); + expect(Column.view(arr, [1, 3]).toArray()).toEqual([2, 4]); + }); + + it('map to array', () => { + expect(Column.mapToArray(arrWindow, x => x + 1)).toEqual([3, 4]); + }); +}) + +describe('table', () => { + const schema = { + x: Column.Type.int, + n: Column.Type.str + }; + + it('ofRows', () => { + const t = Table.ofRows(schema, [ + { x: 10, n: 'row1' }, + { x: -1, n: 'row2' }, + ]); + expect(t.x.toArray()).toEqual([10, -1]); + expect(t.n.toArray()).toEqual(['row1', 'row2']); + }); + + it('ofColumns', () => { + const t = Table.ofColumns(schema, { + x: Column.ofArray({ array: [10, -1], type: Column.Type.int }), + n: Column.ofArray({ array: ['row1', 'row2'], type: Column.Type.str }), + }); + expect(t.x.toArray()).toEqual([10, -1]); + expect(t.n.toArray()).toEqual(['row1', 'row2']); + }); + + it('ofArrays', () => { + const t = Table.ofArrays(schema, { + x: [10, -1], + n: ['row1', 'row2'], + }); + expect(t.x.toArray()).toEqual([10, -1]); + expect(t.n.toArray()).toEqual(['row1', 'row2']); + }); + + it('pickColumns', () => { + const t = Table.ofColumns(schema, { + x: Column.ofArray({ array: [10, -1], type: Column.Type.int }), + n: Column.ofArray({ array: ['row1', 'row2'], type: Column.Type.str }), + }); + const s = { x: Column.Type.int, y: Column.Type.int }; + const picked = Table.pickColumns(s, t, { y: Column.ofArray({ array: [3, 4], type: Column.Type.int })}); + expect(picked._columns).toEqual(['x', 'y']); + expect(picked._rowCount).toEqual(2); + expect(picked.x.toArray()).toEqual([10, -1]); + expect(picked.y.toArray()).toEqual([3, 4]); + }); + + it('view', () => { + const t = Table.ofColumns(schema, { + x: Column.ofArray({ array: [10, -1], type: Column.Type.int }), + n: Column.ofArray({ array: ['row1', 'row2'], type: Column.Type.str }), + }); + const s = { x: Column.Type.int }; + const view = Table.view(t, s, [1]); + expect(view._columns).toEqual(['x']); + expect(view._rowCount).toEqual(1); + expect(view.x.toArray()).toEqual([-1]); + }); + + it('sort', () => { + const t = Table.ofColumns<typeof schema>(schema, { + x: Column.ofArray({ array: [10, -1], type: Column.Type.int }), + n: Column.ofArray({ array: ['row1', 'row2'], type: Column.Type.str }), + }); + const { x } = t; + const sorted = Table.sort(t, (i, j) => x.value(i) - x.value(j)) + expect(sorted.x.toArray()).toEqual([-1, 10]); + expect(sorted.n.toArray()).toEqual(['row2', 'row1']); + }); +}); \ No newline at end of file diff --git a/src/mol-base/collections/database/column-helpers.ts b/src/mol-base/collections/database/column-helpers.ts new file mode 100644 index 0000000000000000000000000000000000000000..b57deec09a41d9c88c346066058c962a88de9d6d --- /dev/null +++ b/src/mol-base/collections/database/column-helpers.ts @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import Column from './column' + +export function getArrayBounds(rowCount: number, params?: Column.ToArrayParams<any>) { + 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 function createArray(rowCount: number, params?: Column.ToArrayParams<any>) { + 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 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 createAndFillArray(rowCount: number, value: (row: number) => any, params?: Column.ToArrayParams<any>) { + 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 typedArrayWindow(data: any, params?: Column.ToArrayParams<any>): 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-base/collections/database/column.ts b/src/mol-base/collections/database/column.ts new file mode 100644 index 0000000000000000000000000000000000000000..9f0263306e0f5fe5c10866c27cb7729856a0e995 --- /dev/null +++ b/src/mol-base/collections/database/column.ts @@ -0,0 +1,309 @@ +/** + * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import * as ColumnHelpers from './column-helpers' + +interface Column<T> { + readonly '@type': Column.Type, + readonly '@array': ArrayLike<any> | undefined, + + readonly isDefined: boolean, + readonly rowCount: number, + value(row: number): T, + valueKind(row: number): Column.ValueKind, + toArray(params?: Column.ToArrayParams<T>): ReadonlyArray<T>, + areValuesEqual(rowA: number, rowB: number): boolean +} + +namespace Column { + export type Type<T = any> = Type.Str | Type.Int | Type.Float | Type.Vector | Type.Matrix | Type.Aliased<T> + export type ArrayCtor<T> = { new(size: number): ArrayLike<T> } + + export namespace Type { + export type Str = { T: string, kind: 'str' } + export type Int = { T: number, kind: 'int' } + export type Float = { T: number, kind: 'float' } + export type Vector = { T: number[], dim: number, kind: 'vector' }; + export type Matrix = { T: number[][], rows: number, cols: number, kind: 'matrix' }; + export type Aliased<T> = { T: T } & { kind: 'str' | 'int' | 'float' } + + export const str: Str = { T: '', kind: 'str' }; + export const int: Int = { T: 0, kind: 'int' }; + export const float: Float = { T: 0, kind: 'float' }; + + export function vector(dim: number): Vector { return { T: [] as number[], dim, kind: 'vector' }; } + export function matrix(rows: number, cols: number): Matrix { return { T: [] as number[][], rows, cols, kind: 'matrix' }; } + export function aliased<T>(t: Type): Aliased<T> { return t as any as Aliased<T>; } + } + + export interface ToArrayParams<T> { + array?: ArrayCtor<T>, + start?: number, + /** Last row (exclusive) */ + end?: number + } + + export interface LambdaSpec<T extends Type> { + value: (row: number) => T['T'], + rowCount: number, + type: T, + valueKind?: (row: number) => ValueKind, + } + + export interface ArraySpec<T extends Type> { + array: ArrayLike<T['T']>, + type: T, + valueKind?: (row: number) => ValueKind + } + + export interface MapSpec<S extends Type, T extends Type> { + f: (v: S['T']) => T['T'], + type: T, + valueKind?: (row: number) => ValueKind, + } + + export const enum ValueKind { Present = 0, NotPresent = 1, Unknown = 2 } + + export function Undefined<T extends Type>(rowCount: number, type: T): Column<T['T']> { + return constColumn(type['T'], rowCount, type, ValueKind.NotPresent); + } + + export function ofConst<T extends Type>(v: T['T'], rowCount: number, type: T): Column<T['T']> { + return constColumn(v, rowCount, type, ValueKind.Present); + } + + export function ofLambda<T extends Type>(spec: LambdaSpec<T>): Column<T['T']> { + return lambdaColumn(spec); + } + + export function ofArray<T extends Column.Type>(spec: Column.ArraySpec<T>): Column<T['T']> { + return arrayColumn(spec); + } + + export function ofIntArray(array: ArrayLike<number>) { + return arrayColumn({ array, type: Type.int }); + } + + export function ofFloatArray(array: ArrayLike<number>) { + return arrayColumn({ array, type: Type.float }); + } + + export function ofStringArray(array: ArrayLike<string>) { + return arrayColumn({ array, type: Type.str }); + } + + export function window<T>(column: Column<T>, start: number, end: number) { + return windowColumn(column, start, end); + } + + export function view<T>(column: Column<T>, indices: ArrayLike<number>, checkIndentity = true) { + return columnView(column, indices, checkIndentity); + } + + /** A map of the 1st occurence of each value. */ + export function createFirstIndexMap<T>(column: Column<T>) { + return createFirstIndexMapOfColumn(column); + } + + export function mapToArray<T, S>(column: Column<T>, f: (v: T) => S, ctor?: ArrayCtor<S>): ArrayLike<S> { + return mapToArrayImpl(column, f, ctor || Array); + } + + export function areEqual<T>(a: Column<T>, b: Column<T>) { + return areColumnsEqual(a, b); + } + + /** Makes the column backned by an array. Useful for columns that accessed often. */ + export function asArrayColumn<T>(c: Column<T>, array?: ArrayCtor<T>): Column<T> { + if (c['@array']) return c; + if (!c.isDefined) return Undefined(c.rowCount, c['@type']) as any as Column<T>; + return arrayColumn({ array: c.toArray({ array }), type: c['@type'] as any, valueKind: c.valueKind }); + } +} + +export default Column; + +function createFirstIndexMapOfColumn<T>(c: Column<T>): Map<T, number> { + 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 { + '@type': type, + '@array': void 0, + isDefined: valueKind === Column.ValueKind.Present, + rowCount, + value, + valueKind: row => valueKind, + toArray: params => { + const { array } = ColumnHelpers.createArray(rowCount, params); + for (let i = 0, _i = array.length; i < _i; i++) array[i] = v; + return array; + }, + areValuesEqual: (rowA, rowB) => true + } +} + +function lambdaColumn<T extends Column.Type>({ value, valueKind, rowCount, type }: Column.LambdaSpec<T>): Column<T['T']> { + return { + '@type': type, + '@array': void 0, + isDefined: true, + rowCount, + value, + valueKind: valueKind ? valueKind : row => Column.ValueKind.Present, + toArray: 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; + }, + areValuesEqual: (rowA, rowB) => value(rowA) === value(rowB) + } +} + +function arrayColumn<T extends Column.Type>({ array, type, valueKind }: Column.ArraySpec<T>): Column<T['T']> { + const rowCount = array.length; + const value: Column<T['T']>['value'] = type.kind === 'str' + ? row => { const v = array[row]; return typeof v === 'string' ? v : '' + v; } + : row => array[row]; + + const isTyped = ColumnHelpers.isTypedArray(array); + return { + '@type': type, + '@array': array, + isDefined: true, + rowCount, + value, + valueKind: valueKind ? valueKind : row => Column.ValueKind.Present, + toArray: type.kind === 'str' + ? 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]; + ret[i] = typeof v === 'string' ? v : '' + v; + } + return ret; + } + : isTyped + ? params => ColumnHelpers.typedArrayWindow(array, params) as any as ReadonlyArray<T> + : 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]; + return ret; + }, + areValuesEqual: (rowA, rowB) => array[rowA] === array[rowB] + } +} + +function windowColumn<T>(column: Column<T>, start: number, end: number) { + if (!column.isDefined) return Column.Undefined(end - start, column['@type']); + 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 = ColumnHelpers.typedArrayWindow(c['@array'], { start, end }); + return arrayColumn({ array, type: c['@type'], valueKind: c.valueKind }) as any; +} + +function windowFull<T>(c: Column<T>, start: number, end: number): Column<T> { + const v = c.value, vk = c.valueKind, ave = c.areValuesEqual; + const value: Column<T>['value'] = start === 0 ? v : row => v(row + start); + const rowCount = end - start; + return { + '@type': c['@type'], + '@array': void 0, + isDefined: c.isDefined, + rowCount, + value, + valueKind: start === 0 ? vk : row => vk(row + start), + toArray: params => { + const { array } = ColumnHelpers.createArray(rowCount, params); + for (let i = 0, _i = array.length; i < _i; i++) array[i] = v(i + start); + return array; + }, + areValuesEqual: start === 0 ? ave : (rowA, rowB) => ave(rowA + start, rowB + start) + }; +} + +function isIdentity(map: ArrayLike<number>, rowCount: number) { + if (map.length !== rowCount) return false; + for (let i = 0, _i = map.length; i < _i; i++) { + if (map[i] !== i) return false; + } + return true; +} + +function columnView<T>(c: Column<T>, map: ArrayLike<number>, checkIdentity: boolean): Column<T> { + if (!c.isDefined) return c; + if (checkIdentity && isIdentity(map, c.rowCount)) return c; + if (!!c['@array']) return arrayView(c, map); + return viewFull(c, map); +} + +function arrayView<T>(c: Column<T>, map: ArrayLike<number>): Column<T> { + const array = c['@array']!; + const ret = new (array as any).constructor(map.length); + for (let i = 0, _i = map.length; i < _i; i++) ret[i] = array[map[i]]; + return arrayColumn({ array: ret, type: c['@type'], valueKind: c.valueKind }); +} + +function viewFull<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]); + const rowCount = map.length; + return { + '@type': c['@type'], + '@array': void 0, + isDefined: c.isDefined, + rowCount, + value, + valueKind: row => vk(map[row]), + toArray: params => { + const { array } = ColumnHelpers.createArray(rowCount, params); + for (let i = 0, _i = array.length; i < _i; i++) array[i] = v(map[i]); + return array; + }, + areValuesEqual: (rowA, rowB) => ave(map[rowA], map[rowB]) + }; +} + +function mapToArrayImpl<T, S>(c: Column<T>, f: (v: T) => S, ctor: Column.ArrayCtor<S>): ArrayLike<S> { + const ret = new ctor(c.rowCount) as any; + for (let i = 0, _i = c.rowCount; i < _i; i++) ret[i] = f(c.value(i)); + return ret; +} + +function areColumnsEqual(a: Column<any>, b: Column<any>) { + if (a.rowCount !== b.rowCount || a.isDefined !== b.isDefined || a['@type'].kind !== b['@type'].kind) return false; + if (!!a['@array'] && !!b['@array']) return areArraysEqual(a, b); + return areValuesEqual(a, b); +} + +function areArraysEqual(a: Column<any>, b: Column<any>) { + const xs = a['@array']!, ys = b['@array']!; + for (let i = 0, _i = a.rowCount; i < _i; i++) { + if (xs[i] !== ys[i]) return false; + } + return true; +} + +function areValuesEqual(a: Column<any>, b: Column<any>) { + const va = a.value, vb = b.value; + for (let i = 0, _i = a.rowCount; i < _i; i++) { + if (va(i) !== vb(i)) return false; + } + return true; +} \ No newline at end of file diff --git a/src/mol-base/collections/database/database.ts b/src/mol-base/collections/database/database.ts new file mode 100644 index 0000000000000000000000000000000000000000..b4d5d0053aef32b997bb6aaeff328ca9b57bde6f --- /dev/null +++ b/src/mol-base/collections/database/database.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 Table from './table' + +/** A collection of tables */ +type Database<Schema extends Database.Schema> = { + readonly _name: string, + readonly _tableNames: string[], + readonly _schema: Schema +} & Database.Tables<Schema> + +namespace Database { + export type Tables<S extends Schema> = { [T in keyof Schema]: Table<S[T]> } + export type Schema = { [table: string]: Table.Schema } + + export function ofTables<S extends Schema, Db = Database<S>>(name: string, schema: Schema, tables: Tables<S>): Db { + const keys = Object.keys(tables); + const ret = Object.create(null); + const tableNames: string[] = []; + ret._name = name; + ret._tableNames = tableNames; + ret._schema = schema; + for (const k of keys) { + if (!Table.is(tables[k])) continue; + ret[k] = tables[k]; + tableNames[tableNames.length] = k; + } + return ret; + } +} + +export default Database \ No newline at end of file diff --git a/src/mol-base/collections/database/table.ts b/src/mol-base/collections/database/table.ts new file mode 100644 index 0000000000000000000000000000000000000000..9d376f13491ce6d5a3a3bfa9e7f8cdff0460c9ca --- /dev/null +++ b/src/mol-base/collections/database/table.ts @@ -0,0 +1,135 @@ +/** + * Copyright (c) 2017 molio contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import Column from './column' +import { sortArray } from '../sort' + +/** A collection of columns */ +type Table<Schema extends Table.Schema> = { + readonly _rowCount: number, + readonly _columns: ReadonlyArray<string>, + readonly _schema: Schema +} & Table.Columns<Schema> + +/** An immutable table */ +namespace Table { + export type Schema = { [field: string]: Column.Type } + export type Columns<S extends Schema> = { [C in keyof S]: Column<S[C]['T']> } + export type Row<S extends Schema> = { [C in keyof S]: S[C]['T'] } + export type Arrays<S extends Schema> = { [C in keyof S]: ArrayLike<S[C]['T']> } + export type PartialTable<S extends Table.Schema> = { readonly _rowCount: number, readonly _columns: ReadonlyArray<string> } & { [C in keyof S]?: Column<S[C]['T']> } + + export function is(t: any): t is Table<any> { + return t && typeof t._rowCount === 'number' && !!t._columns && !!t._schema; + } + + export function pickColumns<S extends Schema>(schema: S, table: PartialTable<S>, guard: Partial<Columns<S>> = {}): Table<S> { + const ret = Object.create(null); + const keys = Object.keys(schema); + ret._rowCount = table._rowCount; + ret._columns = keys; + ret._schema = schema; + for (const k of keys) { + if (!!table[k]) ret[k] = table[k]; + else if (!!guard[k]) ret[k] = guard[k]; + else throw Error(`Cannot find column '${k}'.`); + } + return ret; + } + + export function ofColumns<S extends Schema, R extends Table<S> = Table<S>>(schema: S, columns: Columns<S>): R { + const _columns = Object.keys(columns); + const _rowCount = columns[_columns[0]].rowCount; + return { _rowCount, _columns, _schema: schema, ...(columns as any) }; + } + + export function ofRows<S extends Schema, R extends Table<S> = Table<S>>(schema: Schema, rows: ArrayLike<Row<S>>): R { + const ret = Object.create(null); + const rowCount = rows.length; + const columns = Object.keys(schema); + ret._rowCount = rowCount; + ret._columns = columns; + ret._schema = schema; + for (const k of columns) { + (ret as any)[k] = Column.ofLambda({ + rowCount, + type: schema[k], + value: r => rows[r][k], + valueKind: r => typeof rows[r][k] === 'undefined' ? Column.ValueKind.NotPresent : Column.ValueKind.Present + }) + } + return ret as R; + } + + export function ofArrays<S extends Schema, R extends Table<S> = Table<S>>(schema: Schema, arrays: Arrays<S>): R { + const ret = Object.create(null); + const columns = Object.keys(schema); + ret._rowCount = arrays[columns[0]].length; + ret._columns = columns; + ret._schema = schema; + for (const k of columns) { + (ret as any)[k] = Column.ofArray({ array: arrays[k], type: schema[k] }) + } + return ret as R; + } + + export function view<S extends R, R extends Schema>(table: Table<S>, schema: R, view: ArrayLike<number>) { + const ret = Object.create(null); + const columns = Object.keys(schema); + ret._rowCount = view.length; + ret._columns = columns; + ret._schema = schema; + for (const k of columns) { + (ret as any)[k] = Column.view(table[k], view); + } + return ret as Table<R>; + } + + export function columnToArray<S extends Schema>(table: Table<S>, name: keyof S, array?: Column.ArrayCtor<any>) { + table[name] = Column.asArrayColumn(table[name], array); + } + + /** Sort and return a new table */ + export function sort<T extends Table<S>, S extends Schema>(table: T, cmp: (i: number, j: number) => number) { + const indices = new Int32Array(table._rowCount); + for (let i = 0, _i = indices.length; i < _i; i++) indices[i] = i; + sortArray(indices, (_, i, j) => cmp(i, j)); + + let isIdentity = true; + for (let i = 0, _i = indices.length; i < _i; i++) { + if (indices[i] !== i) { + isIdentity = false; + break; + } + } + if (isIdentity) return table; + + const ret = Object.create(null); + ret._rowCount = table._rowCount; + ret._columns = table._columns; + ret._schema = table._schema; + for (const c of table._columns) { + ret[c] = Column.view((table as any)[c], indices, false); + } + return ret; + } + + export function areEqual<T extends Table<Schema>>(a: T, b: T) { + if (a._rowCount !== b._rowCount) return false; + if (a._columns.length !== b._columns.length) return false; + for (const c of a._columns) { + if (!b[c]) return false; + } + + for (const c of a._columns) { + if (!Column.areEqual(a[c], b[c])) return false; + } + + return true; + } +} + +export default Table \ No newline at end of file diff --git a/src/mol-io/writer/cif/TODO b/src/mol-io/writer/cif/TODO new file mode 100644 index 0000000000000000000000000000000000000000..c6e88be3bcd77787b6b50dfa3819e3d06d5ab1c1 --- /dev/null +++ b/src/mol-io/writer/cif/TODO @@ -0,0 +1 @@ +- Make a writer that takes a database and produces a CIF/BinaryCIF file. \ No newline at end of file