Reputation: 33
This is similar to multi level groupby ramda js, but with a twist that is giving me trouble. In addition to a two level grouping, I'd like the inner group by to be on a processed version of the property value.
Consider data like this:
const data = [
{ top: 'top1',
name: 'junk-key-a-101' },
{ top: 'top1',
name: 'junk-key-b-102' },
{ top: 'top2',
name: 'junk-key-c-103' },
{ top: 'top2',
name: 'junk-key-c-104' } ];
I can pull out the key, process it and make it unique like so:
const getZoneFromName = n => join('-', slice(1, 3, split('-', n)));
uniq(map(getZoneFromName, pluck('name', data)));
which will get me a nice list:
["key-a", "key-b", "key-c"]
I can group the list at two levels fine:
const groupByTopThenZone = pipe(
groupBy(prop("top")),
map(groupBy(prop("name")))
);
groupByTopThenZone(data);
But I cannot figure out how to combine them to get the following output:
{
top1: {
"key-a": [
{
name: "junk-key-a-101",
top: "top1"
}
],
"key-b": [
{
name: "junk-key-b-102",
top: "top1"
}
]
},
top2: {
"key-c": [
{
name: "junk-key-c-103",
top: "top2"
},
{
name: "junk-key-c-104",
top: "top2"
}
]
}
}
I'm feeling a bit silly that I can't get this. Any ideas? Here is a place to play with it.
Upvotes: 3
Views: 231
Reputation: 18961
Another way would be to construct each final object and merge them all:
You can transform this object:
{
"top": "top1",
"name": "junk-key-a-101"
}
Into this one:
{
"top1": {
"key-a": [
{
"name": "junk-key-a-101",
"top": "top1"
}
]
}
}
With these functions:
const key = slice(5, -4);
const obj = ({top, name}) => ({
[top]: {
[key(name)]: [
{top, name}
]
}
});
So now you can iterate on your data, transform each object and merge them together:
const groupByTopTenZone = reduce(useWith(mergeDeepWith(concat), [identity, obj]), {});
Full example:
const {slice, useWith, identity, reduce, mergeDeepWith, concat} = R;
const data = [
{ top: 'top1',
name: 'junk-key-a-101' },
{ top: 'top1',
name: 'junk-key-b-102' },
{ top: 'top2',
name: 'junk-key-c-103' },
{ top: 'top2',
name: 'junk-key-c-104' }
];
const key = slice(5, -4);
const obj = ({top, name}) => ({
[top]: {
[key(name)]: [
{top, name}
]
}
});
const groupByTopTenZone = reduce(useWith(mergeDeepWith(concat), [identity, obj]), {});
console.log(
groupByTopTenZone(data)
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
Upvotes: 0
Reputation: 50807
You were very close. Just combining those functions with compose
/pipe
does the trick.
(Note here also a simplified version of getZoneFromName
.)
const {pipe, groupBy, map, prop, slice} = R
//const getZoneFromName = n => join('-', slice(1, 3, split('-', n)));
const getZoneFromName = slice(5, -4)
const groupByTopThenZone = pipe(
groupBy(prop("top")),
map(groupBy(pipe(prop("name"), getZoneFromName)))
)
const data = [{"name": "junk-key-a-101", "top": "top1"}, {"name": "junk-key-b-102", "top": "top1"}, {"name": "junk-key-c-103", "top": "top2"}, {"name": "junk-key-c-104", "top": "top2"}]
console.log(groupByTopThenZone(data))
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js"></script>
Of course with this function simplified that much, it's probably easier to inline it:
const groupByTopThenZone = pipe(
groupBy(prop("top")),
map(groupBy(pipe(prop("name"), slice(5, -4)))
)
The main thing to remember is that groupBy
is not necessarily tied with prop
. We can group on the result of any String
/Number
/Symbol
-generating function.
Upvotes: 1
Reputation: 9998
This is not using ramda, but vanilla JS.
const data = [
{ top: 'top1',
name: 'junk-key-a-101' },
{ top: 'top1',
name: 'junk-key-b-102' },
{ top: 'top2',
name: 'junk-key-c-103' },
{ top: 'top2',
name: 'junk-key-c-104' } ];
const res = data.reduce((acc, val, ind, arr) => {
const top = val.top;
// if the top does not exist in the obj, create it
if (!acc[top]) {
acc[top] = {};
}
// get the key through split. you could also use a regex here
const keyFragments = val.name.split('-');
const key = [keyFragments[1], keyFragments[2]].join('-');
// if the key obj prop does not exist yet, create the array
if (!acc[top][key]) {
acc[top][key] = [];
}
// push the value
acc[top][key].push({ name: val.name, top: val.top });
return acc;
}, {});
console.log(res);
Upvotes: 0