使用ramdajs创建前5个聚合

时间:2019-05-08 09:36:20

标签: javascript functional-programming ramda.js

我想转换此输入

[
        { country: 'France', value: 100 },
        { country: 'France', value: 100 },
        { country: 'Romania', value: 500 },
        { country: 'England', value: 400 },
        { country: 'England', value: 400 },
        { country: 'Spain', value: 130 },
        { country: 'Albania', value: 4 },
        { country: 'Hungary', value: 3 }
]

进入输出

[
      { country: 'England', value: 800 },
      { country: 'Romania', value: 500 },
      { country: 'France', value: 200 },
      { country: 'Spain', value: 130 },
      { country: 'Other', value: 8 }
]

基本上,这是前四个+其他国家/地区的 值之和

我正在将JavaScript与ramdajs一起使用,而我只能在 somehow cumbersome way so far中使用它。

我正在寻找一个优雅的解决方案:那里的功能程序员可以提供他们的解决方案吗?还是对ramda方法有帮助的想法?

9 个答案:

答案 0 :(得分:4)

(每个步骤都获得上一步的输出。所有内容都将放在最后。)

步骤1:获取总和图

您可以对此进行转换:

[
  { country: 'France', value: 100 },
  { country: 'France', value: 100 },
  { country: 'Romania', value: 500 },
  { country: 'England', value: 400 },
  { country: 'England', value: 400 },
  { country: 'Spain', value: 130 },
  { country: 'Albania', value: 4 },
  { country: 'Hungary', value: 3 }
]

对此:

{
  Albania: 4,
  England: 800,
  France: 200,
  Hungary: 3,
  Romania: 500,
  Spain: 130
}

与此:

const reducer = reduceBy((sum, {value}) => sum + value, 0);
const reduceCountries = reducer(prop('country'));

步骤2:将其转换回已排序的数组

[
  { country: "Hungary", value: 3 },
  { country: "Albania", value: 4 },
  { country: "Spain", value: 130 },
  { country: "France", value: 200 },
  { country: "Romania", value: 500 },
  { country: "England", value: 800 }
]

您可以执行以下操作:

const countryFromPair = ([country, value]) => ({country, value});
pipe(toPairs, map(countryFromPair), sortBy(prop('value')));

第3步:创建两个子组,即非排名前4的国家和排名前4的国家

[
  [
    { country: "Hungary", value: 3},
    { country: "Albania", value: 4}
  ],
  [
    { country: "Spain", value: 130 },
    { country: "France", value: 200 },
    { country: "Romania", value: 500 },
    { country: "England", value: 800 }
  ]
]

您可以这样做:

splitAt(-4)

第4步:合并第一个子组

[
  [
    { country: "Others", value: 7 }
  ],
  [
    { country: "Spain", value: 130 },
    { country: "France", value: 200 },
    { country: "Romania", value: 500 },
    { country: "England", value: 800 }
  ]
]

与此:

over(lensIndex(0), compose(map(countryFromPair), toPairs, reduceOthers));

第5步:展平整个阵列

[
  { country: "Others", value: 7 },
  { country: "Spain", value: 130 },
  { country: "France", value: 200 },
  { country: "Romania", value: 500 },
  { country: "England", value: 800 }
]

使用

flatten

完整的工作示例

const data = [
  { country: 'France', value: 100 },
  { country: 'France', value: 100 },
  { country: 'Romania', value: 500 },
  { country: 'England', value: 400 },
  { country: 'England', value: 400 },
  { country: 'Spain', value: 130 },
  { country: 'Albania', value: 4 },
  { country: 'Hungary', value: 3 }
];

const reducer = reduceBy((sum, {value}) => sum + value, 0);
const reduceOthers = reducer(always('Others'));
const reduceCountries = reducer(prop('country'));
const countryFromPair = ([country, value]) => ({country, value});

const top5 = pipe(
  reduceCountries,
  toPairs,
  map(countryFromPair),
  sortBy(prop('value')),
  splitAt(-4),
  over(lensIndex(0), compose(map(countryFromPair), toPairs, reduceOthers)),
  flatten
);

top5(data)

答案 1 :(得分:3)

这是一种方法:

const combineAllBut = (n) => pipe(drop(n), pluck(1), sum, of, prepend('Others'), of)

const transform = pipe(
  groupBy(prop('country')),
  map(pluck('value')),
  map(sum),
  toPairs,
  sort(descend(nth(1))),
  lift(concat)(take(4), combineAllBut(4)),
  map(zipObj(['country', 'value']))
)

const countries = [{ country: 'France', value: 100 }, { country: 'France', value: 100 }, { country: 'Romania', value: 500 }, { country: 'England', value: 400 }, { country: 'England', value: 400 }, { country: 'Spain', value: 130 }, { country: 'Albania', value: 4 }, { country: 'Hungary', value: 3 }]

console.log(transform(countries))
<script src="https://bundle.run/ramda@0.26.1"></script>
<script>
const {pipe, groupBy, prop, map, pluck, sum, of, prepend, toPairs, sort, descend, nth, lift, concat, take, drop, zipObj} = ramda
</script>

除了一个复杂的行(lift(concat)(take(4), combineAllBut(4)))和关联的辅助函数(combineAllBut)以外,这是一组简单的转换。该辅助函数可能不在该函数之外有用,因此将其内联为lift(concat)(take(4), pipe(drop(4), pluck(1), sum, of, prepend('Others'), of))是完全可以接受的,但是我发现结果函数有点难以阅读。

请注意,该函数将返回类似[['Other', 7]]的格式,这是毫无意义的格式,因为事实是我们将concat排在前四位。因此,至少有一些理由要删除最后的of并将concat替换为flip(append)。我之所以没有这样做,是因为该辅助函数仅在该管道的上下文中没有任何意义。但是我会理解是否有人会选择其他方式。

我喜欢此功能的其余部分,它似乎很适合Ramda管道样式。但是该辅助功能在某种程度上破坏了它。我很想听听简化它的建议。

更新

然后来自customcommander的答案证明了我可以采用的简化方法,即使用reduceBy而不是上述方法中的groupBy -> map(pluck) -> map(sum)舞蹈。这肯定可以改善。

const combineAllBut = (n) => pipe(drop(n), pluck(1), sum, of, prepend('Others'), of)

const transform = pipe(
  reduceBy((a, {value}) => a + value, 0, prop('country')),
  toPairs,
  sort(descend(nth(1))),
  lift(concat)(take(4), combineAllBut(4)),
  map(zipObj(['country', 'value']))
)

const countries = [{ country: 'France', value: 100 }, { country: 'France', value: 100 }, { country: 'Romania', value: 500 }, { country: 'England', value: 400 }, { country: 'England', value: 400 }, { country: 'Spain', value: 130 }, { country: 'Albania', value: 4 }, { country: 'Hungary', value: 3 }]

console.log(transform(countries))
<script src="https://bundle.run/ramda@0.26.1"></script>
<script>
const {pipe, reduceBy, prop, map, pluck, sum, of, prepend, toPairs, sort, descend, nth, lift, concat, take, drop, zipObj} = ramda
</script>

答案 2 :(得分:2)

我尝试一下,尝试将其功能用于大多数事情。并保持单个pipe

const f = pipe(
  groupBy(prop('country')),
  map(map(prop('value'))),
  map(sum),
  toPairs(),
  sortBy(prop(1)),
  reverse(),
  addIndex(map)((val, idx) => idx<4?val:['Others',val[1]]),
  groupBy(prop(0)),
  map(map(prop(1))),
  map(sum),
  toPairs(),
  map(([a,b])=>({'country':a,'value':b}))
)

Ramda REPL


但是,我认为它不可读。

答案 3 :(得分:1)

我认为您可以通过在缩小数组之前对数组进行分割来稍微简化groupOthersKeeping(以ramda表示),如下所示:

const groupOthersKeeping = contriesToKeep => arr => [
    ...slice(0, contriesToKeep, arr),
    reduce(
      (acc, i) => ({ ...acc, value: acc.value + i.value }),
      { country: 'Others', value: 0 },
      slice(contriesToKeep, Infinity, arr)
    )
 ]

答案 4 :(得分:1)

使用更多的ramda函数,但不确定是否更好:

let country = pipe(
  groupBy(prop('country')),
  map(pluck('value')),
  map(sum)
)([
  { country: 'France', value: 100 },
  { country: 'France', value: 100 },
  { country: 'Romania', value: 500 },
  { country: 'England', value: 400 },
  { country: 'England', value: 400 },
  { country: 'Spain', value: 130 },
  { country: 'Albania', value: 4 },
  { country: 'Hungary', value: 3 }
]);

let splitCountry = pipe(
  map((k) => ({country: k, value: country[k]})),
  sortBy(prop('value')),
  reverse,
  splitAt(4)
)(keys(country));

splitCountry[0].push({country: 'Others', value: sum(map(prop('value'))(splitCountry[1]))});
splitCountry[0]

答案 5 :(得分:1)

这是我的两分钱。

const a = [
    { country: 'France', value: 100 },
    { country: 'France', value: 100 },
    { country: 'Romania', value: 500 },
    { country: 'England', value: 400 },
    { country: 'England', value: 400 },
    { country: 'Spain', value: 130 },
    { country: 'Albania', value: 4 },
    { country: 'Hungary', value: 3 }
];

const diff = (a, b) => b.value - a.value;
const addValues = (acc, {value}) => R.add(acc,value);
const count = R.reduce(addValues, 0);
const toCountry = ({country}) => country;
const toCountryObj = (x) => ({'country': x[0], 'value': x[1] });
const reduceC = R.reduceBy(addValues, [], toCountry);

const [countries, others] = R.compose(
    R.splitAt(4), 
    R.sort(diff), 
    R.chain(toCountryObj), 
    R.toPairs, 
    reduceC)(a);

const othersArray = [{ 'country': 'Others', 'value': count(others) }];

R.concat(countries, othersArray);

Ramda REPL

答案 6 :(得分:1)

我将按国家/地区分组,将每个国家/地区组合并为一个对象,同时对值求和,排序,拆分为两个数组(最高4个)和[其他],将其他国家/地区合并为单个对象,并与最高4。

const { pipe, groupBy, prop, values, map, converge, merge, head, pluck, sum, objOf, sort, descend, splitAt, concat, last, of, assoc } = R

const sumProp = key => pipe(pluck(key), sum, objOf(key))

const combineProp = key => converge(merge, [head, sumProp(key)])

const getTop5 = pipe(
  groupBy(prop('country')),
  values, // convert to array of country arrays
  map(combineProp('value')), // merge each sub array to a single object
  sort(descend(prop('value'))), // sort descebdubg by the value property
  splitAt(4), // split to two arrays [4 highest][the rest]
  converge(concat, [ // combine the highest and the others object
    head,
    // combine the rest to the others object wrapped in an array
    pipe(last, combineProp('value'), assoc('country', 'others'), of)
  ])
)

const countries = [{ country: 'France', value: 100 }, { country: 'France', value: 100 }, { country: 'Romania', value: 500 }, { country: 'England', value: 400 }, { country: 'England', value: 400 }, { country: 'Spain', value: 130 }, { country: 'Albania', value: 4 }, { country: 'Hungary', value: 3 }]

const result = getTop5(countries)

console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>

答案 7 :(得分:1)

我可能会做这样的事情:

const aggregate = R.pipe(
  R.groupBy(R.prop('country')),
  R.toPairs,
  R.map(
    R.applySpec({ 
      country: R.head, 
      value: R.pipe(R.last, R.pluck('value'), R.sum),
    }),
  ),
  R.sort(R.descend(R.prop('value'))),
  R.splitAt(4),
  R.over(
    R.lensIndex(1), 
    R.applySpec({ 
      country: R.always('Others'), 
      value: R.pipe(R.pluck('value'), R.sum),
    }),
  ),
  R.unnest,
);

const data = [
  { country: 'France', value: 100 },
  { country: 'France', value: 100 },
  { country: 'Romania', value: 500 },
  { country: 'England', value: 400 },
  { country: 'England', value: 400 },
  { country: 'Spain', value: 130 },
  { country: 'Albania', value: 4 },
  { country: 'Hungary', value: 3 }
];

console.log('result', aggregate(data));
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>

答案 8 :(得分:0)

这是两个解决方案

我认为第二个更长的时间更容易理解

函数“ mergeAllWithKeyBy”结合了“ R.mergeAll”,“ R.mergeWithKey”和“ R.groupBy”的功能。

const mergeAllWithKeyBy = R.curry((mergeFn, keyFn, objs) =>
  R.values(R.reduceBy(R.mergeWithKey(mergeFn), {}, keyFn, objs)))

const addValue = (k, l, r) => 
  k === 'value' ? l + r : r

const getTop = 
  R.pipe(
    mergeAllWithKeyBy(addValue, R.prop('country')),
    R.sort(R.descend(R.prop('value'))),
    R.splitAt(4),
    R.adjust(-1, R.map(R.assoc('country', 'Others'))),
    R.unnest,
    mergeAllWithKeyBy(addValue, R.prop('country')),
  )
  
const data = [
  { country: 'France', value: 100 },
  { country: 'France', value: 100 },
  { country: 'Romania', value: 500 },
  { country: 'England', value: 400 },
  { country: 'England', value: 400 },
  { country: 'Spain', value: 130 },
  { country: 'Albania', value: 4 },
  { country: 'Hungary', value: 3 }
]

console.log(getTop(data))
<script src="//cdn.jsdelivr.net/npm/ramda@latest/dist/ramda.min.js"></script>

const getTop = (data) => {
  const getCountryValue =
    R.prop(R.__, R.reduceBy((y, x) => y + x.value, 0, R.prop('country'), data))
    
  const countries = 
    R.uniq(R.pluck('country', data))
  
  const [topCounties, bottomCountries] = 
    R.splitAt(4, R.sort(R.descend(getCountryValue), countries))
  
  const others = {
    country: 'Others', 
    value: R.sum(R.map(getCountryValue, bottomCountries))
  }
  
  const top =
    R.map(R.applySpec({country: R.identity, value: getCountryValue}), topCounties)
  
  return R.append(others, top)
}

const data = [
  { country: 'France', value: 100 },
  { country: 'France', value: 100 },
  { country: 'Romania', value: 500 },
  { country: 'England', value: 400 },
  { country: 'England', value: 400 },
  { country: 'Spain', value: 130 },
  { country: 'Albania', value: 4 },
  { country: 'Hungary', value: 3 }
]

console.log(getTop(data))
<script src="//cdn.jsdelivr.net/npm/ramda@latest/dist/ramda.min.js"></script>