Skip to content
Snippets Groups Projects
Commit a81464dd authored by David Sehnal's avatar David Sehnal
Browse files

Table collection

parent ab2031f7
No related branches found
No related tags found
No related merge requests found
......@@ -5,6 +5,7 @@
*/
import Column, { isTypedArray } from '../column'
import Table from '../table'
describe('column', () => {
const cc = Column.ofConst(10, 2, Column.Type.int);
......@@ -38,4 +39,44 @@ describe('column', () => {
expect(numStr.value(0)).toBe('1');
expect(numStr.toArray()).toEqual(['1', '2']);
});
})
\ No newline at end of file
it('permutation', () => {
expect(Column.permutation(arr, [1, 0, 3, 2]).toArray()).toEqual([2, 1, 4, 3]);
});
})
describe('table', () => {
const schema = {
x: Column.Type.int,
n: Column.Type.str
};
it('ofRows', () => {
const t = Table.ofRows<typeof schema>(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<typeof 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('sort', () => {
const t = Table.ofColumns<typeof 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
......@@ -13,19 +13,22 @@ interface Column<T> {
value(row: number): T,
valueKind(row: number): Column.ValueKind,
toArray(params?: Column.ToArrayParams): ReadonlyArray<T>,
stringEquals(row: number, value: string): boolean,
areValuesEqual(rowA: number, rowB: number): boolean
}
namespace Column {
export type Type = typeof Type.str | typeof Type.int | typeof Type.float | typeof Type.vector | typeof Type.matrix
export type Type = typeof Type.str | typeof Type.int | typeof Type.float | Type.Vector | Type.Matrix
export namespace Type {
export const str = { '@type': '' as string, kind: 'str' as 'str' };
export const int = { '@type': 0 as number, kind: 'int' as 'int' };
export const float = { '@type': 0 as number, kind: 'float' as 'float' };
export const vector = { '@type': [] as number[], kind: 'vector' as 'vector' };
export const matrix = { '@type': [] as number[][], kind: 'matrix' as 'matrix' };
export const str = { T: '' as string, kind: 'str' as 'str' };
export const int = { T: 0 as number, kind: 'int' as 'int' };
export const float = { T: 0 as number, kind: 'float' as 'float' };
export type Vector = { T: number[], dim: number, kind: 'vector' };
export type Matrix = { T: number[][], rows: number, cols: number, kind: 'matrix' };
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 interface ToArrayParams {
......@@ -36,33 +39,39 @@ namespace Column {
}
export interface LambdaSpec<T extends Type> {
value: (row: number) => T['@type'],
value: (row: number) => T['T'],
rowCount: number,
type: T,
valueKind?: (row: number) => ValueKind,
}
export interface ArraySpec<T extends Type> {
array: ArrayLike<T['@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['@type']> {
return constColumn(type['@type'], rowCount, type, ValueKind.NotPresent);
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['@type'], rowCount: number, type: T): Column<T['@type']> {
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['@type']> {
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['@type']> {
export function ofArray<T extends Column.Type>(spec: Column.ArraySpec<T>): Column<T['T']> {
return arrayColumn(spec);
}
......@@ -70,6 +79,10 @@ namespace Column {
return windowColumn(column, start, end);
}
export function permutation<T>(column: Column<T>, indices: ArrayLike<number>, checkIndentity = true) {
return columnPermutation(column, indices, checkIndentity);
}
/** 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;
......@@ -80,8 +93,8 @@ namespace Column {
export default Column;
function constColumn<T extends Column.Type>(v: T['@type'], rowCount: number, type: T, valueKind: Column.ValueKind): Column<T['@type']> {
const value: Column<T['@type']>['value'] = row => v;
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,
......@@ -94,16 +107,11 @@ function constColumn<T extends Column.Type>(v: T['@type'], rowCount: number, typ
for (let i = 0, _i = array.length; i < _i; i++) array[i] = v;
return array;
},
stringEquals: type.kind === 'str'
? (row, value) => value === v
: type.kind === 'float' || type.kind === 'int'
? (row, value) => +value === v
: (row, value) => false,
areValuesEqual: (rowA, rowB) => true
}
}
function lambdaColumn<T extends Column.Type>({ value, valueKind, rowCount, type }: Column.LambdaSpec<T>): Column<T['@type']> {
function lambdaColumn<T extends Column.Type>({ value, valueKind, rowCount, type }: Column.LambdaSpec<T>): Column<T['T']> {
return {
'@type': type,
'@array': void 0,
......@@ -116,18 +124,13 @@ function lambdaColumn<T extends Column.Type>({ value, valueKind, rowCount, type
for (let i = 0, _i = array.length; i < _i; i++) array[i] = value(i + start);
return array;
},
stringEquals: type.kind === 'str'
? (row, v) => value(row) === v
: type.kind === 'float' || type.kind === 'int'
? (row, v) => value(row) === +v
: (row, value) => false,
areValuesEqual: (rowA, rowB) => value(rowA) === value(rowB)
}
}
function arrayColumn<T extends Column.Type>({ array, type, valueKind }: Column.ArraySpec<T>): Column<T['@type']> {
function arrayColumn<T extends Column.Type>({ array, type, valueKind }: Column.ArraySpec<T>): Column<T['T']> {
const rowCount = array.length;
const value: Column<T['@type']>['value'] = type.kind === 'str'
const value: Column<T['T']>['value'] = type.kind === 'str'
? row => { const v = array[row]; return typeof v === 'string' ? v : '' + v; }
: row => array[row];
......@@ -153,16 +156,11 @@ function arrayColumn<T extends Column.Type>({ array, type, valueKind }: Column.A
? params => typedArrayWindow(array, params) as any as ReadonlyArray<T>
: params => {
const { start, end } = getArrayBounds(rowCount, params);
if (start === 0 && end === array.length) return array as ReadonlyArray<T['@type']>;
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;
},
stringEquals: type.kind === 'int' || type.kind === 'float'
? (row, value) => (array as any)[row] === +value
: type.kind === 'str'
? (row, value) => { const v = array[row]; return typeof v === 'string' ? v === value : +v === +value; }
: (row, value) => false,
areValuesEqual: (rowA, rowB) => array[rowA] === array[rowB]
}
}
......@@ -179,7 +177,7 @@ function windowTyped<T>(c: Column<T>, start: number, end: number): Column<T> {
}
function windowFull<T>(c: Column<T>, start: number, end: number): Column<T> {
const v = c.value, vk = c.valueKind, se = c.stringEquals, ave = c.areValuesEqual;
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 {
......@@ -194,11 +192,45 @@ function windowFull<T>(c: Column<T>, start: number, end: number): Column<T> {
for (let i = 0, _i = array.length; i < _i; i++) array[i] = v(i + start);
return array;
},
stringEquals: start === 0 ? se : (row, value) => se(row + start, value),
areValuesEqual: start === 0 ? ave : (rowA, rowB) => ave(rowA + start, rowB + start)
};
}
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;
}
return permutationFull(c, map);
}
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]);
const rowCount = c.rowCount;
return {
'@type': c['@type'],
'@array': void 0,
isDefined: c.isDefined,
rowCount,
value,
valueKind: row => vk(map[row]),
toArray: params => {
const { array } = 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])
};
}
/** 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;
......
/**
* 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'
type Table<Schema extends Table.Schema> = { readonly _rowCount: number, readonly _columns: ReadonlyArray<string> } & Table.Columns<Schema>
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 function ofColumns<S extends Schema, R extends Table<S> = Table<S>>(columns: Columns<S>): R {
const _columns = Object.keys(columns);
const _rowCount = columns[_columns[0]].rowCount;
return { _rowCount, _columns, ...(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;
for (const k of Object.keys(schema)) {
(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;
}
/** 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;
for (const c of table._columns) {
ret[c] = Column.permutation((table as any)[c], indices, false);
}
return ret;
}
}
export default Table
\ No newline at end of file
......@@ -66,7 +66,6 @@ export namespace Field {
rowCount: field.rowCount,
value,
valueKind: field.valueKind,
stringEquals: field.stringEquals,
areValuesEqual: field.areValuesEqual,
toArray
};
......@@ -79,14 +78,14 @@ export namespace Field {
function Vector(rows: number) {
return function(field: Data.Field, category: Data.Category, key: string) {
const value = (row: number) => Data.getVector(category, key, rows, row);
return create(Column.Type.vector, field, value, params => createAndFillArray(field.rowCount, value, params));
return create(Column.Type.vector(rows), field, value, params => createAndFillArray(field.rowCount, value, params));
}
}
function Matrix(rows: number, cols: number) {
return function(field: Data.Field, category: Data.Category, key: string) {
const value = (row: number) => Data.getMatrix(category, key, rows, cols, row);
return create(Column.Type.matrix, field, value, params => createAndFillArray(field.rowCount, value, params));
return create(Column.Type.matrix(rows, cols), field, value, params => createAndFillArray(field.rowCount, value, params));
}
}
......
......@@ -14,11 +14,11 @@ export default function FixedColumnProvider(lines: Tokens) {
}
}
export function FixedColumn<T extends Column.Type>(lines: Tokens, offset: number, width: number, type: T): Column<T['@type']> {
export function FixedColumn<T extends Column.Type>(lines: Tokens, offset: number, width: number, type: T): Column<T['T']> {
const { data, indices, count: rowCount } = lines;
const { kind } = type;
const value: Column<T['@type']>['value'] = kind === 'str' ? row => {
const value: Column<T['T']>['value'] = kind === 'str' ? row => {
let s = indices[2 * row] + offset, le = indices[2 * row + 1];
if (s >= le) return '';
let e = s + width;
......@@ -41,7 +41,6 @@ export function FixedColumn<T extends Column.Type>(lines: Tokens, offset: number
value,
valueKind: row => Column.ValueKind.Present,
toArray: params => createAndFillArray(rowCount, value, params),
stringEquals: (row, v) => value(row) === v,
areValuesEqual: (rowA, rowB) => value(rowA) === value(rowB)
};
}
\ No newline at end of file
......@@ -14,11 +14,11 @@ export default function TokenColumnProvider(tokens: Tokens) {
}
}
export function TokenColumn<T extends Column.Type>(tokens: Tokens, type: T): Column<T['@type']> {
export function TokenColumn<T extends Column.Type>(tokens: Tokens, type: T): Column<T['T']> {
const { data, indices, count: rowCount } = tokens;
const { kind } = type;
const value: Column<T['@type']>['value'] =
const value: Column<T['T']>['value'] =
kind === 'str'
? row => data.substring(indices[2 * row], indices[2 * row + 1])
: kind === 'int'
......@@ -33,16 +33,6 @@ export function TokenColumn<T extends Column.Type>(tokens: Tokens, type: T): Col
value,
valueKind: row => Column.ValueKind.Present,
toArray: params => createAndFillArray(rowCount, value, params),
stringEquals: (row, v) => {
const s = indices[2 * row];
const value = v || '';
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;
},
areValuesEqual: areValuesEqualProvider(tokens)
};
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment