Era otro día de autoaislamiento y estaba haciendo uno de esos proyectos para mí que abandonamos un par de días después de comenzar. Ya sabes, el proyecto que te hará famoso te permitirá aprender un nuevo lenguaje de programación, un nuevo framework y todo eso. En general, fue el día más común, la pandemia más común. Nada presagiaba problemas, hasta que una biblioteca con la que solía trabajar con matrices cayó con desbordamiento de pila ... Y ahí fue cuando todo comenzó a burbujear.
En general, entiendo perfectamente que era posible usar algo ya hecho, pero no es tan interesante.
¿Por qué LINQ?
Brevemente para aquellos que no están al tanto:
LINQ (Language-Integrated Query) es un lenguaje simple y conveniente para consultar una fuente de datos. Un objeto que implementa la interfaz IEnumerable (por ejemplo, colecciones estándar, matrices), un DataSet, un documento XML puede actuar como fuente de datos.
Y LINQ te permite hacer esto:
string[] teams = { "", "", " ", " ", "", "" };
var selectedTeams = teams.Where(t=>t.ToUpper().StartsWith("")).OrderBy(t => t);
De las ventajas especiales, me gustaría señalar:
- Computación perezosa
- Optimización de consultas
- Conveniente sistema de métodos de extensión
No sé ustedes, pero para mí personalmente sirvió como un argumento convincente para poner manos a la obra.
Empezando
Me gustaría inmediatamente, con una calva de sable, apresurarme a los negocios, pero no lo haremos, generalmente somos personas serias que escriben cosas serias.
Por ello, nos fijaremos unos requisitos, además de lo indicado en las ventajas de LINQ:
- El código debe ser fácilmente extensible
- El código debe 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
Implementación del agregador de búsqueda de elementos mínimos
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))
}
}
¿No esperaban? Y no habrá decorador.
En cambio, tendremos un agregador.
Explicación
No estoy seguro de si lo necesitamos en absoluto, solo hacemos una convolución.
----------------------------------------------------
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 ganó.
Salir
Pasé un tiempo divertido e interesante.
En mi opinión, resultó ser una buena biblioteca que me gustaría desarrollar.
Si alguien sabe cómo reducir el costo de iteración para el filtrado y el mapeo, es aquí:
this.inner.toArray().filter(this.condition);
¡Descubriré la respuesta!
Estoy abierto a las críticas, quiero mejorar el código.
Gracias a todos por su atención.