GroupBy 并以无点样式减少对象数组
GroupBy and reduce an array of object in a pointfree style
我最近开始使用 Ramda
并试图找到一种无点方式来编写减少对象数组的方法。
这是对象数组:
const someObj = [
{
name: 'A',
city: 1,
other: {
playtime: 30
}
},
{
name: 'B',
city: 2,
other: {
playtime: 20
}
},
{
name: 'c',
city: 1,
other: {
playtime: 20
}
}
];
我正在尝试的是使用 ramda 以像
这样的 poinfree 风格来减少对象
{
'1': {
count: 2,
avg_play_time: 20 + 30 / count
},
'2': {
count: 1,
avg_play_time: 20 / count
}
}
我可以使用数组 reduce 方法来完成,但不确定如何以 ramda pointfree 样式编写相同的方法。任何建议将不胜感激。
一个解决方案是做这样的事情:
// An optic to extract the nested playtime value
// Coupled with a `lift` operation which allows it to be applied over a collection
// Effectively A -> B => A[] -> B[]
const playtimes = R.lift(R.path(['other', 'playtime']))
R.pipe(
// Group the provided array by the city value
R.groupBy(R.prop('city')),
// Return a body specification which computes each property based on the
// provided function value.
R.map(R.applySpec({
count: R.length,
average: R.pipe(playtimes, R.mean)
}))
)(someObj)
Ramda 还有另一个名为 R.reduceBy
的函数,它提供介于 reduce
和 groupBy
之间的功能,允许您将值与匹配的键折叠在一起。
因此您可以创建如下所示的数据类型来计算平均值。
const Avg = (count, val) => ({ count, val })
Avg.of = val => Avg(1, val)
Avg.concat = (a, b) => Avg(a.count + b.count, a.val + b.val)
Avg.getAverage = ({ count, val }) => val / count
Avg.empty = Avg(0, 0)
然后使用R.reduceBy
将它们组合在一起。
const avgCities = R.reduceBy(
(avg, a) => Avg.concat(avg, Avg.of(a.other.playtime)),
Avg.empty,
x => x.city
)
然后将 Avg
的平均值拉出最终对象的形状。
const buildAvg = R.applySpec({
count: x => x.count,
avg_play_time: Avg.getAverage
})
最后通过管道将两者结合起来,将 buildAvg
映射到对象中的值。
const fn = R.pipe(avgCities, R.map(buildAvg))
fn(someObj)
这是另一个使用 reduceBy
并在每个 属性 结果对象上映射一个 applySpec
函数的建议:
想法是使用getPlaytimeByCity
将someObj
转换成这个对象:
{ 1: [30, 20],
2: [20]}
然后您可以在该对象的每个 属性 上映射 stats
函数:
stats({ 1: [30, 20], 2: [20]});
// { 1: {count: 2, avg_play_time: 25},
// 2: {count: 1, avg_play_time: 20}}
const someObj = [
{ name: 'A',
city: 1,
other: { playtime: 30 }},
{ name: 'B',
city: 2,
other: { playtime: 20 }},
{ name: 'c',
city: 1,
other: { playtime: 20 }}
];
const city = prop('city');
const playtime = path(['other', 'playtime']);
const stats = applySpec({count: length, avg_play_time: mean});
const collectPlaytime = useWith(flip(append), [identity, playtime]);
const getPlaytimeByCity = reduceBy(collectPlaytime, [], city);
console.log(
map(stats, getPlaytimeByCity(someObj))
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const {prop, path, useWith, flip, append, identity, applySpec, length, mean, reduceBy, map} = R;</script>
我喜欢到目前为止给出的所有其他答案。所以很自然地我想添加我自己的。 ;-)
这是一个使用 reduceBy
来跟踪 运行 计数和平均值的版本。如果您正在寻找中值或其他一些统计数据,这将不起作用,但给定一个计数、一个平均值和一个新值,我们可以直接计算新的计数和平均值。这允许我们以每次迭代都做一些算术为代价只迭代数据一次。
const transform = reduceBy(
({count, avg_play_time}, {other: {playtime}}) => ({
count: count + 1,
avg_play_time: (avg_play_time * count + playtime) / (count + 1)
}),
{count: 0, avg_play_time: 0},
prop('city')
)
const someObj = [{city: 1, name: "A", other: {playtime: 30}}, {city: 2, name: "B", other: {playtime: 20}}, {city: 1, name: "c", other: {playtime: 20}}]
console.log(transform(someObj))
<script src="https://bundle.run/ramda@0.26.1"></script>
<script>
const {reduceBy, prop} = ramda
</script>
这不是没有意义的。虽然我是无点样式的忠实拥护者,但我只在适用时才使用它。我认为为了自己的利益而寻求它是错误的。
请注意,可以轻松修改 Scott Christopher 的答案以使用此类计算
我就这样写,希望对你有帮助!
const stats = R.pipe(
R.groupBy(R.prop('city')),
R.map(
R.applySpec({
count: R.length,
avg_play_time: R.pipe(
R.map(R.path(['other', 'playtime'])),
R.mean,
),
}),
),
);
const data = [
{ name: 'A', city: 1, other: { playtime: 30 } },
{ name: 'B', city: 2, other: { playtime: 20 } },
{ name: 'c', city: 1, other: { playtime: 20 } },
];
console.log('result', stats(data));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
我最近开始使用 Ramda
并试图找到一种无点方式来编写减少对象数组的方法。
这是对象数组:
const someObj = [
{
name: 'A',
city: 1,
other: {
playtime: 30
}
},
{
name: 'B',
city: 2,
other: {
playtime: 20
}
},
{
name: 'c',
city: 1,
other: {
playtime: 20
}
}
];
我正在尝试的是使用 ramda 以像
这样的 poinfree 风格来减少对象{
'1': {
count: 2,
avg_play_time: 20 + 30 / count
},
'2': {
count: 1,
avg_play_time: 20 / count
}
}
我可以使用数组 reduce 方法来完成,但不确定如何以 ramda pointfree 样式编写相同的方法。任何建议将不胜感激。
一个解决方案是做这样的事情:
// An optic to extract the nested playtime value
// Coupled with a `lift` operation which allows it to be applied over a collection
// Effectively A -> B => A[] -> B[]
const playtimes = R.lift(R.path(['other', 'playtime']))
R.pipe(
// Group the provided array by the city value
R.groupBy(R.prop('city')),
// Return a body specification which computes each property based on the
// provided function value.
R.map(R.applySpec({
count: R.length,
average: R.pipe(playtimes, R.mean)
}))
)(someObj)
Ramda 还有另一个名为 R.reduceBy
的函数,它提供介于 reduce
和 groupBy
之间的功能,允许您将值与匹配的键折叠在一起。
因此您可以创建如下所示的数据类型来计算平均值。
const Avg = (count, val) => ({ count, val })
Avg.of = val => Avg(1, val)
Avg.concat = (a, b) => Avg(a.count + b.count, a.val + b.val)
Avg.getAverage = ({ count, val }) => val / count
Avg.empty = Avg(0, 0)
然后使用R.reduceBy
将它们组合在一起。
const avgCities = R.reduceBy(
(avg, a) => Avg.concat(avg, Avg.of(a.other.playtime)),
Avg.empty,
x => x.city
)
然后将 Avg
的平均值拉出最终对象的形状。
const buildAvg = R.applySpec({
count: x => x.count,
avg_play_time: Avg.getAverage
})
最后通过管道将两者结合起来,将 buildAvg
映射到对象中的值。
const fn = R.pipe(avgCities, R.map(buildAvg))
fn(someObj)
这是另一个使用 reduceBy
并在每个 属性 结果对象上映射一个 applySpec
函数的建议:
想法是使用getPlaytimeByCity
将someObj
转换成这个对象:
{ 1: [30, 20],
2: [20]}
然后您可以在该对象的每个 属性 上映射 stats
函数:
stats({ 1: [30, 20], 2: [20]});
// { 1: {count: 2, avg_play_time: 25},
// 2: {count: 1, avg_play_time: 20}}
const someObj = [
{ name: 'A',
city: 1,
other: { playtime: 30 }},
{ name: 'B',
city: 2,
other: { playtime: 20 }},
{ name: 'c',
city: 1,
other: { playtime: 20 }}
];
const city = prop('city');
const playtime = path(['other', 'playtime']);
const stats = applySpec({count: length, avg_play_time: mean});
const collectPlaytime = useWith(flip(append), [identity, playtime]);
const getPlaytimeByCity = reduceBy(collectPlaytime, [], city);
console.log(
map(stats, getPlaytimeByCity(someObj))
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
<script>const {prop, path, useWith, flip, append, identity, applySpec, length, mean, reduceBy, map} = R;</script>
我喜欢到目前为止给出的所有其他答案。所以很自然地我想添加我自己的。 ;-)
这是一个使用 reduceBy
来跟踪 运行 计数和平均值的版本。如果您正在寻找中值或其他一些统计数据,这将不起作用,但给定一个计数、一个平均值和一个新值,我们可以直接计算新的计数和平均值。这允许我们以每次迭代都做一些算术为代价只迭代数据一次。
const transform = reduceBy(
({count, avg_play_time}, {other: {playtime}}) => ({
count: count + 1,
avg_play_time: (avg_play_time * count + playtime) / (count + 1)
}),
{count: 0, avg_play_time: 0},
prop('city')
)
const someObj = [{city: 1, name: "A", other: {playtime: 30}}, {city: 2, name: "B", other: {playtime: 20}}, {city: 1, name: "c", other: {playtime: 20}}]
console.log(transform(someObj))
<script src="https://bundle.run/ramda@0.26.1"></script>
<script>
const {reduceBy, prop} = ramda
</script>
这不是没有意义的。虽然我是无点样式的忠实拥护者,但我只在适用时才使用它。我认为为了自己的利益而寻求它是错误的。
请注意,可以轻松修改 Scott Christopher 的答案以使用此类计算
我就这样写,希望对你有帮助!
const stats = R.pipe(
R.groupBy(R.prop('city')),
R.map(
R.applySpec({
count: R.length,
avg_play_time: R.pipe(
R.map(R.path(['other', 'playtime'])),
R.mean,
),
}),
),
);
const data = [
{ name: 'A', city: 1, other: { playtime: 30 } },
{ name: 'B', city: 2, other: { playtime: 20 } },
{ name: 'c', city: 1, other: { playtime: 20 } },
];
console.log('result', stats(data));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>