Ramda.js transducer:对结果数组进行平均
Ramda.js transducers: average the resulting array of numbers
我目前正在使用 Ramda.js 学习换能器。 (太有趣了,耶!)
我发现 描述了如何首先过滤数组,然后使用换能器对其中的值求和。
我想做一些类似但不同的事情。我有一组具有时间戳的对象,我想对时间戳进行平均。像这样:
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.
当然 average
不能像上面那样工作,因为 transform 函数像 reduce 一样工作。
我发现我可以在换能器之后一步完成。
const timestamps = R.transduce(transducer, R.flip(R.append), [], checkins);
average(timestamps);
但是,我认为必须有一种方法可以使用迭代器函数(转换器的第二个参数)来做到这一点。你怎么能做到这一点?或者 average
必须是 transducer
的一部分(使用 compose
)?
恐怕这让我很困惑。
我认为 transducers 是一种将组合函数的步骤组合到值序列上的方法,这样您就可以只迭代该序列一次。
average
在这里没有意义。要取平均值,您需要整个系列。
因此您可以转换值的过滤和映射。但是您绝对需要单独进行平均。请注意 filter
然后 map
是一种足够常见的模式,周围有很多 filterMap
函数。 Ramda 没有,但这样就可以了:
const filterMap = (f, m) => (xs) =>
xs .flatMap (x => f (x) ? [m (x)] : [])
然后会像这样使用:
filterMap (
propEq ('startStation', 'foo'),
prop ('timestamp')
) (checkins)
但是对于更复杂的转换序列,换能器当然可以满足要求。
我还建议您尽可能使用 lift
而不是 converge
。它是一个更标准的 FP 函数,适用于更抽象的数据类型。这里 const average = lift (divide) (sum, length)
可以正常工作。
第一步,您可以创建一个简单类型以允许合并平均值。这需要对要平均的项目的总和和数量进行统计 运行。
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)
有了这个,我们可以将注意力转向创建将合并平均值的转换器。
首先,一个简单的辅助函数,它允许将值转换为我们的 Avg
类型,并将 reduce 函数包装为默认为其接收的第一个值,而不需要提供初始值(a平均值不存在很好的初始值,因此我们将只使用第一个值)
const mapReduce1 = (map, reduce) =>
(acc, n) => acc == null ? map(n) : reduce(acc, map(n))
Transformer 然后只需要组合 Avg
值,然后从结果中提取结果平均值。 n.b。在转换器 运行 超过空列表的情况下,结果需要保护 null
值。
const avgXf = {
'@@transducer/step': mapReduce1(Avg.of, Avg.append),
'@@transducer/result': result =>
result == null ? null : result.sum / result.count
}
然后您可以将其作为累加器函数传递给 transduce
,它应该会产生结果平均值。
transduce(transducer, avgXf, null, checkins)
我目前正在使用 Ramda.js 学习换能器。 (太有趣了,耶!)
我发现
我想做一些类似但不同的事情。我有一组具有时间戳的对象,我想对时间戳进行平均。像这样:
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.
当然 average
不能像上面那样工作,因为 transform 函数像 reduce 一样工作。
我发现我可以在换能器之后一步完成。
const timestamps = R.transduce(transducer, R.flip(R.append), [], checkins);
average(timestamps);
但是,我认为必须有一种方法可以使用迭代器函数(转换器的第二个参数)来做到这一点。你怎么能做到这一点?或者 average
必须是 transducer
的一部分(使用 compose
)?
恐怕这让我很困惑。
我认为 transducers 是一种将组合函数的步骤组合到值序列上的方法,这样您就可以只迭代该序列一次。
average
在这里没有意义。要取平均值,您需要整个系列。
因此您可以转换值的过滤和映射。但是您绝对需要单独进行平均。请注意 filter
然后 map
是一种足够常见的模式,周围有很多 filterMap
函数。 Ramda 没有,但这样就可以了:
const filterMap = (f, m) => (xs) =>
xs .flatMap (x => f (x) ? [m (x)] : [])
然后会像这样使用:
filterMap (
propEq ('startStation', 'foo'),
prop ('timestamp')
) (checkins)
但是对于更复杂的转换序列,换能器当然可以满足要求。
我还建议您尽可能使用 lift
而不是 converge
。它是一个更标准的 FP 函数,适用于更抽象的数据类型。这里 const average = lift (divide) (sum, length)
可以正常工作。
第一步,您可以创建一个简单类型以允许合并平均值。这需要对要平均的项目的总和和数量进行统计 运行。
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)
有了这个,我们可以将注意力转向创建将合并平均值的转换器。
首先,一个简单的辅助函数,它允许将值转换为我们的 Avg
类型,并将 reduce 函数包装为默认为其接收的第一个值,而不需要提供初始值(a平均值不存在很好的初始值,因此我们将只使用第一个值)
const mapReduce1 = (map, reduce) =>
(acc, n) => acc == null ? map(n) : reduce(acc, map(n))
Transformer 然后只需要组合 Avg
值,然后从结果中提取结果平均值。 n.b。在转换器 运行 超过空列表的情况下,结果需要保护 null
值。
const avgXf = {
'@@transducer/step': mapReduce1(Avg.of, Avg.append),
'@@transducer/result': result =>
result == null ? null : result.sum / result.count
}
然后您可以将其作为累加器函数传递给 transduce
,它应该会产生结果平均值。
transduce(transducer, avgXf, null, checkins)