Thursday 1 July 2021

SelectMany / Flattening multiple arrays at arbitrary depth in Typescript (Javascript)

I just added a flatten method of my SimpleTsLinq library today! The Github repo is at: The Npm page is at: This method can flatten multiple arrays at desired depth (defaults to Infinity) and each array itself may have arbitrary depth. The end result is that the multiple (nested arrays) are returned as a flat, single array. Much similar to SelectMany in Linq! First I added the method to generic interface Array below
 
 export { } //creating a module of below code
declare global {
  type predicate<T> = (arg: T) => boolean;
  type sortingValue<T> = (arg: T) => any;
  type keySelector<T> = (arg: T) => any;
  type resultSelector<T, TInner> = (arg: T, arg2: TInner) => any;
  interface Array<T> {
    AddRange<T>(itemsToAdd: T[]);
    InsertRange<T>(index: number, itemsToAdd: T[]);
    RemoveAt(index: number): T;
    RemoveWhere<T>(condition: predicate<T>): T[];
    FirstOrDefault<T>(condition: predicate<T>): T;
    SingleOrDefault<T>(condition: predicate<T>): T;
    First<T>(condition: predicate<T>): T;
    Single<T>(condition: predicate<T>): T;
    LastOrDefault<T>(condition: predicate<T>): T;
    Join<T, TInner>(otherArray: TInner[], outerKeySelector: keySelector<T>,
      innerKeySelector: keySelector<TInner>, res: resultSelector<T, TInner>): any[];
    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;
    Contains<T>(item: 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;
    Reverse<T>(): T[];
    Empty<T>(): T[];
    Except<T>(otherArray: T[]): T[];
    Intersect<T>(otherArray: T[]): T[];
    Union<T>(otherArray: T[]): T[];
    Cast<TOtherType>(TOtherType: Function): TOtherType[];
    TryCast<TOtherType>(TOtherType: Function): TOtherType[];
    GetProperties<T>(TClass: Function, sortProps: boolean): string[];
    Concat<T>(otherArray: T[]): T[];
    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[];
    ToDictionary<T>(keySelector: (arg: T) => any): any;
    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;
    Flatten<T>(otherArrays: T[][], depth: number): T[];
  }
}
 

Now we can implement the method as follows:
 
  
if (!Array.prototype.Flatten) {
  Array.prototype.Flatten = function <T>(otherArrays: T[][] = null, depth = Infinity) {
    let flattenedArrayOfThis = [...flatten(this, depth)];
    if (otherArrays == null || otherArrays == undefined) {
      return flattenedArrayOfThis;
    }
    return [...flattenedArrayOfThis, ...flatten(otherArrays, depth)];
  }
}

function* flatten(array, depth) {
  if (depth === undefined) {
    depth = 1;
  }
  for (const item of array) {
    if (Array.isArray(item) && depth > 0) {
      yield* flatten(item, depth - 1);
    } else {
      yield item;
    }
  }
}

 
The implementation uses a generator (identified by the * suffix) method which is recursively called if we have an array within an array Two tests below are run in Karma to test it out.
 
    it('can flatten multiple arrays into a single array', () => {
    let oneArray = [1, 2, [3, 3]];
    let anotherArray = [4, [4, 5], 6];
    let thirdArray = [7, 7, [7, 7]];
    let threeArrays = [oneArray, anotherArray, thirdArray];
    let flattenedArrays = oneArray.Flatten([anotherArray, thirdArray], Infinity);
    let isEqualInContentToExpectedFlattenedArray = flattenedArrays.SequenceEqual([1, 2, 3, 3, 4, 4, 5, 6, 7, 7, 7, 7]);
    expect(isEqualInContentToExpectedFlattenedArray).toBe(true);
  });

  it('can flatten one deep array into a single array', () => {
    let oneArray = [1, 2, [3, 3]];
    let flattenedArrays = oneArray.Flatten(null, 1);
    let isEqualInContentToExpectedFlattenedArray = flattenedArrays.SequenceEqual([1, 2, 3, 3]);
    expect(isEqualInContentToExpectedFlattenedArray).toBe(true);
  }); 
 

Share this article on LinkedIn.

1 comment:

  1. Good post and I always like this type of information, Because it is helpful for me Because I am an app developer. this type of coding can be useful for me. But now I want to learn digital marketing and outreach. But for this, We have to need a good expert in this and it will be great for me. Because now it's my starting and in starting, We face the most problem. I hope so, You will focus on my instructions. But at this time, Most people ask me about blockchain engineers in ukraine. But I haven't time to explain it. So in this post, I am trying to clear all questions about this. After reading this post, Most doubts will be clear. In Ukraine, Most blockchain engineers company available and We should check all company reviews. After that, We should think to use this. Because its very good platform for hiring

    ReplyDelete