Actualmente estoy aprendiendo sobre transductores con Ramda.js. (¡Qué divertido, sí! 🎉)
Encontré esta pregunta que describe cómo filtrar primero una matriz y luego resumir los valores usando un transductor.
Quiero hacer algo similar, pero diferente. Tengo una matriz de objetos que tienen una marca de tiempo y quiero promediar las marcas de tiempo. Algo como esto:
const createCheckin = ({ timestamp = Date.now(), // default is now startStation = 'foo', endStation = 'bar' } = {}) => ({timestamp, startStation, endStation}); const checkins = [ createCheckin(), createCheckin({ startStation: 'baz' }), createCheckin({ timestamp: Date.now() + 100 }), // offset of 100 ]; const filterCheckins = R.filter(({ startStation }) => startStation === 'foo'); const mapTimestamps = R.map(({ timestamp }) => timestamp); const transducer = R.compose( filterCheckins, mapTimestamps, ); const average = R.converge(R.divide, [R.sum, R.length]); R.transduce(transducer, average, 0, checkins); // Should return something like Date.now() + 50, giving the 100 offset at the top. Por supuesto, el average tal como está arriba no puede funcionar porque la función de transformación funciona como una reducción.
Descubrí que puedo hacerlo en un paso después del transductor.
const timestamps = R.transduce(transducer, R.flip(R.append), [], checkins); average(timestamps); Sin embargo, creo que debe haber una manera de hacer esto con la función de iterador (segundo argumento del transductor). ¿Cómo pudiste lograr esto? ¿O tal vez average tiene que ser parte del transducer (usando compose )?
Como primer paso, puede crear un tipo simple para permitir que se combinen los promedios. Esto requiere llevar un registro continuo de la suma y el número de elementos que se promedian.
const Avg = (sum, count) => ({ sum, count }) // creates a new `Avg` from a given value, initilised with a count of 1 Avg.of = n => Avg(n, 1) // takes two `Avg` types and combines them together Avg.append = (avg1, avg2) => Avg(avg1.sum + avg2.sum, avg1.count + avg2.count)Con esto, podemos centrar nuestra atención en crear el transformador que combinará los valores promedio.
Primero, una función de ayuda simple que permite que los valores se conviertan a nuestro tipo Avg y también envuelve una función de reducción al valor predeterminado que recibe en lugar de requerir que se proporcione un valor inicial (no existe un buen valor inicial para los promedios , así que solo usaremos el primero de los valores en su lugar)
const mapReduce1 = (map, reduce) => (acc, n) => acc == null ? map(n) : reduce(acc, map(n)) El transformador solo necesita Avg los valores promedio y luego extraer el promedio resultante del resultado. Nota: el resultado debe protegerse de valores null en el caso de que el transformador se ejecute sobre una lista vacía.
const avgXf = { '@@transducer/step': mapReduce1(Avg.of, Avg.append), '@@transducer/result': result => result == null ? null : result.sum / result.count } Luego puede pasar esto como la función del acumulador para transduce , lo que debería producir el valor promedio resultante.
transduce(transducer, avgXf, null, checkins)Me temo que esto me parece bastante confuso.
Pienso en los transductores como una forma de combinar los pasos de una función compuesta en secuencias de valores para que pueda iterar la secuencia solo una vez.
average no tiene sentido aquí. Para sacar un promedio necesitas toda la colección.
Para que pueda transducir el filtrado y el mapeo de los valores. Pero absolutamente necesitarás hacer el promedio por separado. Tenga en cuenta que filter y luego map es un patrón lo suficientemente común como para que haya muchas funciones de filterMap . Ramda no tiene uno, pero esto funcionaría bien:
const filterMap = (f, m) => (xs) => xs .flatMap (x => f (x) ? [m (x)] : [])que luego se usaría así:
filterMap ( propEq ('startStation', 'foo'), prop ('timestamp') ) (checkins)Pero para secuencias de transformaciones más complejas, los transductores ciertamente pueden cumplir los requisitos.
También sugeriría que, cuando pueda, use lift en lugar de converge . Es una función FP más estándar y funciona en un tipo de datos más abstracto. Aquí const average = lift (divide) (sum, length) funcionaría bien.