mirror of https://github.com/grafana/grafana
grafana/data: Reorganise code (#19136)
* Organise data frame and vectors code
* Organise transformations
* Move dataframe utils to dataframe dir
* Organise datetime utils
* Organise text utils
* Organise logs utils
* Revert "Organise logs utils"
This reverts commit c24115c755
.
* registry -> Registry
* Transformations reorg
pull/19192/head
parent
330cd597ec
commit
5c0f424d1b
@ -0,0 +1,22 @@ |
||||
import { MutableDataFrame } from './MutableDataFrame'; |
||||
import { CircularVector } from '../vector/CircularVector'; |
||||
|
||||
interface CircularOptions { |
||||
append?: 'head' | 'tail'; |
||||
capacity?: number; |
||||
} |
||||
|
||||
/** |
||||
* This dataframe can have values constantly added, and will never |
||||
* exceed the given capacity |
||||
*/ |
||||
export class CircularDataFrame<T = any> extends MutableDataFrame<T> { |
||||
constructor(options: CircularOptions) { |
||||
super(undefined, (buffer?: any[]) => { |
||||
return new CircularVector({ |
||||
...options, |
||||
buffer, |
||||
}); |
||||
}); |
||||
} |
||||
} |
@ -1,7 +1,7 @@ |
||||
import { FieldType, DataFrameDTO } from '../types/index'; |
||||
import { MutableDataFrame } from './dataFrameHelper'; |
||||
import { DataFrameView } from './dataFrameView'; |
||||
import { DateTime } from './moment_wrapper'; |
||||
import { FieldType, DataFrameDTO } from '../types/dataFrame'; |
||||
import { DateTime } from '../datetime/moment_wrapper'; |
||||
import { MutableDataFrame } from './MutableDataFrame'; |
||||
import { DataFrameView } from './DataFrameView'; |
||||
|
||||
interface MySpecialObject { |
||||
time: DateTime; |
@ -1,4 +1,5 @@ |
||||
import { DataFrame, Vector } from '../types/index'; |
||||
import { Vector } from '../types/vector'; |
||||
import { DataFrame } from '../types/dataFrame'; |
||||
|
||||
/** |
||||
* This abstraction will present the contents of a DataFrame as if |
@ -0,0 +1,78 @@ |
||||
import { Field, DataFrame, FieldType, guessFieldTypeForField } from '../index'; |
||||
|
||||
interface FieldWithIndex extends Field { |
||||
index: number; |
||||
} |
||||
|
||||
export class FieldCache { |
||||
fields: FieldWithIndex[] = []; |
||||
|
||||
private fieldByName: { [key: string]: FieldWithIndex } = {}; |
||||
private fieldByType: { [key: string]: FieldWithIndex[] } = {}; |
||||
|
||||
constructor(data: DataFrame) { |
||||
this.fields = data.fields.map((field, idx) => ({ |
||||
...field, |
||||
index: idx, |
||||
})); |
||||
|
||||
for (let i = 0; i < data.fields.length; i++) { |
||||
const field = data.fields[i]; |
||||
// Make sure it has a type
|
||||
if (field.type === FieldType.other) { |
||||
const t = guessFieldTypeForField(field); |
||||
if (t) { |
||||
field.type = t; |
||||
} |
||||
} |
||||
if (!this.fieldByType[field.type]) { |
||||
this.fieldByType[field.type] = []; |
||||
} |
||||
this.fieldByType[field.type].push({ |
||||
...field, |
||||
index: i, |
||||
}); |
||||
|
||||
if (this.fieldByName[field.name]) { |
||||
console.warn('Duplicate field names in DataFrame: ', field.name); |
||||
} else { |
||||
this.fieldByName[field.name] = { ...field, index: i }; |
||||
} |
||||
} |
||||
} |
||||
|
||||
getFields(type?: FieldType): FieldWithIndex[] { |
||||
if (!type) { |
||||
return [...this.fields]; // All fields
|
||||
} |
||||
const fields = this.fieldByType[type]; |
||||
if (fields) { |
||||
return [...fields]; |
||||
} |
||||
return []; |
||||
} |
||||
|
||||
hasFieldOfType(type: FieldType): boolean { |
||||
const types = this.fieldByType[type]; |
||||
return types && types.length > 0; |
||||
} |
||||
|
||||
getFirstFieldOfType(type: FieldType): FieldWithIndex | undefined { |
||||
const arr = this.fieldByType[type]; |
||||
if (arr && arr.length > 0) { |
||||
return arr[0]; |
||||
} |
||||
return undefined; |
||||
} |
||||
|
||||
hasFieldNamed(name: string): boolean { |
||||
return !!this.fieldByName[name]; |
||||
} |
||||
|
||||
/** |
||||
* Returns the first field with the given name. |
||||
*/ |
||||
getFieldByName(name: string): FieldWithIndex | undefined { |
||||
return this.fieldByName[name]; |
||||
} |
||||
} |
@ -0,0 +1,66 @@ |
||||
import { DataFrameDTO, FieldType } from '../types/dataFrame'; |
||||
import { MutableDataFrame } from './MutableDataFrame'; |
||||
|
||||
describe('Reversing DataFrame', () => { |
||||
describe('when called with a DataFrame', () => { |
||||
it('then it should reverse the order of values in all fields', () => { |
||||
const frame: DataFrameDTO = { |
||||
fields: [ |
||||
{ name: 'time', type: FieldType.time, values: [100, 200, 300] }, |
||||
{ name: 'name', type: FieldType.string, values: ['a', 'b', 'c'] }, |
||||
{ name: 'value', type: FieldType.number, values: [1, 2, 3] }, |
||||
], |
||||
}; |
||||
|
||||
const helper = new MutableDataFrame(frame); |
||||
|
||||
expect(helper.values.time.toArray()).toEqual([100, 200, 300]); |
||||
expect(helper.values.name.toArray()).toEqual(['a', 'b', 'c']); |
||||
expect(helper.values.value.toArray()).toEqual([1, 2, 3]); |
||||
|
||||
helper.reverse(); |
||||
|
||||
expect(helper.values.time.toArray()).toEqual([300, 200, 100]); |
||||
expect(helper.values.name.toArray()).toEqual(['c', 'b', 'a']); |
||||
expect(helper.values.value.toArray()).toEqual([3, 2, 1]); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('Apending DataFrame', () => { |
||||
it('Should append values', () => { |
||||
const dto: DataFrameDTO = { |
||||
fields: [ |
||||
{ name: 'time', type: FieldType.time, values: [100] }, |
||||
{ name: 'name', type: FieldType.string, values: ['a', 'b'] }, |
||||
{ name: 'value', type: FieldType.number, values: [1, 2, 3] }, |
||||
], |
||||
}; |
||||
|
||||
const frame = new MutableDataFrame(dto); |
||||
expect(frame.values.time.toArray()).toEqual([100, null, null]); |
||||
|
||||
// Set a value on the second row
|
||||
frame.set(1, { time: 200, name: 'BB', value: 20 }); |
||||
expect(frame.toArray()).toEqual([ |
||||
{ time: 100, name: 'a', value: 1 }, // 1
|
||||
{ time: 200, name: 'BB', value: 20 }, // 2
|
||||
{ time: null, name: null, value: 3 }, // 3
|
||||
]); |
||||
|
||||
// Set a value on the second row
|
||||
frame.add({ value2: 'XXX' }, true); |
||||
expect(frame.toArray()).toEqual([ |
||||
{ time: 100, name: 'a', value: 1, value2: null }, // 1
|
||||
{ time: 200, name: 'BB', value: 20, value2: null }, // 2
|
||||
{ time: null, name: null, value: 3, value2: null }, // 3
|
||||
{ time: null, name: null, value: null, value2: 'XXX' }, // 4
|
||||
]); |
||||
|
||||
// Make sure length survives a spread operator
|
||||
const keys = Object.keys(frame); |
||||
const copy = { ...frame } as any; |
||||
expect(keys).toContain('length'); |
||||
expect(copy.length).toEqual(frame.length); |
||||
}); |
||||
}); |
@ -0,0 +1,5 @@ |
||||
export * from './DataFrameView'; |
||||
export * from './FieldCache'; |
||||
export * from './CircularDataFrame'; |
||||
export * from './MutableDataFrame'; |
||||
export * from './processDataFrame'; |
@ -1,7 +1,7 @@ |
||||
import includes from 'lodash/includes'; |
||||
import isDate from 'lodash/isDate'; |
||||
import { DateTime, dateTime, dateTimeForTimeZone, ISO_8601, isDateTime, DurationUnit } from './moment_wrapper'; |
||||
import { TimeZone } from '../types'; |
||||
import { TimeZone } from '../types/index'; |
||||
|
||||
const units: DurationUnit[] = ['y', 'M', 'w', 'd', 'h', 'm', 's']; |
||||
|
@ -0,0 +1,5 @@ |
||||
// Names are too general to export globally
|
||||
import * as dateMath from './datemath'; |
||||
import * as rangeUtil from './rangeutil'; |
||||
export * from './moment_wrapper'; |
||||
export { dateMath, rangeUtil }; |
@ -1,2 +1,7 @@ |
||||
export * from './utils/index'; |
||||
export * from './types/index'; |
||||
export * from './utils'; |
||||
export * from './types'; |
||||
export * from './vector'; |
||||
export * from './dataframe'; |
||||
export * from './transformations'; |
||||
export * from './datetime'; |
||||
export * from './text'; |
||||
|
@ -0,0 +1,3 @@ |
||||
export * from './string'; |
||||
export * from './markdown'; |
||||
export * from './text'; |
@ -1,8 +1,8 @@ |
||||
// Libraries
|
||||
import isNumber from 'lodash/isNumber'; |
||||
|
||||
import { NullValueMode, Field } from '../types'; |
||||
import { Registry, RegistryItem } from './registry'; |
||||
import { NullValueMode, Field } from '../types/index'; |
||||
import { Registry, RegistryItem } from '../utils/Registry'; |
||||
|
||||
export enum ReducerID { |
||||
sum = 'sum', |
@ -0,0 +1,7 @@ |
||||
export * from './matchers/ids'; |
||||
export * from './transformers/ids'; |
||||
export * from './matchers'; |
||||
export * from './transformers'; |
||||
export * from './fieldReducer'; |
||||
export { FilterFieldsByNameTransformerOptions } from './transformers/filterByName'; |
||||
export { ReduceTransformerOptions } from './transformers/reduce'; |
@ -1,27 +1,16 @@ |
||||
import { Field, DataFrame } from '../../types/dataFrame'; |
||||
import { Registry, RegistryItemWithOptions } from '../registry'; |
||||
|
||||
export type FieldMatcher = (field: Field) => boolean; |
||||
export type FrameMatcher = (frame: DataFrame) => boolean; |
||||
|
||||
export interface FieldMatcherInfo<TOptions = any> extends RegistryItemWithOptions<TOptions> { |
||||
get: (options: TOptions) => FieldMatcher; |
||||
} |
||||
|
||||
export interface FrameMatcherInfo<TOptions = any> extends RegistryItemWithOptions<TOptions> { |
||||
get: (options: TOptions) => FrameMatcher; |
||||
} |
||||
|
||||
export interface MatcherConfig<TOptions = any> { |
||||
id: string; |
||||
options?: TOptions; |
||||
} |
||||
|
||||
// Load the Buildtin matchers
|
||||
import { getFieldPredicateMatchers, getFramePredicateMatchers } from './predicates'; |
||||
import { getFieldNameMatchers, getFrameNameMatchers } from './nameMatcher'; |
||||
import { getFieldTypeMatchers } from './fieldTypeMatcher'; |
||||
import { getRefIdMatchers } from './refIdMatcher'; |
||||
import { getFieldPredicateMatchers, getFramePredicateMatchers } from './matchers/predicates'; |
||||
import { getFieldNameMatchers, getFrameNameMatchers } from './matchers/nameMatcher'; |
||||
import { getFieldTypeMatchers } from './matchers/fieldTypeMatcher'; |
||||
import { getRefIdMatchers } from './matchers/refIdMatcher'; |
||||
import { |
||||
FieldMatcherInfo, |
||||
MatcherConfig, |
||||
FrameMatcherInfo, |
||||
FieldMatcher, |
||||
FrameMatcher, |
||||
} from '../types/transformations'; |
||||
import { Registry } from '../utils/Registry'; |
||||
|
||||
export const fieldMatchers = new Registry<FieldMatcherInfo>(() => { |
||||
return [ |
@ -1,7 +1,7 @@ |
||||
import { FieldType } from '../../types/dataFrame'; |
||||
import { fieldMatchers } from './matchers'; |
||||
import { fieldMatchers } from '../matchers'; |
||||
import { FieldMatcherID } from './ids'; |
||||
import { toDataFrame } from '../processDataFrame'; |
||||
import { toDataFrame } from '../../dataframe/processDataFrame'; |
||||
|
||||
export const simpleSeriesWithTypes = toDataFrame({ |
||||
fields: [ |
@ -1,6 +1,6 @@ |
||||
import { Field, FieldType } from '../../types/dataFrame'; |
||||
import { FieldMatcherInfo } from './matchers'; |
||||
import { FieldMatcherID } from './ids'; |
||||
import { FieldMatcherInfo } from '../../types/transformations'; |
||||
|
||||
// General Field matcher
|
||||
const fieldTypeMacher: FieldMatcherInfo<FieldType> = { |
@ -1,4 +1,4 @@ |
||||
import { fieldMatchers } from './matchers'; |
||||
import { fieldMatchers } from '../matchers'; |
||||
import { FieldMatcherID } from './ids'; |
||||
|
||||
describe('Matchers', () => { |
@ -1,6 +1,6 @@ |
||||
import { getFieldMatcher } from './matchers'; |
||||
import { getFieldMatcher } from '../matchers'; |
||||
import { FieldMatcherID } from './ids'; |
||||
import { toDataFrame } from '../processDataFrame'; |
||||
import { toDataFrame } from '../../dataframe/processDataFrame'; |
||||
|
||||
describe('Field Name Matcher', () => { |
||||
it('Match all with wildcard regex', () => { |
@ -1,7 +1,7 @@ |
||||
import { Field, DataFrame } from '../../types/dataFrame'; |
||||
import { FieldMatcherInfo, FrameMatcherInfo } from './matchers'; |
||||
import { FieldMatcherID, FrameMatcherID } from './ids'; |
||||
import { stringToJsRegex } from '../string'; |
||||
import { FieldMatcherInfo, FrameMatcherInfo } from '../../types/transformations'; |
||||
import { stringToJsRegex } from '../../text/string'; |
||||
|
||||
// General Field matcher
|
||||
const fieldNameMacher: FieldMatcherInfo<string> = { |
@ -1,7 +1,8 @@ |
||||
import { FieldType } from '../../types/dataFrame'; |
||||
import { MatcherConfig, fieldMatchers } from './matchers'; |
||||
import { fieldMatchers } from '../matchers'; |
||||
import { simpleSeriesWithTypes } from './fieldTypeMatcher.test'; |
||||
import { FieldMatcherID, MatcherID } from './ids'; |
||||
import { MatcherConfig } from '../../types/transformations'; |
||||
|
||||
const matchesNumberConfig: MatcherConfig = { |
||||
id: FieldMatcherID.byType, |
@ -1,14 +1,7 @@ |
||||
import { Field, DataFrame } from '../../types/dataFrame'; |
||||
import { MatcherID } from './ids'; |
||||
import { |
||||
FrameMatcherInfo, |
||||
FieldMatcherInfo, |
||||
MatcherConfig, |
||||
getFieldMatcher, |
||||
fieldMatchers, |
||||
getFrameMatchers, |
||||
frameMatchers, |
||||
} from './matchers'; |
||||
import { getFieldMatcher, fieldMatchers, getFrameMatchers, frameMatchers } from '../matchers'; |
||||
import { FieldMatcherInfo, MatcherConfig, FrameMatcherInfo } from '../../types/transformations'; |
||||
|
||||
const anyFieldMatcher: FieldMatcherInfo<MatcherConfig[]> = { |
||||
id: MatcherID.anyMatch, |
@ -1,6 +1,6 @@ |
||||
import { DataFrame } from '../../types/dataFrame'; |
||||
import { FrameMatcherInfo } from './matchers'; |
||||
import { FrameMatcherID } from './ids'; |
||||
import { FrameMatcherInfo } from '../../types/transformations'; |
||||
|
||||
// General Field matcher
|
||||
const refIdMacher: FrameMatcherInfo<string> = { |
@ -1,7 +1,7 @@ |
||||
import { DataTransformerInfo } from './transformers'; |
||||
import { DataFrame } from '../../types/dataFrame'; |
||||
import { DataTransformerID } from './ids'; |
||||
import { MutableDataFrame } from '../dataFrameHelper'; |
||||
import { MutableDataFrame } from '../../dataframe/MutableDataFrame'; |
||||
import { DataTransformerInfo } from '../../types/transformations'; |
||||
|
||||
export interface AppendOptions {} |
||||
|
@ -1,8 +1,8 @@ |
||||
import { FieldType } from '../../types/dataFrame'; |
||||
import { FieldMatcherID } from '../matchers/ids'; |
||||
import { transformDataFrame } from './transformers'; |
||||
import { DataTransformerID } from './ids'; |
||||
import { toDataFrame } from '../processDataFrame'; |
||||
import { toDataFrame } from '../../dataframe/processDataFrame'; |
||||
import { FieldMatcherID } from '../matchers/ids'; |
||||
import { transformDataFrame } from '../transformers'; |
||||
|
||||
export const simpleSeriesWithTypes = toDataFrame({ |
||||
fields: [ |
@ -1,9 +1,9 @@ |
||||
import { DataTransformerInfo } from './transformers'; |
||||
import { noopTransformer } from './noop'; |
||||
import { DataFrame, Field } from '../../types/dataFrame'; |
||||
import { FieldMatcherID } from '../matchers/ids'; |
||||
import { DataTransformerID } from './ids'; |
||||
import { MatcherConfig, getFieldMatcher, getFrameMatchers } from '../matchers/matchers'; |
||||
import { DataTransformerInfo, MatcherConfig } from '../../types/transformations'; |
||||
import { FieldMatcherID } from '../matchers/ids'; |
||||
import { getFieldMatcher, getFrameMatchers } from '../matchers'; |
||||
|
||||
export interface FilterOptions { |
||||
include?: MatcherConfig; |
@ -1,6 +1,7 @@ |
||||
import { toDataFrame, transformDataFrame } from '../index'; |
||||
import { FieldType } from '../../index'; |
||||
import { DataTransformerID } from './ids'; |
||||
import { transformDataFrame } from '../transformers'; |
||||
import { toDataFrame } from '../../dataframe/processDataFrame'; |
||||
import { FieldType } from '../../types/dataFrame'; |
||||
|
||||
export const seriesWithNamesToMatch = toDataFrame({ |
||||
fields: [ |
@ -1,7 +1,7 @@ |
||||
import { DataTransformerInfo } from './transformers'; |
||||
import { FieldMatcherID } from '../matchers/ids'; |
||||
import { DataTransformerID } from './ids'; |
||||
import { filterFieldsTransformer, FilterOptions } from './filter'; |
||||
import { DataTransformerInfo } from '../../types/transformations'; |
||||
import { FieldMatcherID } from '../matchers/ids'; |
||||
|
||||
export interface FilterFieldsByNameTransformerOptions { |
||||
include?: string; |
@ -1,6 +1,6 @@ |
||||
import { DataTransformerInfo } from './transformers'; |
||||
import { DataTransformerID } from './ids'; |
||||
import { DataFrame } from '../../types/dataFrame'; |
||||
import { DataTransformerInfo } from '../../types/transformations'; |
||||
|
||||
export interface NoopTransformerOptions { |
||||
include?: string; |
@ -1,7 +1,7 @@ |
||||
import { transformDataFrame } from './transformers'; |
||||
import { ReducerID } from '../fieldReducer'; |
||||
import { DataTransformerID } from './ids'; |
||||
import { toDataFrame, toDataFrameDTO } from '../processDataFrame'; |
||||
import { toDataFrame, toDataFrameDTO } from '../../dataframe/processDataFrame'; |
||||
import { transformDataFrame } from '../transformers'; |
||||
|
||||
const seriesWithValues = toDataFrame({ |
||||
fields: [ |
@ -1,12 +1,12 @@ |
||||
import { DataTransformerInfo } from './transformers'; |
||||
import { DataFrame, FieldType, Field } from '../../types/dataFrame'; |
||||
import { MatcherConfig, getFieldMatcher } from '../matchers/matchers'; |
||||
import { alwaysFieldMatcher } from '../matchers/predicates'; |
||||
import { DataTransformerID } from './ids'; |
||||
import { MatcherConfig, DataTransformerInfo } from '../../types/transformations'; |
||||
import { ReducerID, fieldReducers, reduceField } from '../fieldReducer'; |
||||
import { alwaysFieldMatcher } from '../matchers/predicates'; |
||||
import { DataFrame, Field, FieldType } from '../../types/dataFrame'; |
||||
import { ArrayVector } from '../../vector/ArrayVector'; |
||||
import { KeyValue } from '../../types/data'; |
||||
import { ArrayVector } from '../vector'; |
||||
import { guessFieldTypeForField } from '../processDataFrame'; |
||||
import { guessFieldTypeForField } from '../../dataframe/processDataFrame'; |
||||
import { getFieldMatcher } from '../matchers'; |
||||
|
||||
export interface ReduceTransformerOptions { |
||||
reducers: ReducerID[]; |
@ -0,0 +1,32 @@ |
||||
import { DataFrame, Field } from './dataFrame'; |
||||
import { RegistryItemWithOptions } from '../utils/Registry'; |
||||
|
||||
/** |
||||
* Immutable data transformation |
||||
*/ |
||||
export type DataTransformer = (data: DataFrame[]) => DataFrame[]; |
||||
|
||||
export interface DataTransformerInfo<TOptions = any> extends RegistryItemWithOptions { |
||||
transformer: (options: TOptions) => DataTransformer; |
||||
} |
||||
|
||||
export interface DataTransformerConfig<TOptions = any> { |
||||
id: string; |
||||
options: TOptions; |
||||
} |
||||
|
||||
export type FieldMatcher = (field: Field) => boolean; |
||||
export type FrameMatcher = (frame: DataFrame) => boolean; |
||||
|
||||
export interface FieldMatcherInfo<TOptions = any> extends RegistryItemWithOptions<TOptions> { |
||||
get: (options: TOptions) => FieldMatcher; |
||||
} |
||||
|
||||
export interface FrameMatcherInfo<TOptions = any> extends RegistryItemWithOptions<TOptions> { |
||||
get: (options: TOptions) => FrameMatcher; |
||||
} |
||||
|
||||
export interface MatcherConfig<TOptions = any> { |
||||
id: string; |
||||
options?: TOptions; |
||||
} |
@ -0,0 +1,40 @@ |
||||
export interface Vector<T = any> { |
||||
length: number; |
||||
|
||||
/** |
||||
* Access the value by index (Like an array) |
||||
*/ |
||||
get(index: number): T; |
||||
|
||||
/** |
||||
* Get the resutls as an array. |
||||
*/ |
||||
toArray(): T[]; |
||||
|
||||
/** |
||||
* Return the values as a simple array for json serialization |
||||
*/ |
||||
toJSON(): any; // same results as toArray()
|
||||
} |
||||
|
||||
/** |
||||
* Apache arrow vectors are Read/Write |
||||
*/ |
||||
export interface ReadWriteVector<T = any> extends Vector<T> { |
||||
set: (index: number, value: T) => void; |
||||
} |
||||
|
||||
/** |
||||
* Vector with standard manipulation functions |
||||
*/ |
||||
export interface MutableVector<T = any> extends ReadWriteVector<T> { |
||||
/** |
||||
* Adds the value to the vector |
||||
*/ |
||||
add: (value: T) => void; |
||||
|
||||
/** |
||||
* modifies the vector so it is now the oposite order |
||||
*/ |
||||
reverse: () => void; |
||||
} |
@ -0,0 +1,28 @@ |
||||
import { Field, FieldType } from '../types/dataFrame'; |
||||
import { guessFieldTypeFromValue } from '../dataframe/processDataFrame'; |
||||
|
||||
export function makeFieldParser(value: any, field: Field): (value: string) => any { |
||||
if (!field.type) { |
||||
if (field.name === 'time' || field.name === 'Time') { |
||||
field.type = FieldType.time; |
||||
} else { |
||||
field.type = guessFieldTypeFromValue(value); |
||||
} |
||||
} |
||||
|
||||
if (field.type === FieldType.number) { |
||||
return (value: string) => { |
||||
return parseFloat(value); |
||||
}; |
||||
} |
||||
|
||||
// Will convert anything that starts with "T" to true
|
||||
if (field.type === FieldType.boolean) { |
||||
return (value: string) => { |
||||
return !(value[0] === 'F' || value[0] === 'f' || value[0] === '0'); |
||||
}; |
||||
} |
||||
|
||||
// Just pass the string back
|
||||
return (value: string) => value; |
||||
} |
@ -1,338 +0,0 @@ |
||||
import { Vector } from '../types/dataFrame'; |
||||
|
||||
export function vectorToArray<T>(v: Vector<T>): T[] { |
||||
const arr: T[] = []; |
||||
for (let i = 0; i < v.length; i++) { |
||||
arr[i] = v.get(i); |
||||
} |
||||
return arr; |
||||
} |
||||
|
||||
/** |
||||
* Apache arrow vectors are Read/Write |
||||
*/ |
||||
export interface ReadWriteVector<T = any> extends Vector<T> { |
||||
set: (index: number, value: T) => void; |
||||
} |
||||
|
||||
/** |
||||
* Vector with standard manipulation functions |
||||
*/ |
||||
export interface MutableVector<T = any> extends ReadWriteVector<T> { |
||||
/** |
||||
* Adds the value to the vector |
||||
*/ |
||||
add: (value: T) => void; |
||||
|
||||
/** |
||||
* modifies the vector so it is now the oposite order |
||||
*/ |
||||
reverse: () => void; |
||||
} |
||||
|
||||
export class ArrayVector<T = any> implements MutableVector<T> { |
||||
buffer: T[]; |
||||
|
||||
constructor(buffer?: T[]) { |
||||
this.buffer = buffer ? buffer : []; |
||||
} |
||||
|
||||
get length() { |
||||
return this.buffer.length; |
||||
} |
||||
|
||||
add(value: T) { |
||||
this.buffer.push(value); |
||||
} |
||||
|
||||
get(index: number): T { |
||||
return this.buffer[index]; |
||||
} |
||||
|
||||
set(index: number, value: T) { |
||||
this.buffer[index] = value; |
||||
} |
||||
|
||||
reverse() { |
||||
this.buffer.reverse(); |
||||
} |
||||
|
||||
toArray(): T[] { |
||||
return this.buffer; |
||||
} |
||||
|
||||
toJSON(): T[] { |
||||
return this.buffer; |
||||
} |
||||
} |
||||
|
||||
export class ConstantVector<T = any> implements Vector<T> { |
||||
constructor(private value: T, private len: number) {} |
||||
|
||||
get length() { |
||||
return this.len; |
||||
} |
||||
|
||||
get(index: number): T { |
||||
return this.value; |
||||
} |
||||
|
||||
toArray(): T[] { |
||||
const arr = new Array<T>(this.length); |
||||
return arr.fill(this.value); |
||||
} |
||||
|
||||
toJSON(): T[] { |
||||
return this.toArray(); |
||||
} |
||||
} |
||||
|
||||
export class ScaledVector implements Vector<number> { |
||||
constructor(private source: Vector<number>, private scale: number) {} |
||||
|
||||
get length(): number { |
||||
return this.source.length; |
||||
} |
||||
|
||||
get(index: number): number { |
||||
return this.source.get(index) * this.scale; |
||||
} |
||||
|
||||
toArray(): number[] { |
||||
return vectorToArray(this); |
||||
} |
||||
|
||||
toJSON(): number[] { |
||||
return vectorToArray(this); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Values are returned in the order defined by the input parameter |
||||
*/ |
||||
export class SortedVector<T = any> implements Vector<T> { |
||||
constructor(private source: Vector<T>, private order: number[]) {} |
||||
|
||||
get length(): number { |
||||
return this.source.length; |
||||
} |
||||
|
||||
get(index: number): T { |
||||
return this.source.get(this.order[index]); |
||||
} |
||||
|
||||
toArray(): T[] { |
||||
return vectorToArray(this); |
||||
} |
||||
|
||||
toJSON(): T[] { |
||||
return vectorToArray(this); |
||||
} |
||||
} |
||||
|
||||
interface CircularOptions<T> { |
||||
buffer?: T[]; |
||||
append?: 'head' | 'tail'; |
||||
capacity?: number; |
||||
} |
||||
|
||||
/** |
||||
* Circular vector uses a single buffer to capture a stream of values |
||||
* overwriting the oldest value on add. |
||||
* |
||||
* This supports addting to the 'head' or 'tail' and will grow the buffer |
||||
* to match a configured capacity. |
||||
*/ |
||||
export class CircularVector<T = any> implements MutableVector<T> { |
||||
private buffer: T[]; |
||||
private index: number; |
||||
private capacity: number; |
||||
private tail: boolean; |
||||
|
||||
constructor(options: CircularOptions<T>) { |
||||
this.buffer = options.buffer || []; |
||||
this.capacity = this.buffer.length; |
||||
this.tail = 'head' !== options.append; |
||||
this.index = 0; |
||||
|
||||
this.add = this.getAddFunction(); |
||||
if (options.capacity) { |
||||
this.setCapacity(options.capacity); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* This gets the appropriate add function depending on the buffer state: |
||||
* * head vs tail |
||||
* * growing buffer vs overwriting values |
||||
*/ |
||||
private getAddFunction() { |
||||
// When we are not at capacity, it should actually modify the buffer
|
||||
if (this.capacity > this.buffer.length) { |
||||
if (this.tail) { |
||||
return (value: T) => { |
||||
this.buffer.push(value); |
||||
if (this.buffer.length >= this.capacity) { |
||||
this.add = this.getAddFunction(); |
||||
} |
||||
}; |
||||
} else { |
||||
return (value: T) => { |
||||
this.buffer.unshift(value); |
||||
if (this.buffer.length >= this.capacity) { |
||||
this.add = this.getAddFunction(); |
||||
} |
||||
}; |
||||
} |
||||
} |
||||
|
||||
if (this.tail) { |
||||
return (value: T) => { |
||||
this.buffer[this.index] = value; |
||||
this.index = (this.index + 1) % this.buffer.length; |
||||
}; |
||||
} |
||||
|
||||
// Append values to the head
|
||||
return (value: T) => { |
||||
let idx = this.index - 1; |
||||
if (idx < 0) { |
||||
idx = this.buffer.length - 1; |
||||
} |
||||
this.buffer[idx] = value; |
||||
this.index = idx; |
||||
}; |
||||
} |
||||
|
||||
setCapacity(v: number) { |
||||
if (this.capacity === v) { |
||||
return; |
||||
} |
||||
// Make a copy so it is in order and new additions can be at the head or tail
|
||||
const copy = this.toArray(); |
||||
if (v > this.length) { |
||||
this.buffer = copy; |
||||
} else if (v < this.capacity) { |
||||
// Shrink the buffer
|
||||
const delta = this.length - v; |
||||
if (this.tail) { |
||||
this.buffer = copy.slice(delta, copy.length); // Keep last items
|
||||
} else { |
||||
this.buffer = copy.slice(0, copy.length - delta); // Keep first items
|
||||
} |
||||
} |
||||
this.capacity = v; |
||||
this.index = 0; |
||||
this.add = this.getAddFunction(); |
||||
} |
||||
|
||||
setAppendMode(mode: 'head' | 'tail') { |
||||
const tail = 'head' !== mode; |
||||
if (tail !== this.tail) { |
||||
this.buffer = this.toArray().reverse(); |
||||
this.index = 0; |
||||
this.tail = tail; |
||||
this.add = this.getAddFunction(); |
||||
} |
||||
} |
||||
|
||||
reverse() { |
||||
this.buffer.reverse(); |
||||
} |
||||
|
||||
/** |
||||
* Add the value to the buffer |
||||
*/ |
||||
add: (value: T) => void; |
||||
|
||||
get(index: number) { |
||||
return this.buffer[(index + this.index) % this.buffer.length]; |
||||
} |
||||
|
||||
set(index: number, value: T) { |
||||
this.buffer[(index + this.index) % this.buffer.length] = value; |
||||
} |
||||
|
||||
get length() { |
||||
return this.buffer.length; |
||||
} |
||||
|
||||
toArray(): T[] { |
||||
return vectorToArray(this); |
||||
} |
||||
|
||||
toJSON(): T[] { |
||||
return vectorToArray(this); |
||||
} |
||||
} |
||||
|
||||
interface AppendedVectorInfo<T> { |
||||
start: number; |
||||
end: number; |
||||
values: Vector<T>; |
||||
} |
||||
|
||||
/** |
||||
* This may be more trouble than it is worth. This trades some computation time for |
||||
* RAM -- rather than allocate a new array the size of all previous arrays, this just |
||||
* points the correct index to their original array values |
||||
*/ |
||||
export class AppendedVectors<T = any> implements Vector<T> { |
||||
length = 0; |
||||
source: Array<AppendedVectorInfo<T>> = new Array<AppendedVectorInfo<T>>(); |
||||
|
||||
constructor(startAt = 0) { |
||||
this.length = startAt; |
||||
} |
||||
|
||||
/** |
||||
* Make the vector look like it is this long |
||||
*/ |
||||
setLength(length: number) { |
||||
if (length > this.length) { |
||||
// make the vector longer (filling with undefined)
|
||||
this.length = length; |
||||
} else if (length < this.length) { |
||||
// make the array shorter
|
||||
const sources: Array<AppendedVectorInfo<T>> = new Array<AppendedVectorInfo<T>>(); |
||||
for (const src of this.source) { |
||||
sources.push(src); |
||||
if (src.end > length) { |
||||
src.end = length; |
||||
break; |
||||
} |
||||
} |
||||
this.source = sources; |
||||
this.length = length; |
||||
} |
||||
} |
||||
|
||||
append(v: Vector<T>): AppendedVectorInfo<T> { |
||||
const info = { |
||||
start: this.length, |
||||
end: this.length + v.length, |
||||
values: v, |
||||
}; |
||||
this.length = info.end; |
||||
this.source.push(info); |
||||
return info; |
||||
} |
||||
|
||||
get(index: number): T { |
||||
for (let i = 0; i < this.source.length; i++) { |
||||
const src = this.source[i]; |
||||
if (index >= src.start && index < src.end) { |
||||
return src.values.get(index - src.start); |
||||
} |
||||
} |
||||
return (undefined as unknown) as T; |
||||
} |
||||
|
||||
toArray(): T[] { |
||||
return vectorToArray(this); |
||||
} |
||||
|
||||
toJSON(): T[] { |
||||
return vectorToArray(this); |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
import { ArrayVector } from './ArrayVector'; |
||||
import { AppendedVectors } from './AppendedVectors'; |
||||
|
||||
describe('Check Appending Vector', () => { |
||||
it('should transparently join them', () => { |
||||
const appended = new AppendedVectors(); |
||||
appended.append(new ArrayVector([1, 2, 3])); |
||||
appended.append(new ArrayVector([4, 5, 6])); |
||||
appended.append(new ArrayVector([7, 8, 9])); |
||||
expect(appended.length).toEqual(9); |
||||
|
||||
appended.setLength(5); |
||||
expect(appended.length).toEqual(5); |
||||
appended.append(new ArrayVector(['a', 'b', 'c'])); |
||||
expect(appended.length).toEqual(8); |
||||
expect(appended.toArray()).toEqual([1, 2, 3, 4, 5, 'a', 'b', 'c']); |
||||
|
||||
appended.setLength(2); |
||||
appended.setLength(6); |
||||
appended.append(new ArrayVector(['x', 'y', 'z'])); |
||||
expect(appended.toArray()).toEqual([1, 2, undefined, undefined, undefined, undefined, 'x', 'y', 'z']); |
||||
}); |
||||
}); |
@ -0,0 +1,73 @@ |
||||
import { Vector } from '../types/vector'; |
||||
import { vectorToArray } from './vectorToArray'; |
||||
|
||||
interface AppendedVectorInfo<T> { |
||||
start: number; |
||||
end: number; |
||||
values: Vector<T>; |
||||
} |
||||
|
||||
/** |
||||
* This may be more trouble than it is worth. This trades some computation time for |
||||
* RAM -- rather than allocate a new array the size of all previous arrays, this just |
||||
* points the correct index to their original array values |
||||
*/ |
||||
export class AppendedVectors<T = any> implements Vector<T> { |
||||
length = 0; |
||||
source: Array<AppendedVectorInfo<T>> = new Array<AppendedVectorInfo<T>>(); |
||||
|
||||
constructor(startAt = 0) { |
||||
this.length = startAt; |
||||
} |
||||
|
||||
/** |
||||
* Make the vector look like it is this long |
||||
*/ |
||||
setLength(length: number) { |
||||
if (length > this.length) { |
||||
// make the vector longer (filling with undefined)
|
||||
this.length = length; |
||||
} else if (length < this.length) { |
||||
// make the array shorter
|
||||
const sources: Array<AppendedVectorInfo<T>> = new Array<AppendedVectorInfo<T>>(); |
||||
for (const src of this.source) { |
||||
sources.push(src); |
||||
if (src.end > length) { |
||||
src.end = length; |
||||
break; |
||||
} |
||||
} |
||||
this.source = sources; |
||||
this.length = length; |
||||
} |
||||
} |
||||
|
||||
append(v: Vector<T>): AppendedVectorInfo<T> { |
||||
const info = { |
||||
start: this.length, |
||||
end: this.length + v.length, |
||||
values: v, |
||||
}; |
||||
this.length = info.end; |
||||
this.source.push(info); |
||||
return info; |
||||
} |
||||
|
||||
get(index: number): T { |
||||
for (let i = 0; i < this.source.length; i++) { |
||||
const src = this.source[i]; |
||||
if (index >= src.start && index < src.end) { |
||||
return src.values.get(index - src.start); |
||||
} |
||||
} |
||||
return (undefined as unknown) as T; |
||||
} |
||||
|
||||
toArray(): T[] { |
||||
return vectorToArray(this); |
||||
} |
||||
|
||||
toJSON(): T[] { |
||||
return vectorToArray(this); |
||||
} |
||||
} |
@ -0,0 +1,37 @@ |
||||
import { MutableVector } from '../types/vector'; |
||||
|
||||
export class ArrayVector<T = any> implements MutableVector<T> { |
||||
buffer: T[]; |
||||
|
||||
constructor(buffer?: T[]) { |
||||
this.buffer = buffer ? buffer : []; |
||||
} |
||||
|
||||
get length() { |
||||
return this.buffer.length; |
||||
} |
||||
|
||||
add(value: T) { |
||||
this.buffer.push(value); |
||||
} |
||||
|
||||
get(index: number): T { |
||||
return this.buffer[index]; |
||||
} |
||||
|
||||
set(index: number, value: T) { |
||||
this.buffer[index] = value; |
||||
} |
||||
|
||||
reverse() { |
||||
this.buffer.reverse(); |
||||
} |
||||
|
||||
toArray(): T[] { |
||||
return this.buffer; |
||||
} |
||||
|
||||
toJSON(): T[] { |
||||
return this.buffer; |
||||
} |
||||
} |
@ -0,0 +1,138 @@ |
||||
import { MutableVector } from '../types/vector'; |
||||
import { vectorToArray } from './vectorToArray'; |
||||
|
||||
interface CircularOptions<T> { |
||||
buffer?: T[]; |
||||
append?: 'head' | 'tail'; |
||||
capacity?: number; |
||||
} |
||||
|
||||
/** |
||||
* Circular vector uses a single buffer to capture a stream of values |
||||
* overwriting the oldest value on add. |
||||
* |
||||
* This supports addting to the 'head' or 'tail' and will grow the buffer |
||||
* to match a configured capacity. |
||||
*/ |
||||
export class CircularVector<T = any> implements MutableVector<T> { |
||||
private buffer: T[]; |
||||
private index: number; |
||||
private capacity: number; |
||||
private tail: boolean; |
||||
|
||||
constructor(options: CircularOptions<T>) { |
||||
this.buffer = options.buffer || []; |
||||
this.capacity = this.buffer.length; |
||||
this.tail = 'head' !== options.append; |
||||
this.index = 0; |
||||
|
||||
this.add = this.getAddFunction(); |
||||
if (options.capacity) { |
||||
this.setCapacity(options.capacity); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* This gets the appropriate add function depending on the buffer state: |
||||
* * head vs tail |
||||
* * growing buffer vs overwriting values |
||||
*/ |
||||
private getAddFunction() { |
||||
// When we are not at capacity, it should actually modify the buffer
|
||||
if (this.capacity > this.buffer.length) { |
||||
if (this.tail) { |
||||
return (value: T) => { |
||||
this.buffer.push(value); |
||||
if (this.buffer.length >= this.capacity) { |
||||
this.add = this.getAddFunction(); |
||||
} |
||||
}; |
||||
} else { |
||||
return (value: T) => { |
||||
this.buffer.unshift(value); |
||||
if (this.buffer.length >= this.capacity) { |
||||
this.add = this.getAddFunction(); |
||||
} |
||||
}; |
||||
} |
||||
} |
||||
|
||||
if (this.tail) { |
||||
return (value: T) => { |
||||
this.buffer[this.index] = value; |
||||
this.index = (this.index + 1) % this.buffer.length; |
||||
}; |
||||
} |
||||
|
||||
// Append values to the head
|
||||
return (value: T) => { |
||||
let idx = this.index - 1; |
||||
if (idx < 0) { |
||||
idx = this.buffer.length - 1; |
||||
} |
||||
this.buffer[idx] = value; |
||||
this.index = idx; |
||||
}; |
||||
} |
||||
|
||||
setCapacity(v: number) { |
||||
if (this.capacity === v) { |
||||
return; |
||||
} |
||||
// Make a copy so it is in order and new additions can be at the head or tail
|
||||
const copy = this.toArray(); |
||||
if (v > this.length) { |
||||
this.buffer = copy; |
||||
} else if (v < this.capacity) { |
||||
// Shrink the buffer
|
||||
const delta = this.length - v; |
||||
if (this.tail) { |
||||
this.buffer = copy.slice(delta, copy.length); // Keep last items
|
||||
} else { |
||||
this.buffer = copy.slice(0, copy.length - delta); // Keep first items
|
||||
} |
||||
} |
||||
this.capacity = v; |
||||
this.index = 0; |
||||
this.add = this.getAddFunction(); |
||||
} |
||||
|
||||
setAppendMode(mode: 'head' | 'tail') { |
||||
const tail = 'head' !== mode; |
||||
if (tail !== this.tail) { |
||||
this.buffer = this.toArray().reverse(); |
||||
this.index = 0; |
||||
this.tail = tail; |
||||
this.add = this.getAddFunction(); |
||||
} |
||||
} |
||||
|
||||
reverse() { |
||||
this.buffer.reverse(); |
||||
} |
||||
|
||||
/** |
||||
* Add the value to the buffer |
||||
*/ |
||||
add: (value: T) => void; |
||||
|
||||
get(index: number) { |
||||
return this.buffer[(index + this.index) % this.buffer.length]; |
||||
} |
||||
|
||||
set(index: number, value: T) { |
||||
this.buffer[(index + this.index) % this.buffer.length] = value; |
||||
} |
||||
|
||||
get length() { |
||||
return this.buffer.length; |
||||
} |
||||
|
||||
toArray(): T[] { |
||||
return vectorToArray(this); |
||||
} |
||||
|
||||
toJSON(): T[] { |
||||
return vectorToArray(this); |
||||
} |
||||
} |
@ -0,0 +1,17 @@ |
||||
import { ConstantVector } from './ConstantVector'; |
||||
|
||||
describe('ConstantVector', () => { |
||||
it('should support constant values', () => { |
||||
const value = 3.5; |
||||
const v = new ConstantVector(value, 7); |
||||
expect(v.length).toEqual(7); |
||||
|
||||
expect(v.get(0)).toEqual(value); |
||||
expect(v.get(1)).toEqual(value); |
||||
|
||||
// Now check all of them
|
||||
for (let i = 0; i < 10; i++) { |
||||
expect(v.get(i)).toEqual(value); |
||||
} |
||||
}); |
||||
}); |
@ -0,0 +1,22 @@ |
||||
import { Vector } from '../types/vector'; |
||||
|
||||
export class ConstantVector<T = any> implements Vector<T> { |
||||
constructor(private value: T, private len: number) {} |
||||
|
||||
get length() { |
||||
return this.len; |
||||
} |
||||
|
||||
get(index: number): T { |
||||
return this.value; |
||||
} |
||||
|
||||
toArray(): T[] { |
||||
const arr = new Array<T>(this.length); |
||||
return arr.fill(this.value); |
||||
} |
||||
|
||||
toJSON(): T[] { |
||||
return this.toArray(); |
||||
} |
||||
} |
@ -0,0 +1,15 @@ |
||||
import { ArrayVector } from './ArrayVector'; |
||||
import { ScaledVector } from './ScaledVector'; |
||||
|
||||
describe('ScaledVector', () => { |
||||
it('should support multiply operations', () => { |
||||
const source = new ArrayVector([1, 2, 3, 4]); |
||||
const scale = 2.456; |
||||
const v = new ScaledVector(source, scale); |
||||
expect(v.length).toEqual(source.length); |
||||
// expect(v.push(10)).toEqual(source.length); // not implemented
|
||||
for (let i = 0; i < 10; i++) { |
||||
expect(v.get(i)).toEqual(source.get(i) * scale); |
||||
} |
||||
}); |
||||
}); |
@ -0,0 +1,22 @@ |
||||
import { Vector } from '../types/vector'; |
||||
import { vectorToArray } from './vectorToArray'; |
||||
|
||||
export class ScaledVector implements Vector<number> { |
||||
constructor(private source: Vector<number>, private scale: number) {} |
||||
|
||||
get length(): number { |
||||
return this.source.length; |
||||
} |
||||
|
||||
get(index: number): number { |
||||
return this.source.get(index) * this.scale; |
||||
} |
||||
|
||||
toArray(): number[] { |
||||
return vectorToArray(this); |
||||
} |
||||
|
||||
toJSON(): number[] { |
||||
return vectorToArray(this); |
||||
} |
||||
} |
@ -0,0 +1,25 @@ |
||||
import { Vector } from '../types/vector'; |
||||
import { vectorToArray } from './vectorToArray'; |
||||
|
||||
/** |
||||
* Values are returned in the order defined by the input parameter |
||||
*/ |
||||
export class SortedVector<T = any> implements Vector<T> { |
||||
constructor(private source: Vector<T>, private order: number[]) {} |
||||
|
||||
get length(): number { |
||||
return this.source.length; |
||||
} |
||||
|
||||
get(index: number): T { |
||||
return this.source.get(this.order[index]); |
||||
} |
||||
|
||||
toArray(): T[] { |
||||
return vectorToArray(this); |
||||
} |
||||
|
||||
toJSON(): T[] { |
||||
return vectorToArray(this); |
||||
} |
||||
} |
@ -0,0 +1,6 @@ |
||||
export * from './AppendedVectors'; |
||||
export * from './ArrayVector'; |
||||
export * from './CircularVector'; |
||||
export * from './ConstantVector'; |
||||
export * from './ScaledVector'; |
||||
export * from './SortedVector'; |
@ -0,0 +1,9 @@ |
||||
import { Vector } from '../types/vector'; |
||||
|
||||
export function vectorToArray<T>(v: Vector<T>): T[] { |
||||
const arr: T[] = []; |
||||
for (let i = 0; i < v.length; i++) { |
||||
arr[i] = v.get(i); |
||||
} |
||||
return arr; |
||||
} |
Loading…
Reference in new issue