由几个groupBy函数嵌套的对象组对

时间:2016-01-11 09:15:35

标签: javascript functional-programming ramda.js

我尝试使用Ramda编写一个函数groupByMult,将多个groupBy函数groupBys应用于对象数组input

function groupByMult(groupBys, input) { ... }

它应该返回一个嵌套对象,该对象具有groupBys[0]的子属性,groupBys[1]的孙子属性,groupBys[2]的孙子属性等等。最后一个grand子属性的值为属于该组路径的对象数组(请参阅底部的expected输出)。

我用一个例子解释了所需的行为:

我的输入是一个对象数组。在此示例中,所有对象都具有属性g1g2g3

const input = [
  { g1: 'g1a', g2: 'g2a', g3: true },
  { g1: 'g1b', g2: 'g2b', g3: 42 },
  { g1: 'g1a', g2: 'g2a', g3: 'text' },
  { g1: 'g1a', g2: 'g2a', g3: false },
  { g1: 'g1a', g2: 'g2b', g3: 0 },
  { g1: 'g1a', g2: 'g2b', g3: 1 },
  { g1: 'g1a', g2: 'g2b', g3: true },
];

在我的示例中,我的群组功能是:

const groupBys = [
  R.prop('g1'),
  R.prop('g2'),
  R.compose(R.type, R.prop('g3'))
];

我这样打电话给groupByMult

const outpout = groupByMult(groupBys, input);

我希望outputexpected完全相同:

const expected = {
  g1a: {
    g2a: {
      Boolean: [
        { g1: 'g1a', g2: 'g2a', g3: true },
        { g1: 'g1a', g2: 'g2a', g3: false },
      ],
      String: [
        { g1: 'g1a', g2: 'g2a', g3: 'text' },
      ],
    },
    g2b: {
      Number: [
        { g1: 'g1a', g2: 'g2b', g3: 0 },
        { g1: 'g1a', g2: 'g2b', g3: 1 },
      ],
      Boolean: [
        { g1: 'g1a', g2: 'g2b', g3: true },
      ],
    },
  },
  g1b: {
    g2b: {
      Number: [
        { g1: 'g1b', g2: 'g2b', g3: 42 },
      ]
    },
  },
}

outputexpected分别拥有g1a,孙子属性g1b,{{1}的子属性groupBys[0]g2ag2b,等级groupBys[1]BooleanNumber String的{​​{1}}等等,其中包含属于该组的对象数组路径。例如,数组groupBys[2]的所有对象看起来都像output.g1a.g2b.Boolean,其中{ g1: 'g1a', g2: 'g2b', Boolean: <boolean> }表示任何布尔值。

如何实施<boolean>来获取描述的行为?

3 个答案:

答案 0 :(得分:6)

应该可以通过在应用R.groupBy后以递归方式映射结果对象值的函数来实现。

groupByMany = R.curry((fns, items) => 
  R.isEmpty(fns) ? items
                 : R.map(groupByMany(R.tail(fns)),
                         R.groupBy(R.head(fns), items)));

此函数将获取分组函数列表以及对象列表,并继续递归调用自身,直到没有其他函数可以分组。

例如:

input = [
  { g1: 'g1a', g2: 'g2a', g3: 'g3a' },
  { g1: 'g1b', g2: 'g2c', g3: 'g3d' },
  { g1: 'g1c', g2: 'g2a', g3: 'g3b' },
  { g1: 'g1a', g2: 'g2b', g3: 'g3a' },
  { g1: 'g1a', g2: 'g2b', g3: 'g3b' }
];

groupByMany([R.prop('g1'), R.prop('g2'), R.prop('g3')], input);

结果如下:

{ "g1a": { "g2a": { "g3a": [{ "g1": "g1a", "g2": "g2a", "g3": "g3a" }] },
           "g2b": { "g3a": [{ "g1": "g1a", "g2": "g2b", "g3": "g3a" }], 
                    "g3b": [{ "g1": "g1a", "g2": "g2b", "g3": "g3b" }] } },
  "g1b": { "g2c": { "g3d": [{ "g1": "g1b", "g2": "g2c", "g3": "g3d" }] } },
  "g1c": { "g2a": { "g3b": [{ "g1": "g1c", "g2": "g2a", "g3": "g3b" }] } } }

答案 1 :(得分:1)

减少函数数组并累积映射的合成。对于嵌套的分组对象,我们使用map(map(fn),input)再次对值进行分组:

const groupByMult = R.curry(function(fns, input) {
  const groupByMultReduced = R.reduce(function(acc, fn) {
    if (acc === null) {
      return {
        fn: R.groupBy(fn),
        map: R.map
      };
    }

    return {
      // main function
      fn: R.compose(acc.map(R.groupBy(fn)), acc.fn), 

      // accumulating helper map(map(map(...
      map: R.compose(R.map, acc.map)
    };
  }, null, fns);

  return groupByMultReduced !== null ? groupByMultReduced.fn(input) :  input;
});

let output = groupByMult(groupBys, input);

答案 2 :(得分:1)

在我看来,你想要做的是,你的例子是这样的:

const groups = R.pipe(
  R.groupBy(R.prop('g1')),
  R.map(R.groupBy(R.prop('g2'))),
  R.map(R.map(R.groupBy(R.compose(R.type, R.prop('g3')))))
);

基于这样的输入:

[
  R.prop('g1'),
  R.prop('g2'),
  R.compose(R.type, R.prop('g3'))
]

使用groupBy将每个函数包装在列表中没有任何困难。这只是map电话。但是,为每个元素制作map s的增加列表需要做一些工作。我不确定是否有比这个递归addMap助手更优雅或更高效的东西,但这就是我提出的:

const groupByMults = (() => {
  const addMap = fns => fns.length < 1 ? [] : 
      [R.head(fns)].concat(addMap(R.map(R.map, R.tail(fns))));

  return R.curry((groupers, obj) => {
    var fns = addMap(R.map(R.groupBy, groupers))
    return R.apply(R.pipe)(fns)(obj);
  });
}());

您可以在 the Ramda REPL

上看到这一点