Empresas
Empregos
  • Sobre nós
  • Soluções
    • Publicação de vagas
      Publique sua vaga e receba candidatos qualificados em 48h.
    • Avaliações de candidatos
      Mais de 500 testes técnicos e psicológicos, mais anti-fraude.
    • Headhunting
      Busca executiva personalizada do início ao fim.
    • Folha de Pagamento + EOR
      Dispersão de folha e EOR em mais de 15 países da LATAM.
  • Preços
  • Empregos

0

151
Visualizações
Ramda.js transducers: average the resulting array of numbers

I'm currently learning about transducers with Ramda.js. (So fun, yay! 🎉)

I found this question that describes how to first filter an array and then sum up the values in it using a transducer.

I want to do something similar, but different. I have an array of objects that have a timestamp and I want to average out the timestamps. Something like this:

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.

Of course average as it stands above can't work because the transform function works like a reduce.

I found out that I can do it in a step after the transducer.

const timestamps = R.transduce(transducer,  R.flip(R.append), [], checkins);
average(timestamps);

However, I think there must be a way to do this with the iterator function (second argument of the transducer). How could you achieve this? Or maybe average has to be part of the transducer (using compose)?

about 4 years ago · Juan Pablo Isaza
2 Respostas
Responde à pergunta

0

As a first step, you can create a simple type to allow averages to be combined. This requires keeping a running tally of the sum and number of items being averaged.

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)

With this, we can turn our attention to creating the transformer that will combine the average values.

First, a simple helper function that allow values to be converted to our Avg type and also wraps a reduce function to default to the first value it receives rather than requiring an initial value to be provided (a nice initial value doesn't exist for averages, so we'll just use the first of the values instead)

const mapReduce1 = (map, reduce) =>
  (acc, n) => acc == null ? map(n) : reduce(acc, map(n))

The transformer then just needs to combine the Avg values and then pull resulting average out of the result. n.b. The result needs to guard for null values in the case where the transformer is run over an empty list.

const avgXf = {
  '@@transducer/step': mapReduce1(Avg.of, Avg.append),
  '@@transducer/result': result =>
    result == null ? null : result.sum / result.count
}

You can then pass this as the accumulator function to transduce, which should produce the resulting average value.

transduce(transducer, avgXf, null, checkins)
about 4 years ago · Juan Pablo Isaza Relatório

0

I'm afraid this strikes me as quite confused.

I think of transducers as a way of combining the steps of a composed function on sequences of values so that you can iterate the sequence only once.

average makes no sense here. To take an average you need the whole collection.

So you can transduce the filtering and mapping of the values. But you will absolutely need to then do the averaging separately. Note that filter then map is a common enough pattern that there are plenty of filterMap functions around. Ramda doesn't have one, but this would do fine:

const filterMap = (f, m) => (xs) =>
  xs .flatMap (x => f (x) ? [m (x)] : [])

which would then be used like this:

filterMap (
  propEq ('startStation', 'foo'), 
  prop ('timestamp')
) (checkins)

But for more complex sequences of transformations, transducers can certainly fit the bill.


I would also suggest that when you can, you should use lift instead of converge. It's a more standard FP function, and works on a more abstract data type. Here const average = lift (divide) (sum, length) would work fine.

about 4 years ago · Juan Pablo Isaza Relatório
Responde à pergunta
Encontrar trabalhos remotos

Descubra a nova forma de encontrar um emprego!

melhores empregos
Principais categorias de trabalho
Empresas
Postar vaga Preços Comercial
Jurídico
Termos e Condições Política de privacidade
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomende algumas ofertas para mim
Preciso de ajuda