最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Group and Count Uniquely on Nested Properties with RamdaLoDashUnderscore - Stack Overflow

programmeradmin0浏览0评论

How do I group and count on a nested property? Apologies if this seems like a very basic question, but I honestly am not even sure where to start.

EDIT I initially was too vague with my description above, and it's probably because my English wasn't very good. I'll try to elaborate further here.

How do I group by each product name, and then aggregate/count the number of unique nested items?

My data source:

[
  {
    product_name: 'Cool Gadget',
    offer_code: {name: '50off'}
  },
  {
    product_name: 'Cool Gadget',
    offer_code: {name: '50OFF'}
  },
  {
    product_name: 'Cool Gadget',
    offer_code: {name: '75OFF'}
  },
  {
    product_name: 'Another Cool Gadget'
  },
  {
    product_name: 'Another Cool Gadget',
    offer_code: {name: '50OFF'}
  },
  {
    product_name: 'Another Cool Gadget',
    offer_code: {name: '50OFF'}
  }
]

My preferred output:

[
  {
    product_name: 'Cool Gadget',
    count: {
      '50OFF': 2,
      '75OFF': 1
    }
  },
  {
    product_name: 'Another Cool Gadget',
    count: {
      '_default': 1,
      '50OFF': 2
    }
  }
]

How do I group and count on a nested property? Apologies if this seems like a very basic question, but I honestly am not even sure where to start.

EDIT I initially was too vague with my description above, and it's probably because my English wasn't very good. I'll try to elaborate further here.

How do I group by each product name, and then aggregate/count the number of unique nested items?

My data source:

[
  {
    product_name: 'Cool Gadget',
    offer_code: {name: '50off'}
  },
  {
    product_name: 'Cool Gadget',
    offer_code: {name: '50OFF'}
  },
  {
    product_name: 'Cool Gadget',
    offer_code: {name: '75OFF'}
  },
  {
    product_name: 'Another Cool Gadget'
  },
  {
    product_name: 'Another Cool Gadget',
    offer_code: {name: '50OFF'}
  },
  {
    product_name: 'Another Cool Gadget',
    offer_code: {name: '50OFF'}
  }
]

My preferred output:

[
  {
    product_name: 'Cool Gadget',
    count: {
      '50OFF': 2,
      '75OFF': 1
    }
  },
  {
    product_name: 'Another Cool Gadget',
    count: {
      '_default': 1,
      '50OFF': 2
    }
  }
]
Share Improve this question edited Dec 23, 2015 at 17:53 adrianmcli asked Dec 18, 2015 at 5:49 adrianmcliadrianmcli 1,9963 gold badges23 silver badges49 bronze badges 4
  • On what basis you want to sort ?? – Rajan Singh Commented Dec 18, 2015 at 5:51
  • @RajanSingh sort by product name, and then count how many offer codes there were. – adrianmcli Commented Dec 18, 2015 at 5:57
  • I see no sorting in the transformation from input to output. The answer from Scott Christopher below is great, but it involves only grouping, not sorting. I think this question was closed because the text of the question was far too broad, even though the sample output was clear enough. Perhaps if the text were edited to be more accurate and more precise, it would be reopened. – Scott Sauyet Commented Dec 22, 2015 at 14:49
  • @ScottSauyet Perhaps my english wasn't very good. I guess "group by" or "aggregate by" makes more sense. I'll make the necessary edits. – adrianmcli Commented Dec 23, 2015 at 17:51
Add a ment  | 

1 Answer 1

Reset to default 14

We can walk through one solution using Ramda.

var data = [
  {
    product_name: 'Cool Gadget',
    offer_code: {name: '50OFF'}
  },
  {
    product_name: 'Cool Gadget',
    offer_code: {name: '50OFF'}
  },
  {
    product_name: 'Cool Gadget',
    offer_code: {name: '75OFF'}
  },
  {
    product_name: 'Another Cool Gadget'
  },
  {
    product_name: 'Another Cool Gadget',
    offer_code: {name: '50OFF'}
  },
  {
    product_name: 'Another Cool Gadget',
    offer_code: {name: '50OFF'}
  }
];

We'll start off by creating a function that will group the list of products by their name.

const groupByProductName = R.groupBy(R.prop('product_name'));

groupByProductName(data);
// {"Another Cool Gadget": [{"product_name": "Another Cool Gadget"}, {"offer_code": {"name": "50OFF"}, "product_name": "Another Cool Gadget"}, {"offer_code": {"name": "50OFF"}, "product_name": "Another Cool Gadget"}], "Cool Gadget": [{"offer_code": {"name": "50OFF"}, "product_name": "Cool Gadget"}, {"offer_code": {"name": "50OFF"}, "product_name": "Cool Gadget"}, {"offer_code": {"name": "75OFF"}, "product_name": "Cool Gadget"}]}

To help count the number of distinct offer codes, we'll create a new function to group by the offer code name if present, defaulting to _default if not present.

We can use this function to map over the values in the object produced by groupByProductName.

const groupByOfferCode = R.groupBy(R.pathOr('_default', ['offer_code', 'name']));

R.map(groupByOfferCode, groupByProductName(data));
// {"Another Cool Gadget": {"50OFF": [{"offer_code": {"name": "50OFF"}, "product_name": "Another Cool Gadget"}, {"offer_code": {"name": "50OFF"}, "product_name": "Another Cool Gadget"}], "_default": [{"product_name": "Another Cool Gadget"}]}, "Cool Gadget": {"50OFF": [{"offer_code": {"name": "50OFF"}, "product_name": "Cool Gadget"}, {"offer_code": {"name": "50OFF"}, "product_name": "Cool Gadget"}], "75OFF": [{"offer_code": {"name": "75OFF"}, "product_name": "Cool Gadget"}]}}

Once we have the offer codes grouped by name, we'll create a new function to swap out the arrays of codes with just the length of each array.

const countOfferCodes = R.map(R.length);

R.map(countOfferCodes, R.map(groupByOfferCode, groupByProductName(data)));
// {"Another Cool Gadget": {"50OFF": 2, "_default": 1}, "Cool Gadget": {"50OFF": 2, "75OFF": 1}}

Once we have these functions defined, we can get something close to your desired output.

const process = products => R.map(countOfferCodes, R.map(groupByOfferCode, groupByProductName(products)));

process(data);
// {"Another Cool Gadget": {"50OFF": 2, "_default": 1}, "Cool Gadget": {"50OFF": 2, "75OFF": 1}}

Given all these functions are feeding their output directly to the input of the next, this can instead be declared using R.pipe to create a transformation pipeline.

const process = R.pipe(
  groupByProductName,
  R.map(groupByOfferCode),
  R.map(countOfferCodes)
);

You might have noticed that we have two R.map functions next to each other in the pipeline. Due to the law that says R.pipe(R.map(f), R.map(g)) must be the same as R.map(R.pipe(f, g)) we can prevent looping over the list twice by modifying our pipeline to the following.

const process = R.pipe(
  groupByProductName,
  R.map(R.pipe(
    groupByOfferCode,
    countOfferCodes
  ))
);

Now to get the output into the desired shape we can create a function that converts the object to a list, which we can add to the end of our pipeline.

const objToList = R.pipe(
  R.toPairs,
  R.map(R.zipObj(['product_name', 'count']))
);

And finally, we can add a function to the pipeline to sort by the product name. So all together:

const groupByProductName = R.groupBy(R.prop('product_name'));
const groupByOfferCode   = R.groupBy(R.pathOr('_default', ['offer_code', 'name']));
const countOfferCodes    = R.map(R.length);
const objToList = R.pipe(
  R.toPairs,
  R.map(R.zipObj(['product_name', 'count']))
);

const process = R.pipe(
  groupByProductName,
  R.map(R.pipe(
    groupByOfferCode,
    countOfferCodes
  )),
  objToList,
  R.sortBy(R.prop('product_name'))
);

process(data);
// [{"count": {"50OFF": 2, "_default": 1}, "product_name": "Another Cool Gadget"}, {"count": {"50OFF": 2, "75OFF": 1}, "product_name": "Cool Gadget"}]

And we're done.

发布评论

评论列表(0)

  1. 暂无评论