column.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. /**
  2. * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  6. */
  7. import * as ColumnHelpers from './column-helpers'
  8. import { Tensor as Tensors } from 'mol-math/linear-algebra'
  9. interface Column<T> {
  10. readonly schema: Column.Schema,
  11. readonly '@array': ArrayLike<any> | undefined,
  12. readonly isDefined: boolean,
  13. readonly rowCount: number,
  14. value(row: number): T,
  15. valueKind(row: number): Column.ValueKind,
  16. toArray(params?: Column.ToArrayParams<T>): ArrayLike<T>,
  17. areValuesEqual(rowA: number, rowB: number): boolean
  18. }
  19. namespace Column {
  20. export type ArrayCtor<T> = { new(size: number): ArrayLike<T> }
  21. export type Schema<T = any> = Schema.Str | Schema.Int | Schema.Float | Schema.Coordinate | Schema.Aliased<T> | Schema.Tensor | Schema.List<number|string>
  22. export namespace Schema {
  23. // T also serves as a default value for undefined columns
  24. type Base<T extends string> = { valueType: T }
  25. export type Str = { '@type': 'str', T: string } & Base<'str'>
  26. export type Int = { '@type': 'int', T: number } & Base<'int'>
  27. export type Float = { '@type': 'float', T: number } & Base<'float'>
  28. export type Coordinate = { '@type': 'coord', T: number } & Base<'float'>
  29. export type Tensor = { '@type': 'tensor', T: Tensors, space: Tensors.Space } & Base<'tensor'>
  30. export type Aliased<T> = { '@type': 'aliased', T: T } & Base<'str' | 'int'>
  31. export type List<T extends number|string> = { '@type': 'list', T: T[] } & Base<'list'>
  32. export const str: Str = { '@type': 'str', T: '', valueType: 'str' };
  33. export const int: Int = { '@type': 'int', T: 0, valueType: 'int' };
  34. export const coord: Coordinate = { '@type': 'coord', T: 0, valueType: 'float' };
  35. export const float: Float = { '@type': 'float', T: 0, valueType: 'float' };
  36. export function Str(defaultValue = ''): Str { return { '@type': 'str', T: defaultValue, valueType: 'str' } };
  37. export function Int(defaultValue = 0): Int { return { '@type': 'int', T: defaultValue, valueType: 'int' } };
  38. export function Float(defaultValue = 0): Float { return { '@type': 'float', T: defaultValue, valueType: 'float' } };
  39. export function Tensor(space: Tensors.Space): Tensor { return { '@type': 'tensor', T: space.create(), space, valueType: 'tensor' }; }
  40. export function Vector(dim: number): Tensor { return Tensor(Tensors.Vector(dim)); }
  41. export function Matrix(rows: number, cols: number): Tensor { return Tensor(Tensors.ColumnMajorMatrix(rows, cols)); }
  42. export function Aliased<T>(t: Str | Int, defaultValue?: T): Aliased<T> {
  43. if (typeof defaultValue !== 'undefined') return { ...t, T: defaultValue } as any as Aliased<T>;
  44. return t as any as Aliased<T>;
  45. }
  46. export function List<T extends number|string>(defaultValue: T[] = []): List<T> {
  47. return { '@type': 'list', T: defaultValue, valueType: 'list' }
  48. }
  49. }
  50. export interface ToArrayParams<T> {
  51. array?: ArrayCtor<T>,
  52. start?: number,
  53. /** Last row (exclusive) */
  54. end?: number
  55. }
  56. export interface LambdaSpec<T extends Schema> {
  57. value: (row: number) => T['T'],
  58. rowCount: number,
  59. schema: T,
  60. valueKind?: (row: number) => ValueKind,
  61. }
  62. export interface ArraySpec<T extends Schema> {
  63. array: ArrayLike<T['T']>,
  64. schema: T,
  65. valueKind?: (row: number) => ValueKind
  66. }
  67. export interface MapSpec<S extends Schema, T extends Schema> {
  68. f: (v: S['T']) => T['T'],
  69. schema: T,
  70. valueKind?: (row: number) => ValueKind,
  71. }
  72. export function is(v: any): v is Column<any> {
  73. return !!v && !!(v as Column<any>).schema && !!(v as Column<any>).value;
  74. }
  75. export const enum ValueKind { Present = 0, NotPresent = 1, Unknown = 2 }
  76. export function Undefined<T extends Schema>(rowCount: number, schema: T): Column<T['T']> {
  77. return constColumn(schema['T'], rowCount, schema, ValueKind.NotPresent);
  78. }
  79. export function ofConst<T extends Schema>(v: T['T'], rowCount: number, type: T): Column<T['T']> {
  80. return constColumn(v, rowCount, type, ValueKind.Present);
  81. }
  82. export function ofLambda<T extends Schema>(spec: LambdaSpec<T>): Column<T['T']> {
  83. return lambdaColumn(spec);
  84. }
  85. export function ofArray<T extends Column.Schema>(spec: Column.ArraySpec<T>): Column<T['T']> {
  86. return arrayColumn(spec);
  87. }
  88. export function ofIntArray(array: ArrayLike<number>) {
  89. return arrayColumn({ array, schema: Schema.int });
  90. }
  91. export function ofFloatArray(array: ArrayLike<number>) {
  92. return arrayColumn({ array, schema: Schema.float });
  93. }
  94. export function ofStringArray(array: ArrayLike<string>) {
  95. return arrayColumn({ array, schema: Schema.str });
  96. }
  97. export function window<T>(column: Column<T>, start: number, end: number) {
  98. return windowColumn(column, start, end);
  99. }
  100. export function view<T>(column: Column<T>, indices: ArrayLike<number>, checkIndentity = true) {
  101. return columnView(column, indices, checkIndentity);
  102. }
  103. /** A map of the 1st occurence of each value. */
  104. export function createFirstIndexMap<T>(column: Column<T>) {
  105. return createFirstIndexMapOfColumn(column);
  106. }
  107. export function mapToArray<T, S>(column: Column<T>, f: (v: T) => S, ctor?: ArrayCtor<S>): ArrayLike<S> {
  108. return mapToArrayImpl<T, S>(column, f, ctor || Array);
  109. }
  110. export function areEqual<T>(a: Column<T>, b: Column<T>) {
  111. return areColumnsEqual(a, b);
  112. }
  113. export function indicesOf<T>(c: Column<T>, test: (e: T) => boolean) {
  114. return columnIndicesOf(c, test);
  115. }
  116. /** Makes the column backed by an array. Useful for columns that are accessed often. */
  117. export function asArrayColumn<T>(c: Column<T>, array?: ArrayCtor<T>): Column<T> {
  118. if (c['@array']) return c;
  119. if (!c.isDefined) return Undefined(c.rowCount, c.schema) as any as Column<T>;
  120. return arrayColumn({ array: c.toArray({ array }), schema: c.schema, valueKind: c.valueKind });
  121. }
  122. }
  123. export default Column;
  124. function createFirstIndexMapOfColumn<T>(c: Column<T>): Map<T, number> {
  125. const map = new Map<T, number>();
  126. for (let i = 0, _i = c.rowCount; i < _i; i++) {
  127. const v = c.value(i);
  128. if (!map.has(v)) return map.set(c.value(i), i);
  129. }
  130. return map;
  131. }
  132. function constColumn<T extends Column.Schema>(v: T['T'], rowCount: number, schema: T, valueKind: Column.ValueKind): Column<T['T']> {
  133. const value: Column<T['T']>['value'] = row => v;
  134. return {
  135. schema: schema,
  136. '@array': void 0,
  137. isDefined: valueKind === Column.ValueKind.Present,
  138. rowCount,
  139. value,
  140. valueKind: row => valueKind,
  141. toArray: params => {
  142. const { array } = ColumnHelpers.createArray(rowCount, params);
  143. for (let i = 0, _i = array.length; i < _i; i++) array[i] = v;
  144. return array;
  145. },
  146. areValuesEqual: (rowA, rowB) => true
  147. }
  148. }
  149. function lambdaColumn<T extends Column.Schema>({ value, valueKind, rowCount, schema }: Column.LambdaSpec<T>): Column<T['T']> {
  150. return {
  151. schema: schema,
  152. '@array': void 0,
  153. isDefined: true,
  154. rowCount,
  155. value,
  156. valueKind: valueKind ? valueKind : row => Column.ValueKind.Present,
  157. toArray: params => {
  158. const { array, start } = ColumnHelpers.createArray(rowCount, params);
  159. for (let i = 0, _i = array.length; i < _i; i++) array[i] = value(i + start);
  160. return array;
  161. },
  162. areValuesEqual: (rowA, rowB) => value(rowA) === value(rowB)
  163. }
  164. }
  165. function arrayColumn<T extends Column.Schema>({ array, schema, valueKind }: Column.ArraySpec<T>): Column<T['T']> {
  166. const rowCount = array.length;
  167. const value: Column<T['T']>['value'] = schema.valueType === 'str'
  168. ? row => { const v = array[row]; return typeof v === 'string' ? v : '' + v; }
  169. : row => array[row];
  170. const isTyped = ColumnHelpers.isTypedArray(array);
  171. return {
  172. schema: schema,
  173. '@array': array,
  174. isDefined: true,
  175. rowCount,
  176. value,
  177. valueKind: valueKind ? valueKind : row => Column.ValueKind.Present,
  178. toArray: schema.valueType === 'str'
  179. ? params => {
  180. const { start, end } = ColumnHelpers.getArrayBounds(rowCount, params);
  181. const ret = new (params && typeof params.array !== 'undefined' ? params.array : (array as any).constructor)(end - start) as any;
  182. for (let i = 0, _i = end - start; i < _i; i++) {
  183. const v = array[start + i];
  184. ret[i] = typeof v === 'string' ? v : '' + v;
  185. }
  186. return ret;
  187. }
  188. : isTyped
  189. ? params => ColumnHelpers.typedArrayWindow(array, params) as any as ReadonlyArray<T>
  190. : params => {
  191. const { start, end } = ColumnHelpers.getArrayBounds(rowCount, params);
  192. if (start === 0 && end === array.length) return array as ReadonlyArray<T['T']>;
  193. const ret = new (params && typeof params.array !== 'undefined' ? params.array : (array as any).constructor)(end - start) as any;
  194. for (let i = 0, _i = end - start; i < _i; i++) ret[i] = array[start + i];
  195. return ret;
  196. },
  197. areValuesEqual: (rowA, rowB) => array[rowA] === array[rowB]
  198. }
  199. }
  200. function windowColumn<T>(column: Column<T>, start: number, end: number) {
  201. if (!column.isDefined) return Column.Undefined(end - start, column.schema);
  202. if (!!column['@array'] && ColumnHelpers.isTypedArray(column['@array'])) return windowTyped(column, start, end);
  203. return windowFull(column, start, end);
  204. }
  205. function windowTyped<T>(c: Column<T>, start: number, end: number): Column<T> {
  206. const array = ColumnHelpers.typedArrayWindow(c['@array'], { start, end });
  207. return arrayColumn({ array, schema: c.schema, valueKind: c.valueKind }) as any;
  208. }
  209. function windowFull<T>(c: Column<T>, start: number, end: number): Column<T> {
  210. const v = c.value, vk = c.valueKind, ave = c.areValuesEqual;
  211. const value: Column<T>['value'] = start === 0 ? v : row => v(row + start);
  212. const rowCount = end - start;
  213. return {
  214. schema: c.schema,
  215. '@array': void 0,
  216. isDefined: c.isDefined,
  217. rowCount,
  218. value,
  219. valueKind: start === 0 ? vk : row => vk(row + start),
  220. toArray: params => {
  221. const { array } = ColumnHelpers.createArray(rowCount, params);
  222. for (let i = 0, _i = array.length; i < _i; i++) array[i] = v(i + start);
  223. return array;
  224. },
  225. areValuesEqual: start === 0 ? ave : (rowA, rowB) => ave(rowA + start, rowB + start)
  226. };
  227. }
  228. function isIdentity(map: ArrayLike<number>, rowCount: number) {
  229. if (map.length !== rowCount) return false;
  230. for (let i = 0, _i = map.length; i < _i; i++) {
  231. if (map[i] !== i) return false;
  232. }
  233. return true;
  234. }
  235. function columnView<T>(c: Column<T>, map: ArrayLike<number>, checkIdentity: boolean): Column<T> {
  236. if (!c.isDefined) return c;
  237. if (checkIdentity && isIdentity(map, c.rowCount)) return c;
  238. if (!!c['@array']) return arrayView(c, map);
  239. return viewFull(c, map);
  240. }
  241. function arrayView<T>(c: Column<T>, map: ArrayLike<number>): Column<T> {
  242. const array = c['@array']!;
  243. const ret = new (array as any).constructor(map.length);
  244. for (let i = 0, _i = map.length; i < _i; i++) ret[i] = array[map[i]];
  245. return arrayColumn({ array: ret, schema: c.schema, valueKind: c.valueKind });
  246. }
  247. function viewFull<T>(c: Column<T>, map: ArrayLike<number>): Column<T> {
  248. const v = c.value, vk = c.valueKind, ave = c.areValuesEqual;
  249. const value: Column<T>['value'] = row => v(map[row]);
  250. const rowCount = map.length;
  251. return {
  252. schema: c.schema,
  253. '@array': void 0,
  254. isDefined: c.isDefined,
  255. rowCount,
  256. value,
  257. valueKind: row => vk(map[row]),
  258. toArray: params => {
  259. const { array } = ColumnHelpers.createArray(rowCount, params);
  260. for (let i = 0, _i = array.length; i < _i; i++) array[i] = v(map[i]);
  261. return array;
  262. },
  263. areValuesEqual: (rowA, rowB) => ave(map[rowA], map[rowB])
  264. };
  265. }
  266. function mapToArrayImpl<T, S>(c: Column<T>, f: (v: T) => S, ctor: Column.ArrayCtor<S>): ArrayLike<S> {
  267. const ret = new ctor(c.rowCount) as any;
  268. for (let i = 0, _i = c.rowCount; i < _i; i++) ret[i] = f(c.value(i));
  269. return ret;
  270. }
  271. function areColumnsEqual(a: Column<any>, b: Column<any>) {
  272. if (a === b) return true;
  273. if (a.rowCount !== b.rowCount || a.isDefined !== b.isDefined || a.schema.valueType !== b.schema.valueType) return false;
  274. if (!!a['@array'] && !!b['@array']) return areArraysEqual(a, b);
  275. return areValuesEqual(a, b);
  276. }
  277. function areArraysEqual(a: Column<any>, b: Column<any>) {
  278. const xs = a['@array']!, ys = b['@array']!;
  279. for (let i = 0, _i = a.rowCount; i < _i; i++) {
  280. if (xs[i] !== ys[i]) return false;
  281. }
  282. return true;
  283. }
  284. function areValuesEqual(a: Column<any>, b: Column<any>) {
  285. const va = a.value, vb = b.value;
  286. for (let i = 0, _i = a.rowCount; i < _i; i++) {
  287. if (va(i) !== vb(i)) return false;
  288. }
  289. return true;
  290. }
  291. function columnIndicesOf<T>(c: Column<T>, test: (e: T) => boolean) {
  292. const ret = [], v = c.value;
  293. for (let i = 0, _i = c.rowCount; i < _i; i++) {
  294. if (test(v(i))) ret[ret.length] = i;
  295. }
  296. return ret;
  297. }