FieldValues: Use plain arrays instead of Vector (part 1 of 2) (#66187)

Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
Co-authored-by: Torkel Ödegaard <torkel@grafana.com>
This commit is contained in:
Ryan McKinley
2023-04-14 07:36:53 -05:00
committed by GitHub
co-authored by Leon Sorokin Torkel Ödegaard
parent 4f5b80095e
commit b8188eead4
37 changed files with 531 additions and 292 deletions
@@ -1,5 +1,6 @@
import { Vector } from '../types/vector';
import { FunctionalVector } from './FunctionalVector';
import { vectorToArray } from './vectorToArray';
interface AppendedVectorInfo<T> {
@@ -13,11 +14,12 @@ interface AppendedVectorInfo<T> {
* 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> {
export class AppendedVectors<T = any> extends FunctionalVector<T> {
length = 0;
source: Array<AppendedVectorInfo<T>> = [];
constructor(startAt = 0) {
super();
this.length = startAt;
}
@@ -0,0 +1,41 @@
import { Field, FieldType } from '../types';
import { ArrayVector } from './ArrayVector';
describe('ArrayVector', () => {
it('should init 150k with 65k Array.push() chonking', () => {
const arr = Array.from({ length: 150e3 }, (v, i) => i);
const av = new ArrayVector(arr);
expect(av.toArray()).toEqual(arr);
});
it('should support add and push', () => {
const av = new ArrayVector<number>();
av.add(1);
av.push(2);
av.push(3, 4);
expect(av.toArray()).toEqual([1, 2, 3, 4]);
});
it('typescript should not re-define the ArrayVector<T> based on input to the constructor', () => {
const field: Field<number> = {
name: 'test',
config: {},
type: FieldType.number,
values: new ArrayVector(), // this defaults to `new ArrayVector<any>()`
};
expect(field).toBeDefined();
// Before collapsing Vector, ReadWriteVector, and MutableVector these all worked fine
field.values = new ArrayVector();
field.values = new ArrayVector(undefined);
field.values = new ArrayVector([1, 2, 3]);
field.values = new ArrayVector([]);
field.values = new ArrayVector([1, undefined]);
field.values = new ArrayVector([null]);
field.values = new ArrayVector(['a', 'b', 'c']);
expect(field.values.length).toBe(3);
});
});
+28 -33
View File
@@ -1,43 +1,38 @@
import { MutableVector } from '../types/vector';
import { FunctionalVector } from './FunctionalVector';
/**
* @public
*
* @deprecated use a simple Array<T>
*/
export class ArrayVector<T = any> extends FunctionalVector<T> implements MutableVector<T> {
buffer: T[];
export class ArrayVector<T = any> extends Array<T> {
get buffer() {
return this;
}
constructor(buffer?: T[]) {
set buffer(values: any[]) {
this.length = 0;
const len = values?.length;
if (len) {
let chonkSize = 65e3;
let numChonks = Math.ceil(len / chonkSize);
for (let chonkIdx = 0; chonkIdx < numChonks; chonkIdx++) {
this.push.apply(this, values.slice(chonkIdx * chonkSize, (chonkIdx + 1) * chonkSize));
}
}
}
/**
* This any type is here to make the change type changes in v10 non breaking for plugins.
* Before you could technically assign field.values any typed ArrayVector no matter what the Field<T> T type was.
*/
constructor(buffer?: any[]) {
super();
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;
this.buffer = buffer ?? [];
}
toJSON(): T[] {
return this.buffer;
return [...this]; // copy to avoid circular reference (only for jest)
}
}
@@ -1,13 +1,16 @@
import { Vector } from '../types/vector';
import { BinaryOperation } from '../utils/binaryOperators';
import { FunctionalVector } from './FunctionalVector';
import { vectorToArray } from './vectorToArray';
/**
* @public
*/
export class BinaryOperationVector implements Vector<number> {
constructor(private left: Vector<number>, private right: Vector<number>, private operation: BinaryOperation) {}
export class BinaryOperationVector extends FunctionalVector<number> {
constructor(private left: Vector<number>, private right: Vector<number>, private operation: BinaryOperation) {
super();
}
get length(): number {
return this.left.length;
@@ -20,6 +20,9 @@ describe('Check Circular Vector', () => {
v.add(8);
expect(v.toArray()).toEqual([6, 7, 8]);
v.push(9, 10);
expect(v.toArray()).toEqual([8, 9, 10]);
});
it('should grow buffer until it hits capacity (append)', () => {
@@ -1,7 +1,4 @@
import { MutableVector } from '../types/vector';
import { FunctionalVector } from './FunctionalVector';
import { vectorToArray } from './vectorToArray';
interface CircularOptions<T> {
buffer?: T[];
@@ -18,7 +15,7 @@ interface CircularOptions<T> {
*
* @public
*/
export class CircularVector<T = any> extends FunctionalVector<T> implements MutableVector<T> {
export class CircularVector<T = any> extends FunctionalVector<T> {
private buffer: T[];
private index: number;
private capacity: number;
@@ -43,7 +40,7 @@ export class CircularVector<T = any> extends FunctionalVector<T> implements Muta
* * head vs tail
* * growing buffer vs overwriting values
*/
private getAddFunction() {
private getAddFunction(): (value: T) => void {
// When we are not at capacity, it should actually modify the buffer
if (this.capacity > this.buffer.length) {
if (this.tail) {
@@ -114,31 +111,18 @@ export class CircularVector<T = any> extends FunctionalVector<T> implements Muta
}
reverse() {
this.buffer.reverse();
return 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) {
set(index: number, value: any) {
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);
}
}
@@ -1,10 +1,12 @@
import { Vector } from '../types/vector';
import { FunctionalVector } from './FunctionalVector';
/**
* @public
*/
export class ConstantVector<T = any> implements Vector<T> {
constructor(private value: T, private len: number) {}
export class ConstantVector<T = any> extends FunctionalVector<T> {
constructor(private value: T, private len: number) {
super();
}
get length() {
return this.len;
@@ -3,7 +3,7 @@ import { Vector } from '../types';
import { vectorToArray } from './vectorToArray';
/** @public */
export abstract class FunctionalVector<T = any> implements Vector<T>, Iterable<T> {
export abstract class FunctionalVector<T = any> implements Vector<T> {
abstract get length(): number;
abstract get(index: number): T;
@@ -15,32 +15,176 @@ export abstract class FunctionalVector<T = any> implements Vector<T>, Iterable<T
}
}
set(index: number, value: any): void {
throw 'unsupported operation';
}
add(value: T): void {
throw 'unsupported operation';
}
push(...vals: T[]): number {
for (const v of vals) {
this.add(v);
}
return this.length;
}
// Implement "iterable protocol"
[Symbol.iterator]() {
return this.iterator();
}
forEach(iterator: (row: T) => void) {
forEach(iterator: (row: T, index: number, array: T[]) => void): void {
return vectorator(this).forEach(iterator);
}
map<V>(transform: (item: T, index: number) => V) {
map<V>(transform: (item: T, index: number, array: T[]) => V): V[] {
return vectorator(this).map(transform);
}
filter(predicate: (item: T) => boolean): T[] {
filter(predicate: (item: T, index: number, array: T[]) => boolean): T[] {
return vectorator(this).filter(predicate);
}
at(index: number): T | undefined {
return this.get(index);
}
toArray(): T[] {
return vectorToArray(this);
}
join(separator?: string | undefined): string {
return this.toArray().join(separator);
}
toJSON(): any {
return this.toArray();
}
//--------------------------
// Method not implemented
//--------------------------
[n: number]: T;
pop(): T | undefined {
throw new Error('Method not implemented.');
}
concat(...items: Array<ConcatArray<T>>): T[];
concat(...items: Array<T | ConcatArray<T>>): T[] {
throw new Error('Method not implemented.');
}
reverse(): T[] {
throw new Error('Method not implemented.');
}
shift(): T | undefined {
throw new Error('Method not implemented.');
}
slice(start?: number | undefined, end?: number | undefined): T[] {
throw new Error('Method not implemented.');
}
sort(compareFn?: ((a: T, b: T) => number) | undefined): this {
throw new Error('Method not implemented.');
}
splice(start: number, deleteCount?: number | undefined): T[];
splice(start: number, deleteCount: number, ...items: T[]): T[] {
throw new Error('Method not implemented.');
}
unshift(...items: T[]): number {
throw new Error('Method not implemented.');
}
fill(value: T, start?: number | undefined, end?: number | undefined): this {
throw new Error('Method not implemented.');
}
copyWithin(target: number, start: number, end?: number | undefined): this {
throw new Error('Method not implemented.');
}
[Symbol.unscopables](): {
copyWithin: boolean;
entries: boolean;
fill: boolean;
find: boolean;
findIndex: boolean;
keys: boolean;
values: boolean;
} {
throw new Error('Method not implemented.');
}
//--------------------------------------------------------------------------------
// Delegated Array function -- these will not be efficient :grimmice:
//--------------------------------------------------------------------------------
indexOf(searchElement: T, fromIndex?: number | undefined): number {
return this.toArray().indexOf(searchElement, fromIndex);
}
lastIndexOf(searchElement: T, fromIndex?: number | undefined): number {
return this.toArray().lastIndexOf(searchElement, fromIndex);
}
every<S extends T>(predicate: (value: T, index: number, array: T[]) => value is S, thisArg?: any): this is S[];
every(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): boolean;
every(predicate: any, thisArg?: unknown): boolean {
return this.toArray().every(predicate, thisArg);
}
some(predicate: (value: T, index: number, array: T[]) => unknown, thisArg?: any): boolean {
return this.toArray().some(predicate, thisArg);
}
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T;
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
reduce<U>(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U;
reduce(callbackfn: unknown, initialValue?: unknown): T {
throw new Error('Method not implemented.');
}
reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T;
reduceRight(
callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T,
initialValue: T
): T;
reduceRight<U>(
callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U,
initialValue: U
): U;
reduceRight(callbackfn: unknown, initialValue?: unknown): T {
throw new Error('Method not implemented.');
}
find<S extends T>(
predicate: (this: void, value: T, index: number, obj: T[]) => value is S,
thisArg?: any
): S | undefined;
find(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): T | undefined {
return this.toArray().find(predicate, thisArg);
}
findIndex(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): number {
return this.toArray().findIndex(predicate, thisArg);
}
entries(): IterableIterator<[number, T]> {
return this.toArray().entries();
}
keys(): IterableIterator<number> {
return this.toArray().keys();
}
values(): IterableIterator<T> {
return this.toArray().values();
}
includes(searchElement: T, fromIndex?: number | undefined): boolean {
return this.toArray().includes(searchElement, fromIndex);
}
flatMap<U, This = undefined>(
callback: (this: This, value: T, index: number, array: T[]) => U | readonly U[],
thisArg?: This | undefined
): U[] {
return this.toArray().flatMap(callback, thisArg);
}
flat<A, D extends number = 1>(this: A, depth?: D | undefined): Array<FlatArray<A, D>> {
throw new Error('Method not implemented.');
}
}
const emptyarray: any[] = [];
/**
* Use functional programming with your vector
*/
@@ -52,25 +196,26 @@ export function vectorator<T>(vector: Vector<T>) {
}
},
forEach(iterator: (row: T) => void) {
forEach(iterator: (row: T, index: number, array: T[]) => void): void {
for (let i = 0; i < vector.length; i++) {
iterator(vector.get(i));
iterator(vector.get(i), i, emptyarray);
}
},
map<V>(transform: (item: T, index: number) => V) {
map<V>(transform: (item: T, index: number, array: T[]) => V): V[] {
const result: V[] = [];
for (let i = 0; i < vector.length; i++) {
result.push(transform(vector.get(i), i));
result.push(transform(vector.get(i), i, emptyarray));
}
return result;
},
/** Add a predicate where you return true if it should *keep* the value */
filter(predicate: (item: T) => boolean): T[] {
filter(predicate: (item: T, index: number, array: T[]) => boolean): T[] {
const result: T[] = [];
let count = 0;
for (const val of this) {
if (predicate(val)) {
if (predicate(val, count++, emptyarray)) {
result.push(val);
}
}
@@ -6,6 +6,8 @@ import { FunctionalVector } from './FunctionalVector';
* IndexVector is a simple vector implementation that returns the index value
* for each element in the vector. It is functionally equivolant a vector backed
* by an array with values: `[0,1,2,...,length-1]`
*
* @deprecated use a simple Arrays
*/
export class IndexVector extends FunctionalVector<number> {
constructor(private len: number) {
@@ -1,13 +1,16 @@
import { Vector } from '../types';
import { FunctionalVector } from './FunctionalVector';
import { vectorToArray } from './vectorToArray';
/**
* RowVector makes the row values look like a vector
* @internal
*/
export class RowVector implements Vector {
constructor(private columns: Vector[]) {}
export class RowVector extends FunctionalVector<number> {
constructor(private columns: Vector[]) {
super();
}
rowIndex = 0;
@@ -1,12 +1,17 @@
import { Vector } from '../types/vector';
import { FunctionalVector } from './FunctionalVector';
import { vectorToArray } from './vectorToArray';
/**
* Values are returned in the order defined by the input parameter
*
* @deprecated use a simple Arrays
*/
export class SortedVector<T = any> implements Vector<T> {
constructor(private source: Vector<T>, private order: number[]) {}
export class SortedVector<T = any> extends FunctionalVector<T> {
constructor(private source: Vector<T>, private order: number[]) {
super();
}
get length(): number {
return this.source.length;
@@ -1,5 +1,6 @@
import { Vector } from '../types/vector';
/** @deprecated use a simple Arrays */
export function vectorToArray<T>(v: Vector<T>): T[] {
const arr: T[] = Array(v.length);
for (let i = 0; i < v.length; i++) {