table.ts 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. /**
  2. * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import Column from './column'
  7. import { sortArray } from '../util/sort'
  8. import { StringBuilder } from 'mol-util';
  9. /** A collection of columns */
  10. type Table<Schema extends Table.Schema> = {
  11. readonly _rowCount: number,
  12. readonly _columns: ReadonlyArray<string>,
  13. readonly _schema: Schema
  14. } & Table.Columns<Schema>
  15. /** An immutable table */
  16. namespace Table {
  17. export type Schema = { [field: string]: Column.Schema }
  18. export type Columns<S extends Schema> = { [C in keyof S]: Column<S[C]['T']> }
  19. export type Row<S extends Schema> = { [C in keyof S]: S[C]['T'] }
  20. export type Arrays<S extends Schema> = { [C in keyof S]: ArrayLike<S[C]['T']> }
  21. export type PartialTable<S extends Table.Schema> = { readonly _rowCount: number, readonly _columns: ReadonlyArray<string> } & { [C in keyof S]?: Column<S[C]['T']> }
  22. export function is(t: any): t is Table<any> {
  23. return t && typeof t._rowCount === 'number' && !!t._columns && !!t._schema;
  24. }
  25. export function pickColumns<S extends Schema>(schema: S, table: PartialTable<S>, guard: Partial<Columns<S>> = {}): Table<S> {
  26. const ret = Object.create(null);
  27. const keys = Object.keys(schema);
  28. ret._rowCount = table._rowCount;
  29. ret._columns = keys;
  30. ret._schema = schema;
  31. for (const k of keys) {
  32. if (!!table[k]) ret[k] = table[k];
  33. else if (!!guard[k]) ret[k] = guard[k];
  34. else throw Error(`Cannot find column '${k}'.`);
  35. }
  36. return ret;
  37. }
  38. export function ofColumns<S extends Schema, R extends Table<S> = Table<S>>(schema: S, columns: Columns<S>): R {
  39. const _columns = Object.keys(columns);
  40. const _rowCount = columns[_columns[0]].rowCount;
  41. return { _rowCount, _columns, _schema: schema, ...(columns as any) };
  42. }
  43. export function ofUndefinedColumns<S extends Schema, R extends Table<S> = Table<S>>(schema: S, rowCount: number): R {
  44. const ret = Object.create(null);
  45. const columns = Object.keys(schema);
  46. ret._rowCount = rowCount;
  47. ret._columns = columns;
  48. ret._schema = schema;
  49. for (const k of columns) {
  50. ret[k] = Column.Undefined(rowCount, schema[k])
  51. }
  52. return ret;
  53. }
  54. export function ofRows<S extends Schema, R extends Table<S> = Table<S>>(schema: Schema, rows: ArrayLike<Partial<Row<S>>>): R {
  55. const ret = Object.create(null);
  56. const rowCount = rows.length;
  57. const columns = Object.keys(schema);
  58. ret._rowCount = rowCount;
  59. ret._columns = columns;
  60. ret._schema = schema;
  61. for (const k of columns) {
  62. (ret as any)[k] = Column.ofLambda({
  63. rowCount,
  64. schema: schema[k],
  65. value: r => rows[r][k],
  66. valueKind: r => typeof rows[r][k] === 'undefined' ? Column.ValueKind.NotPresent : Column.ValueKind.Present
  67. })
  68. }
  69. return ret as R;
  70. }
  71. export function ofArrays<S extends Schema, R extends Table<S> = Table<S>>(schema: Schema, arrays: Arrays<S>): R {
  72. const ret = Object.create(null);
  73. const columns = Object.keys(schema);
  74. ret._rowCount = arrays[columns[0]].length;
  75. ret._columns = columns;
  76. ret._schema = schema;
  77. for (const k of columns) {
  78. (ret as any)[k] = typeof arrays[k] !== 'undefined' ? Column.ofArray({ array: arrays[k], schema: schema[k] }) : Column.Undefined(ret._rowCount, schema[k]);
  79. }
  80. return ret as R;
  81. }
  82. export function view<S extends R, R extends Schema>(table: Table<S>, schema: R, view: ArrayLike<number>) {
  83. const ret = Object.create(null);
  84. const columns = Object.keys(schema);
  85. ret._rowCount = view.length;
  86. ret._columns = columns;
  87. ret._schema = schema;
  88. for (const k of columns) {
  89. (ret as any)[k] = Column.view(table[k], view);
  90. }
  91. return ret as Table<R>;
  92. }
  93. export function pick<S extends R, R extends Schema>(table: Table<S>, schema: R, test: (i: number) => boolean) {
  94. const _view: number[] = []
  95. for (let i = 0, il = table._rowCount; i < il; ++i) {
  96. if (test(i)) _view.push(i)
  97. }
  98. return view(table, schema, _view)
  99. }
  100. export function window<S extends R, R extends Schema>(table: Table<S>, schema: R, start: number, end: number) {
  101. if (start === 0 && end === table._rowCount) return table;
  102. const ret = Object.create(null);
  103. const columns = Object.keys(schema);
  104. ret._rowCount = end - start;
  105. ret._columns = columns;
  106. ret._schema = schema;
  107. for (const k of columns) {
  108. (ret as any)[k] = Column.window(table[k], start, end);
  109. }
  110. return ret as Table<R>;
  111. }
  112. export function concat<S extends R, R extends Schema>(tables: Table<S>[], schema: R) {
  113. const ret = Object.create(null);
  114. const columns = Object.keys(schema);
  115. ret._rowCount = 0
  116. for (const table of tables) {
  117. ret._rowCount += table._rowCount
  118. }
  119. const arrays: any = {}
  120. for (const column of columns) {
  121. arrays[column] = new Array(ret._rowCount)
  122. }
  123. ret._columns = columns;
  124. ret._schema = schema;
  125. let offset = 0
  126. for (const table of tables) {
  127. for (const k of columns) {
  128. Column.copyToArray(table[k], arrays[k], offset)
  129. }
  130. offset += table._rowCount
  131. }
  132. for (const k of columns) {
  133. ret[k] = Column.ofArray({ array: arrays[k], schema: schema[k] })
  134. }
  135. return ret as Table<R>;
  136. }
  137. export function columnToArray<S extends Schema>(table: Table<S>, name: keyof S, array?: Column.ArrayCtor<any>) {
  138. (table as Columns<S>)[name] = Column.asArrayColumn((table as Columns<S>)[name], array);
  139. }
  140. /** Sort and return a new table */
  141. export function sort<T extends Table<S>, S extends Schema>(table: T, cmp: (i: number, j: number) => number) {
  142. const indices = new Int32Array(table._rowCount);
  143. for (let i = 0, _i = indices.length; i < _i; i++) indices[i] = i;
  144. sortArray(indices, (_, i, j) => cmp(i, j));
  145. let isIdentity = true;
  146. for (let i = 0, _i = indices.length; i < _i; i++) {
  147. if (indices[i] !== i) {
  148. isIdentity = false;
  149. break;
  150. }
  151. }
  152. if (isIdentity) return table;
  153. const ret = Object.create(null);
  154. ret._rowCount = table._rowCount;
  155. ret._columns = table._columns;
  156. ret._schema = table._schema;
  157. for (const c of table._columns) {
  158. ret[c] = Column.view((table as any)[c], indices, false);
  159. }
  160. return ret;
  161. }
  162. export function areEqual<T extends Table<Schema>>(a: T, b: T) {
  163. if (a._rowCount !== b._rowCount) return false;
  164. if (a._columns.length !== b._columns.length) return false;
  165. for (const c of a._columns) {
  166. if (!b[c]) return false;
  167. }
  168. for (const c of a._columns) {
  169. if (!Column.areEqual(a[c], b[c])) return false;
  170. }
  171. return true;
  172. }
  173. /** Allocate a new object with the given row values. */
  174. export function getRow<S extends Schema>(table: Table<S>, index: number) {
  175. const row: Row<S> = Object.create(null);
  176. const { _columns: cols } = table;
  177. for (let i = 0; i < cols.length; i++) {
  178. const c = cols[i] as keyof S;
  179. row[c] = table[c].value(index);
  180. }
  181. return row;
  182. }
  183. /** Pick the first row for which `test` evaluates to true */
  184. export function pickRow<S extends Schema>(table: Table<S>, test: (i: number) => boolean) {
  185. for (let i = 0, il = table._rowCount; i < il; ++i) {
  186. if (test(i)) return getRow(table, i)
  187. }
  188. }
  189. export function getRows<S extends Schema>(table: Table<S>) {
  190. const ret: Row<S>[] = [];
  191. const { _rowCount: c } = table;
  192. for (let i = 0; i < c; i++) {
  193. ret[i] = getRow(table, i);
  194. }
  195. return ret;
  196. }
  197. export function formatToString<S extends Schema>(table: Table<S>) {
  198. const sb = StringBuilder.create();
  199. const { _columns: cols, _rowCount } = table;
  200. let headerLength = 1;
  201. StringBuilder.write(sb, '|');
  202. for (let i = 0; i < cols.length; i++) {
  203. StringBuilder.write(sb, cols[i]);
  204. StringBuilder.write(sb, '|');
  205. headerLength += cols[i].length + 1;
  206. }
  207. StringBuilder.newline(sb);
  208. StringBuilder.write(sb, new Array(headerLength + 1).join('-'));
  209. StringBuilder.newline(sb);
  210. for (let r = 0; r < _rowCount; r++) {
  211. StringBuilder.write(sb, '|');
  212. for (let i = 0; i < cols.length; i++) {
  213. const c = table[cols[i]];
  214. if (c.valueKind(r) === Column.ValueKind.Present) {
  215. StringBuilder.write(sb, c.value(r));
  216. StringBuilder.write(sb, '|');
  217. } else {
  218. StringBuilder.write(sb, '.|');
  219. }
  220. }
  221. StringBuilder.newline(sb);
  222. }
  223. return StringBuilder.getString(sb);
  224. }
  225. }
  226. export default Table