Reputation: 4984
I try to write a function groupByMult
using Ramda that applies several groupBy functions groupBys
to an array of objects input
:
function groupByMult(groupBys, input) { ... }
It should return a nested object that has child properties of groupBys[0]
, grandchild properties of groupBys[1]
, grand grandchildren properties of groupBys[2]
and so on. The last grand child property has as value an array of objects that belong to this group path (see expected
output at the bottom).
I explain the desired behaviour with an example:
My input is an array of objects. In this example all objects have a property g1
, g2
and g3
.
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 },
];
In my example, my group functions are:
const groupBys = [
R.prop('g1'),
R.prop('g2'),
R.compose(R.type, R.prop('g3'))
];
I call groupByMult
like this
const outpout = groupByMult(groupBys, input);
And I expect output
to deeply equal expected
:
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 },
]
},
},
}
output
or respectively expected
has child properties g1a
, g1b
, etc. of groupBys[0]
, grandchild properties g2a
, g2b
, etc. of groupBys[1]
, grand grandchildren properties Boolean
, Number
or String
of groupBys[2]
, which have an array of objects that belong to this group path. For example, all objects of the array output.g1a.g2b.Boolean
look like { g1: 'g1a', g2: 'g2b', Boolean: <boolean> }
where <boolean>
represents any boolean value.
How can I implement groupByMult
to get the described behaviour?
Upvotes: 4
Views: 2192
Reputation: 50797
It looks to me that what you want to do, for your example is something like this:
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')))))
);
based on an input like this:
[
R.prop('g1'),
R.prop('g2'),
R.compose(R.type, R.prop('g3'))
]
There is no difficulty wrapping each function in the list with a groupBy
. That's just a map
call. But there is some work involved in making that increasing list of map
s for each element. I'm not sure if there's something more elegant -- or something more efficient -- than this recursive addMap
helper, but this is what I came up with:
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);
});
}());
You can see this in action on the Ramda REPL
Upvotes: 2
Reputation: 6516
This should be possible via a function that recursively maps over the resulting object values after applying R.groupBy
.
groupByMany = R.curry((fns, items) =>
R.isEmpty(fns) ? items
: R.map(groupByMany(R.tail(fns)),
R.groupBy(R.head(fns), items)));
This function will take a list of grouping functions along with a list of objects and continue calling itself recursively until there are no more functions to group by.
For example:
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);
Results in something like:
{ "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" }] } } }
Upvotes: 7
Reputation: 2570
Reduce array of functions and accumulate a mapped composition. For nested grouped object we use map(map(fn), input) to group values again:
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);
Upvotes: 1