Tuesday 31 December 2019

Aggregate in Typescript

I have implemented Aggregate method in my Linq library written for Typescript.

if (!Array.prototype.Aggregate) {
  Array.prototype.Aggregate = function <T>(accumulator: any, currentValue: any, reducerFunc: (accumulator: any, currentValue: any) => any): any {
    //debugger
    if (reducerFunc === undefined || reducerFunc === null) {
      reducerFunc = (accumulator, currentValue) => accumulator + currentValue;
    }
    let result = this.reduce(reducerFunc);
    return result;
  }
}

if (!Array.prototype.AggregateSelect) {
  Array.prototype.AggregateSelect = function <T>(property: (keyof T), accumulator: any, currentValue: any, reducerFunc: (accumulator: any, currentValue: any) => any): any {
    //debugger
    if (reducerFunc === undefined || reducerFunc === null) {
      reducerFunc = (accumulator, currentValue) => accumulator + currentValue;
    }
    //debugger
    let result = this.Select(property).map(n => n[property]).reduce(reducerFunc);
    return result;
  }
}

Here are some Jasmine tests for these two methods - note that I also support setting the initial value.
  it('can aggregate items to expected result using Aggregate on array of items of numbers', () => {
    let someNums = [1, 2, 3, 4];
    let result = someNums.Aggregate(0, 0, null);
    expect(result).toBe(10);
  });

  it('can aggregate items and project to expected result using AggregateSelect on array of items of objects', () => {
    let someArray: any[] = [];
    someArray.push(<SomeClass>{ Name: "Foo", Num: 1 });
    someArray.push(<SomeClass>{ Name: "FooBaz", Num: 4 });
    someArray.push(<SomeClass>{ Name: "AllyoBaze", Num: 7 });
    let result = someArray.AggregateSelect<SomeClass>("Num", 0, 0, null);
    expect(result).toBe(12);
  });

  it('can aggregate once more items and project to expected result using AggregateSelect on array of items of objects with accumulator value set initially', () => {
    let someArray: Student[] = [];
    someArray.push(<Student>{ StudentID: 1, StudentName: "John", Age: 13 });
    someArray.push(<Student>{ StudentID: 2, StudentName: "Moin", Age: 21 });
    someArray.push(<Student>{ StudentID: 3, StudentName: "Bill", Age: 18 });
    someArray.push(<Student>{ StudentID: 4, StudentName: "Ram", Age: 20 });
    someArray.push(<Student>{ StudentID: 5, StudentName: "Ron", Age: 15 });
    let result = someArray.AggregateSelect<Student>("StudentName", "Student Names: ", 0, (a, b) => a + "," + b);
    expect(result).toBe("John,Moin,Bill,Ram,Ron");
  });

My interface definition is growing everytime for my Linq Library! Here is how it looks now:

export { } //creating a module of below code
declare global {
  type predicate<T> = (arg: T) => boolean;
  type sortingValue<T> = (arg: T) => any;
  interface Array<T> {
    FirstOrDefault<T>(condition: predicate<T>): T;
    LastOrDefault<T>(condition: predicate<T>): T;
    Where<T>(condition: predicate<T>): T[];
    Count<T>(): number;
    CountBy<T>(condition: predicate<T>): number;
    Select<T>(...properties: (keyof T)[]): any[];
    GroupBy<T>(groupFunc: (arg: T) => string): any[];
    EnumerableRange(start: number, count: number): number[];
    Any<T>(condition: predicate<T>): boolean;
    All<T>(condition: predicate<T>): boolean;
    MaxSelect<T>(property: (keyof T)): any;
    MinSelect<T>(property: (keyof T)): any;
    Average<T>(): number;
    AverageSelect<T>(property: (keyof T)): number;
    Max(): any;
    Min(): any;
    Sum(): any;
    Distinct<T>(): T[];
    DistinctBy<T>(property: (keyof T)): any;
    SumSelect<T>(property: (keyof T)): any;
    Intersect<T>(otherArray: T[]): T[];
    IntersectSelect<T>(property: (keyof T), otherArray: T[]): T[];
    MinSelect<T>(property: (keyof T)): any;
    OrderBy<T>(sortMember: sortingValue<T>): T[];
    OrderByDescending<T>(sortMember: sortingValue<T>): T[];
    ThenBy<T>(sortMember: sortingValue<T>): T[];
    OfType<T>(compareObject: T): T[];
    SequenceEqual<T>(compareArray: T): boolean;
    Take<T>(count: number): T[];
    TakeWhile<T>(condition: predicate<T>): T[];
    SkipWhile<T>(condition: predicate<T>): T[];
    Skip<T>(count: number): T[];
    defaultComparerSort<T>(x: T, y: T);
    ElementAt<T>(index: number);
    ElementAtOrDefault<T>(index: number);
    Aggregate<T>(accumulator: any, currentValue: any, reducerFunc: (accumulator: any, currentValue: any) => any): any;
    AggregateSelect<T>(property: (keyof T), accumulator: any, currentValue: any, reducerFunc: (accumulator: any, currentValue: any) => any): any;
  }
}

DistinctBy operator written in Typescript

I am extended my Linq library for Typescript with many more methods! Here is my implementation of DistinctBy.

if (!Array.prototype.DistinctBy) {
  Array.prototype.DistinctBy = function <T>(property: (keyof T)): T[] {
    if (this === null || this === undefined) {
      return [];
    }
    let filteringArray = this.Select(property).map(n => n[property]);

    let distinctRunOnArray = this.filter((value, index, array) => {
      let valueProperty = value[property];
      return filteringArray.indexOf(valueProperty) === index;
    });
    return distinctRunOnArray;
  }
}

This Jasmine test can test this operator out.
describe('TSLinq Jasmine tests', () => {

  it('can filter out duplicates using DistinctBy on array of items of objects', () => {
    let someArray: Student[] = [];
    someArray.push(<Student>{ StudentID: 1, StudentName: "John", Age: 13 });
    someArray.push(<Student>{ StudentID: 2, StudentName: "Moin", Age: 21 });
    someArray.push(<Student>{ StudentID: 2, StudentName: "Moin", Age: 21 });
    someArray.push(<Student>{ StudentID: 4, StudentName: "Ram", Age: 20 });
    someArray.push(<Student>{ StudentID: 5, StudentName: "Ron", Age: 15 });
    let expectedArray: Student[] = [];
    expectedArray.push(<Student>{ StudentID: 1, StudentName: "John", Age: 13 });
    expectedArray.push(<Student>{ StudentID: 2, StudentName: "Moin", Age: 21 });
    expectedArray.push(<Student>{ StudentID: 4, StudentName: "Ram", Age: 20 });
    expectedArray.push(<Student>{ StudentID: 5, StudentName: "Ron", Age: 15 });
    let result = someArray.DistinctBy<Student>("StudentID");
    expect(result).toEqual(expectedArray);
  });


});
The Student class is simple:

class Student {
  StudentID: number;
  StudentName: string;
  Age: number;
}

Implementing OfType in Typescript

I am working on my Linq library for Typescript and wanted to implement OfType. Turns out, this is hard because the generic type arguments in Typescript usually requires a value, i.e. an object instance of type T to get any shape information at run-time. So I ended up passing in a vanilla object setting default property values instead. Here is how my implementation ended up:

function isOfSimilarShape<T>(input: any, compareObject: T): boolean {
  if (input === undefined || input === null || compareObject === undefined || compareObject === null)
    return false;

  let propsOfInput = Object.keys(input);
  let propsOfCompareObject = Object.keys(compareObject);
  //debugger
  let sameShapeOfInputAndCompareObject = propsOfInput.EqualTo(propsOfCompareObject);
  return sameShapeOfInputAndCompareObject;
}

if (!Array.prototype.OfType) {
  Array.prototype.OfType = function <T>(compareObject: T): T[] {
    let result: T[] = [];
    this.forEach(el => {
      //debugger
      let t: T = null;
      if (isOfSimilarShape(el, compareObject))
        result.push(el);
    });
    return result;
  }
}

The following Jasmine test shows its usage:

describe('Array Extensions tests', () => {

  it('can find desired items using OfType of type T', () => {
    let someMixedArray: any[] = [];
    someMixedArray.push(<SomeClass>{ Name: "Foo", Num: 1 });
    someMixedArray.push(<SomeOtherClass>{ SomeName: "BarBazBaze", SomeOtherNum: 813 });
    someMixedArray.push(<SomeClass>{ Name: "FooBaz", Num: 4 });
    someMixedArray.push(<SomeOtherClass>{ SomeName: "BarBaze", SomeOtherNum: 13 });
    someMixedArray.push(<SomeClass>{ Name: "AllyoBaze", Num: 7 });

    let compareObject = <SomeClass>{ Name: "", Num: 0 };
    let filteredArrayBySpecifiedType = someMixedArray.OfType(compareObject);
    console.log(filteredArrayBySpecifiedType);

    expect(filteredArrayBySpecifiedType.All(item => <SomeClass>item !== undefined)).toBe(true, "Expected only items of type SomeOtherClass in the filtered array after running OfType of SomeOtherClass on it.");
  });

It would be nice if we did not have to pass in a vanilla object and populate its properties, but I could not find any tips online or in the Typescript documentation for how to implement extracting type information from generic arguments of Typescript. This is very easy in C#, but while Typescript gives compilation type information, getting runtime information from generic arguments in the Javascript code that Typescript compiles into turned much harder.