LINQ to JavaScript para os mais pequenos

Foi mais um dia de auto-isolamento e eu estava fazendo um daqueles projetos para mim que abandonamos alguns dias depois de começarmos. Você sabe, o projeto que o tornará famoso permitirá que você aprenda uma nova linguagem de programação, um novo framework e tudo mais. Em geral, era o dia mais comum, a pandemia mais comum. Nada pressagiava problemas até que uma biblioteca que eu costumava trabalhar com matrizes caiu com estouro de pilha ... E então tudo começou a borbulhar.



No geral, entendo perfeitamente que era possível usar algo pronto, mas não é tão interessante.



Por que LINQ?



Resumidamente para aqueles que não sabem:



LINQ (Language-Integrated Query) é uma linguagem simples e conveniente para consultar uma fonte de dados. Um objeto que implementa a interface IEnumerable (por exemplo, coleções padrão, arrays), um DataSet, um documento XML pode atuar como uma fonte de dados.

E o LINQ permite que você faça isso:



string[] teams = { "", "", " ", " ", "", "" };

var selectedTeams = teams.Where(t=>t.ToUpper().StartsWith("")).OrderBy(t => t);


Das vantagens especiais, gostaria de observar:



  • Computação preguiçosa
  • Otimizando consultas
  • Sistema conveniente de métodos de extensão


Eu não sei sobre você, mas para mim, pessoalmente, serviu como um argumento convincente para começar a trabalhar.



Começando



Eu gostaria de imediatamente, com um sabre careca, entrar no mundo dos negócios, mas não vamos fazer isso - geralmente somos pessoas sérias que escrevem coisas sérias.



Portanto, iremos definir alguns requisitos para nós, além do que está indicado nas vantagens do LINQ:



  • O código deve ser facilmente extensível
  • O código deve ser rápido


Benchmark.js. Lodash, , .



, , , , npm-.



, , , .

:



  • Where
  • Sort
  • Min


, -, :



  • Where , .
  • Sort , .
  • Min .






:







.



, :





, , , .



export class Collection<T> implements ICollection<T> {
    protected inner: Collection<T>;
    private _computed: T[] | null = null; //          ,     ""

    public where(condition: FilterCondition<T>): ICollection<T> {
        return new FilteringCollection<T>(this, condition);
    }

    public sort(condition?: CompareCondition<T> | undefined): ICollection<T> {
        return new SortingCollection<T>(this, {
            compare: condition
        })
    }

    public min(predicate?: CompareCondition<T> | undefined): T {
        return new MinAggregator(this, predicate).aggregate();
    }

    public toArray(): T[] {
        return this.computed;
    }

    public [Symbol.iterator](): IterableIterator<T> {
        return this.getIterator();
    }

    public getIterator(): IterableIterator<T> {
        return this.computed[Symbol.iterator](); //  ,       for - of
    }

    private get computed(): T[] { //  :      
        if (this._computed == null) {
            const result = this.materialize();

            Object.freeze(result);

            this._computed = result;
        }
        return this._computed
    }

    protected materialize(): T[] { //   
        return this.inner.toArray();
    }
}


" ?" — , : " ".



:



const collection = new Collection([6, 5, 4, 3, 2, 1]);

const result = collection.where(item => item % 2).sort(); // [1, 3, 5]


, :



        const collection = new Collection([6, 5, 4, 3, 2, 1]);
/* 1) */const filtered = collection.where(item => item % 2);
/* 2) */const sorted = filtered.sort();


1) where Collection, inner.

2) sort FilteringCollection, inner.



, , :



1) materialize FilteringCollection [5, 3, 1].

2) materialize SortingCollection [1, 3, 5].



Where





export class FilteringCollection<T> extends Collection<T> {
    public constructor(iterable: Collection<T> | T[], private condition: FilterCondition<T>) {
        super(iterable);
    }

    public where(condition: FilterCondition<T>): ICollection<T> { //   where

        const result = new FilteringCollection<T>(this.inner, item => condition(item) && that.condition(item));

        return result;
    }

    protected materialize(): T[] { //   
        return this.inner.toArray().filter(this.condition);
    }
}




. .

, where(). , ?



, where:





_(cats).where(cat => cat.age < 3).where(cat => cat.age > 1).toArray()




_(cats).where(cat => cat.age < 3 && (function(item){
    return item.age > 1;
}(cat))).toArray()


:



public where(condition: FilterCondition<T>): ICollection<T> { //   where
        const result = new FilteringCollection<T>(this.inner, item => condition(item) && this.condition(item)); // <-- 

        return result;
    }


"".



materialize filter. . , .



----------------------------------------------------
Filter for 1000000:

Where x 104 ops/sec ±14.73% (61 runs sampled)
Lodash filter x 609 ops/sec ±0.67% (88 runs sampled)
Native filter x 537 ops/sec ±1.69% (85 runs sampled)

Double where x 102 ops/sec ±11.51% (64 runs sampled)
Double lodash filter x 368 ops/sec ±1.00% (88 runs sampled)
Double native filter x 336 ops/sec ±1.08% (84 runs sampled)

10 where x 66.60 ops/sec ±9.15% (59 runs sampled)
10 lodash filter x 99.44 ops/sec ±1.20% (73 runs sampled)
10 native filter x 81.80 ops/sec ±1.33% (70 runs sampled)
----------------------------------------------------
Filter for 1000:

Where x 24,296 ops/sec ±0.90% (88 runs sampled)
Lodash filter x 60,927 ops/sec ±0.90% (89 runs sampled)
Native filter x 204,522 ops/sec ±6.76% (87 runs sampled)

Double where x 20,281 ops/sec ±0.86% (90 runs sampled)
Double lodash filter x 37,553 ops/sec ±0.97% (90 runs sampled)
Double native filter x 115,652 ops/sec ±6.12% (91 runs sampled)

10 where x 9,559 ops/sec ±1.09% (87 runs sampled)
10 lodash filter x 8,850 ops/sec ±0.80% (87 runs sampled)
10 native filter x 22,507 ops/sec ±9.22% (84 runs sampled)
----------------------------------------------------
Filter for 10:

Where x 1,788,009 ops/sec ±0.81% (87 runs sampled)
Lodash filter x 720,558 ops/sec ±0.80% (84 runs sampled)
Native filter x 14,917,151 ops/sec ±0.61% (85 runs sampled)

Double where x 1,257,163 ops/sec ±0.52% (95 runs sampled)
Double lodash filter x 456,365 ops/sec ±0.74% (76 runs sampled)
Double native filter x 8,262,940 ops/sec ±0.64% (90 runs sampled)

10 where x 489,733 ops/sec ±0.67% (94 runs sampled)
10 lodash filter x 135,275 ops/sec ±0.61% (94 runs sampled)
10 native filter x 1,350,316 ops/sec ±0.94% (90 runs sampled)
----------------------------------------------------


: , .

select ( ).



Sort





export class SortingCollection<T, V = T> extends Collection<T> implements ISortingCollection<T> {
    private sortSettings: SortSettings<T, V>[];

    public constructor(iterable: Collection<T>, ...sortSettings: SortSettings<T, V>[]) {
        super(iterable);
        this.sortSettings = _(sortSettings)
        .where(item => !!item.compare || !!item.mapping) //        
        .toArray();
    }

    protected materialize(): T[] {
        const comparer = new Comparer(this.sortSettings, this.defaultCompare);

        return  Array.from(this.inner.toArray()).sort(this.sortSettings.length ? (first, second) => comparer.compare(first, second) : undefined);
    }

    private defaultCompare(first: V, second: V): number {
        if(first < second) {
            return -1
        } else if (second < first) {
            return 1
        } else {
            return 0;
        }
    }
}




. . Comparer.



Lodash, js .





----------------------------------------------------
Sort for 1000000:

Sort x 0.80 ops/sec ±3.59% (6 runs sampled)
Lodash sort x 0.97 ops/sec ±27.98% (7 runs sampled)
Native sort x 1.05 ops/sec ±14.71% (7 runs sampled)

SortBy x 0.19 ops/sec ±10.31% (5 runs sampled)
Lodash SortBy x 0.37 ops/sec ±7.21% (5 runs sampled)
Sort after map x 0.47 ops/sec ±8.67% (6 runs sampled)
----------------------------------------------------
Sort for 1000:

Sort x 1,121 ops/sec ±0.77% (85 runs sampled)
Lodash sort x 1,267 ops/sec ±0.77% (89 runs sampled)
Native sort x 1,274 ops/sec ±0.88% (86 runs sampled)

SortBy x 488 ops/sec ±1.45% (80 runs sampled)
Lodash SortBy x 549 ops/sec ±9.60% (70 runs sampled)
Sort after map x 954 ops/sec ±1.50% (83 runs sampled)
----------------------------------------------------
Sort for 10:

Sort x 171,700 ops/sec ±1.38% (85 runs sampled)
Lodash sort x 196,364 ops/sec ±2.01% (80 runs sampled)
Native sort x 250,820 ops/sec ±0.96% (85 runs sampled)

SortBy x 114,064 ops/sec ±0.90% (86 runs sampled)
Lodash SortBy x 86,370 ops/sec ±17.93% (67 runs sampled)
Sort after map x 221,034 ops/sec ±1.31% (87 runs sampled)
----------------------------------------------------


:

± .



Min



Implementando o agregador de pesquisa de elemento mínimo



export class MinAggregator<T> extends ReduceAggregator<T> {
    public constructor(collection: ICollection<T>, condition?: CompareCondition<T>) {
        super(collection, condition ? (first, second) => this.compare(first, second, condition) : (a, b) => a < b ? a : b)
    }

    private compare(first: T, second: T, func: CompareCondition<T>): T {
        return func(first, second) < 0 ? first : second;
    }
}

export class ReduceAggregator<T> extends Aggregator<T> {
    public constructor(private collection: ICollection<T>, protected predicate: ReduceCondition<T>){
        super();
    }

    public aggregate(): T {
        return this.collection.toArray().reduce((first, second) => this.predicate(first, second))
    }
}


Eles não esperavam? E não haverá decorador.



Em vez disso, teremos um agregador.



Explicação



Não tenho certeza se precisamos disso, apenas fazemos uma convolução.



Vamos falar sobre performance
----------------------------------------------------
Aggregate for 1000000:
Min x 43.69 ops/sec ±28.48% (65 runs sampled)
Lodash Min x 117 ops/sec ±0.58% (73 runs sampled)
----------------------------------------------------
Aggregate for 1000:
Min x 61,220 ops/sec ±5.10% (87 runs sampled)
Lodash Min x 111,452 ops/sec ±0.72% (90 runs sampled)
----------------------------------------------------
Aggregate for 10:
Min x 3,502,264 ops/sec ±1.36% (86 runs sampled)
Lodash Min x 4,181,189 ops/sec ±1.48% (86 runs sampled)
----------------------------------------------------
Aggregate By for 1000000 disp 50:
Min By x 23.81 ops/sec ±2.02% (42 runs sampled)
Lodash Min By x 42.66 ops/sec ±2.46% (55 runs sampled)
----------------------------------------------------
Aggregate By for 1000 disp 50:
Min By x 43,064 ops/sec ±0.71% (86 runs sampled)
Lodash Min By x 60,212 ops/sec ±0.89% (87 runs sampled)
----------------------------------------------------
Aggregate By for 100 disp 50:
Min By x 351,098 ops/sec ±1.03% (81 runs sampled)
Lodash Min By x 382,302 ops/sec ±1.39% (76 runs sampled)
----------------------------------------------------


Lodash venceu.



Resultado



Eu tive um tempo divertido e interessante.

Na minha opinião, acabou por ser uma boa biblioteca que gostaria de desenvolver.



Se alguém souber como reduzir o custo de iteração para filtragem e mapeamento, a saber:

this.inner.toArray().filter(this.condition);




Só vou descobrir a resposta!

Estou aberto a críticas, quero tornar o código melhor.



Obrigado a todos pela atenção.



Repositório

Pacote Npm




All Articles