Kevin O'Connor
Kevin O'Connor

Reputation: 33

multi level groupby with function on a property

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

Answers (3)

customcommander
customcommander

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

Scott Sauyet
Scott Sauyet

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

quirimmo
quirimmo

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

Related Questions