LINQ to JavaScript pour les plus petits

C'était un autre jour d'auto-isolement, et je faisais un de ces projets pour moi-même, que nous abandonnons quelques jours après avoir commencé. Vous savez, le projet qui vous rendra célèbre vous permettra d'apprendre un nouveau langage de programmation, un nouveau framework, et tout ça. En général, c'était le jour le plus courant, la pandémie la plus courante. Rien de grave, jusqu'à ce qu'une bibliothèque avec laquelle je travaillais avec des tableaux tombe en panne avec un débordement de pile ... Et c'est là que tout a commencé à bouillonner.



En général, je comprends parfaitement qu'il était possible d'utiliser quelque chose de prêt à l'emploi, mais ce n'est pas si intéressant.



Pourquoi LINQ?



En bref pour ceux qui ne sont pas au courant:



LINQ (Language-Integrated Query) est un langage simple et pratique pour interroger une source de données. Un objet qui implémente l'interface IEnumerable (par exemple, des collections standard, des tableaux), un DataSet, un document XML peut agir comme une source de données.

Et LINQ vous permet de faire ceci:



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

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


Parmi les avantages spéciaux, je voudrais noter:



  • Calcul paresseux
  • Optimiser les requêtes
  • Système pratique de méthodes d'extension


Je ne sais pas pour vous, mais pour moi personnellement, cela a été un argument convaincant pour se mettre au travail.



Commencer



Je voudrais immédiatement, avec un sabre chauve, me précipiter dans les affaires, mais nous ne le ferons pas - nous sommes généralement des gens sérieux qui écrivent des choses sérieuses.



Par conséquent, nous allons nous fixer certaines exigences, en plus de ce qui est indiqué dans les avantages de LINQ:



  • Le code doit être facilement extensible
  • Le code doit être rapide


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



Implémentation de l'agrégateur de recherche d'éléments minimum



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))
    }
}


Ils ne s'attendaient pas? Et il n'y aura pas de décorateur.



Au lieu de cela, nous aurons un agrégateur.



Explication



Je ne sais pas si nous en avons besoin du tout, nous faisons juste une convolution.



Parlons 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 a gagné.



Résultat



J'ai passé un moment amusant et intéressant.

À mon avis, cela s'est avéré être une bonne bibliothèque que j'aimerais développer.



Si quelqu'un sait comment réduire le coût de l'itération pour le filtrage et le mappage, à savoir ici:

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




Je vais juste trouver la réponse!

Je suis ouvert à la critique, je veux améliorer le code.



Merci à tous pour votre attention.



Dépôt

Package Npm




All Articles