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

Group array of objects nested by several groupBy functions

我尝试使用 Ramda that applies several groupBy 函数 groupBys 将函数 groupByMult 写入对象数组 input:

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

它应该 return 具有子属性 groupBys[0]、孙属性 groupBys[1]、孙属性 groupBys[2] 等的嵌套对象。最后一个孙子 属性 具有属于该组路径的对象数组的值(请参阅底部的 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 具有子属性 g1ag1bgroupBys[0]、孙属性 g2ag2bgroupBys[1] 的孙属性 BooleanNumbergroupBys[2]String 等,它们具有属于的对象数组这个组路径。例如,数组 output.g1a.g2b.Boolean 的所有对象看起来都像 { g1: 'g1a', g2: 'g2b', Boolean: <boolean> },其中 <boolean> 表示任何布尔值。

如何实施 groupByMult 以获得所描述的行为?

减少函数数组并累积映射组合。对于嵌套的分组对象,我们使用 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);

这应该可以通过一个函数来实现,该函数在应用 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" }] } } }

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

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 列表需要做一些工作。我不确定是否有比这个递归 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

上看到这个效果