Cómo encadenar el mapa y filtrar las funciones en el orden correcto.

Realmente me gusta encadenar Array.prototype.map , filter y reduce para definir una transformación de datos. Desafortunadamente, en un proyecto reciente que involucró archivos de registro grandes, ya no pude seguir repasando mis datos varias veces …

Mi meta:

Quiero crear una función que .filter métodos .filter y .map , en lugar de mapear una matriz de forma inmediata, componiendo una función que se desplaza sobre los datos una vez . Es decir:

 const DataTransformation = () => ({ map: fn => (/* ... */), filter: fn => (/* ... */), run: arr => (/* ... */) }); const someTransformation = DataTransformation() .map(x => x + 1) .filter(x => x > 3) .map(x => x / 2); // returns [ 2, 2.5 ] without creating [ 2, 3, 4, 5] and [4, 5] in between const myData = someTransformation.run([ 1, 2, 3, 4]); 

Mi bash:

Inspirado por esta respuesta y este blogpost comencé a escribir una función Transduce .

 const filterer = pred => reducer => (acc, x) => pred(x) ? reducer(acc, x) : acc; const mapper = map => reducer => (acc, x) => reducer(acc, map(x)); const Transduce = (reducer = (acc, x) => (acc.push(x), acc)) => ({ map: map => Transduce(mapper(map)(reducer)), filter: pred => Transduce(filterer(pred)(reducer)), run: arr => arr.reduce(reducer, []) }); 

El problema:

El problema con el fragmento de Transduce anterior es que se ejecuta “hacia atrás” … El último método que encadeno es el primero en ejecutarse:

 const someTransformation = Transduce() .map(x => x + 1) .filter(x => x > 3) .map(x => x / 2); // Instead of [ 2, 2.5 ] this returns [] // starts with (x / 2) -> [0.5, 1, 1.5, 2] // then filters (x  [] const myData = someTransformation.run([ 1, 2, 3, 4]); 

O, en términos más abstractos:

Ir desde:

 Transducer(concat).map(f).map(g) == (acc, x) => concat(acc, f(g(x))) 

A:

 Transducer(concat).map(f).map(g) == (acc, x) => concat(acc, g(f(x))) 

Que es similar a:

 mapper(f) (mapper(g) (concat)) 

Creo que entiendo por qué sucede, pero no puedo entender cómo solucionarlo sin cambiar la “interfaz” de mi función.

La pregunta:

¿Cómo puedo hacer que mi cadena de método Transduce filter y realice las operaciones del map en el orden correcto?


Notas:

  • Solo estoy aprendiendo sobre el nombre de algunas de las cosas que estoy tratando de hacer. Por favor, avíseme si he usado incorrectamente el término Transduce o si hay mejores maneras de describir el problema.
  • Soy consciente de que puedo hacer lo mismo usando un bucle nested for :
 const push = (acc, x) => (acc.push(x), acc); const ActionChain = (actions = []) => { const run = arr => arr.reduce((acc, x) => { for (let i = 0, action; i  fn => ActionChain(push(actions, { type, fn })); return { map: addAction("MAP"), filter: addAction("FILTER"), run }; }; // Compare to regular chain to check if // there's a performance gain // Admittedly, in this example, it's quite small... const naiveApproach = { run: arr => arr .map(x => x + 3) .filter(x => x % 3 === 0) .map(x => x / 3) .filter(x => x  x + 3) .filter(x => x % 3 === 0) .map(x => x / 3) .filter(x => x  i); console.time("naive"); const result1 = naiveApproach.run(testData); console.timeEnd("naive"); console.time("chain"); const result2 = actionChain.run(testData); console.timeEnd("chain"); console.log("equal:", JSON.stringify(result1) === JSON.stringify(result2)); 

  • Aquí está mi bash en un fragmento de stack:
 const filterer = pred => reducer => (acc, x) => pred(x) ? reducer(acc, x) : acc; const mapper = map => reducer => (acc, x) => reducer(acc, map(x)); const Transduce = (reducer = (acc, x) => (acc.push(x), acc)) => ({ map: map => Transduce(mapper(map)(reducer)), filter: pred => Transduce(filterer(pred)(reducer)), run: arr => arr.reduce(reducer, []) }); const sameDataTransformation = Transduce() .map(x => x + 5) .filter(x => x % 2 === 0) .map(x => x / 2) .filter(x => x < 4); // It's backwards: // [-1, 0, 1, 2, 3] // [-0.5, 0, 0.5, 1, 1.5] // [0] // [5] console.log(sameDataTransformation.run([-1, 0, 1, 2, 3, 4, 5])); 

    2 Solutions collect form web for “Cómo encadenar el mapa y filtrar las funciones en el orden correcto.”

    antes de que sepamos mejor

    Me gusta mucho el encadenamiento …

    Veo eso y lo tranquilizaré, pero llegará a comprender que forzar su progtwig a través de una API de encadenamiento no es natural, y supone más problemas de los que vale la pena en la mayoría de los casos.

     const Transduce = (reducer = (acc, x) => (acc.push(x), acc)) => ({ map: map => Transduce(mapper(map)(reducer)), filter: pred => Transduce(filterer(pred)(reducer)), run: arr => arr.reduce(reducer, []) }); 

    Creo que entiendo por qué sucede, pero no puedo entender cómo solucionarlo sin cambiar la “interfaz” de mi función.

    El problema es de hecho con su constructor Transduce . Su map y filter métodos de filter son astackr map y pred en el exterior de la cadena del transductor, en lugar de anidarlos en el interior.

    A continuación, he implementado su API Transduce que evalúa los mapas y filtros en el orden correcto. También he agregado un método de log para que podamos ver cómo se está comportando Transduce

     const Transduce = (f = k => k) => ({ map: g => Transduce(k => f ((acc, x) => k(acc, g(x)))), filter: g => Transduce(k => f ((acc, x) => g(x) ? k(acc, x) : acc)), log: s => Transduce(k => f ((acc, x) => (console.log(s, x), k(acc, x)))), run: xs => xs.reduce(f((acc, x) => acc.concat(x)), []) }) const foo = nums => { return Transduce() .log('greater than 2?') .filter(x => x > 2) .log('\tsquare:') .map(x => x * x) .log('\t\tless than 30?') .filter(x => x < 30) .log('\t\t\tpass') .run(nums) } // keep square(n), forall n of nums // where n > 2 // where square(n) < 30 console.log(foo([1,2,3,4,5,6,7])) // => [ 9, 16, 25 ] 

    Creo que necesitas cambiar el orden de tus implementaciones:

     const filterer = pred => reducer => (x) =>pred((a=reducer(x) )?x: undefined; const mapper = map => reducer => (x) => map(reducer(x)); 

    Entonces necesitas cambiar el comando de ejecución a:

     run: arr => arr.reduce((a,b)=>a.concat([reducer(b)]), []); 

    Y el reductor por defecto debe ser

     x=>x 

    Sin embargo, de esta manera el filtro no funcionará. Puede lanzar indefinido en la función de filtro y capturar en la función de ejecución:

     run: arr => arr.reduce((a,b)=>{ try{ a.push(reducer(b)); }catch(e){} return a; }, []); const filterer = pred => reducer => (x) =>{ if(!pred((a=reducer(x))){ throw undefined; } return x; }; 

    Sin embargo, en general, creo que el bucle for es mucho más elegante en este caso …

    Javascript tiene muchos buenos JS marco (como Node.js AngularJS Vue.js React.js) es el mejor lenguaje de script.