Javascript GroupBy并以无点样式减少对象数组

Javascript GroupBy并以无点样式减少对象数组,javascript,reduce,ramda.js,Javascript,Reduce,Ramda.js,我最近开始使用Ramda,并试图找到一种无点方法来编写一个方法来减少对象数组 以下是对象的数组: const someObj = [ { name: 'A', city: 1, other: { playtime: 30 } }, { name: 'B', city: 2, other: { playtime: 2

我最近开始使用
Ramda
,并试图找到一种无点方法来编写一个方法来减少对象数组

以下是对象的数组:

const someObj = [
    {
        name: 'A',
        city: 1,
        other: {
            playtime: 30
        }
    },
    {
        name: 'B',
        city: 2,
        other: {
            playtime: 20
        }
    },
    {
        name: 'c',
        city: 1,
        other: {
            playtime: 20
        }
    }
];
我试图减少在poinfree风格中使用的对象,比如

{
    '1': {
        count: 2,
        avg_play_time: 20 + 30 / count
    },
    '2': {
        count: 1,
        avg_play_time: 20 / count
    }
}

我可以使用array reduce方法来实现,但不确定如何以ramda无点风格编写相同的方法。如有任何建议,我们将不胜感激。

一种解决方案是这样做:

// 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还有另一个名为的函数,它在
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',
城市:1,
其他:{播放时间:30},
{name:'B',
城市:2,
其他:{播放时间:20}},
{name:'c',
城市:1,
其他:{游戏时间:20}
];
const city=道具(“城市”);
const playtime=路径(['other','playtime']);
const stats=applySpec({count:length,avg_play_time:mean});
const collectPlaytime=useWith(翻转(追加),[identity,playtime]);
const getPlaytimeByCity=reduceBy(collectPlaytime,[],城市);
console.log(
地图(统计,getPlaytimeByCity(someObj))
);


const{prop,path,useWith,flip,append,identity,applySpec,length,mean,reduceBy,map}=R我喜欢到目前为止给出的所有其他答案。因此,我自然想添加我自己的。;-)

这是一个使用
reduceBy
来记录计数和平均值的版本。如果你在寻找中值或其他统计数据,这是行不通的,但是给定一个计数、一个平均值和一个新值,我们可以直接计算新的计数和平均值。这允许我们只迭代数据一次,而每次迭代都要做一些算术运算

const transform=reduceBy(
({计数,平均播放时间},{其他:{播放时间}})=>({
计数:计数+1,
平均播放时间:(平均播放时间*计数+播放时间)/(计数+1)
}),
{计数:0,平均播放时间:0},
道具(“城市”)
)
const someObj=[{城市:1,名称:“A”,其他:{游戏时间:30},{城市:2,名称:“B”,其他:{游戏时间:20},{城市:1,名称:“c”,其他:{游戏时间:20}]
console.log(转换(someObj))

常数{reduceBy,prop}=ramda

我会这样写,希望有帮助

const stats=R.pipe(
R.groupBy(R.prop(‘城市’),
R.map(
R.applySpec({
计数:R.length,
平均播放时间:R.pipe(
R.map(R.path(['other','playtime']),和,
R.mean,
),
}),
),  
);
常数数据=[
{姓名:'A',城市:1,其他:{游戏时间:30}},
{姓名:'B',城市:2,其他:{游戏时间:20}},
{姓名:'c',城市:1,其他:{游戏时间:20}},
];
log('result',stats(data))

avg\u play\u time
字符串还是应该计算?应该根据条目计算。@PatrickRoberts-同意!注意你的
average
已经内置到Ramda中了。啊,很好的调用,我只是在文档中搜索
average
。同义词…应该是
平均值
,而不是
中间值
。对于这个简单的示例,它们恰好是相同的,但这是不寻常的。我认为这是一个很好的无点样式用例,请查看我的实现。谢谢,Scott!我同意,寻找一种特定的风格是不适用的,但同时它总是好的,知道如何实施它的变化。我会使用
array reduce
方法,不用走ramda路线就可以使用它。它非常干净。非常感谢。