2

I have an array of objects representing products which I want to summarise the quantity value based on the value of multiple other properties. eg:

[
{
  "title": "House Coffee",
  "weight": "1kg",
  "grind": "filter",
  "quantity": 5
},
{
  "title": "House Coffee",
  "weight": "1kg",
  "grind": "filter",
  "quantity": 5
},
{
  "title": "House Coffee",
  "weight": "250g",
  "grind": "Whole Beans",
  "quantity": 4
},
{
  "title": "House Coffee",
  "weight": "250g",
  "grind": "Whole Beans",
  "quantity": 2
}
]

If all three properties of title weight and grind are the same I want to reduce the quantity so that in this case I'd end up with a new array like:

[
{
  "title": "House Coffee",
  "weight": "1kg",
  "grind": "filter",
  "quantity": 10
},
{
  "title": "House Coffee",
  "weight": "250g",
  "grind": "Whole Beans",
  "quantity": 6
},
]
Sean
  • 629
  • 8
  • 24
  • Please upvote if this is a valid question. I've asked too many dumb ones it seems – Sean Sep 05 '18 at 15:51
  • Just to check; when you say "reduce" you are referring to the reduce function of an array, to sum the values of "quantity"? – Chris Cousins Sep 05 '18 at 15:54
  • Please add code that attempts to address the problem. You will need to iterate the array and either reduce, map, or otherwise process the array. – zero298 Sep 05 '18 at 15:54
  • 3
    It's not a dumb question, it just lacks some effort from your side.. such as your attempt.. best way to learn is to try something not just get it. – Adrian Sep 05 '18 at 15:54
  • @ChrisCousins Yes. Summing the values of quantity as long as title, weight and grind are the same. Although I don't need to use that method if it's not necessary. – Sean Sep 05 '18 at 15:55
  • @zero298 I've tried for a few hours but can't crack it. Don't want to paste go-nowhere code. – Sean Sep 05 '18 at 15:56
  • @Adriani6 I've tried for a couple hours and it's not working. Don't want to paste go-nowhere code. – Sean Sep 05 '18 at 15:56
  • 1
    But you should, it's the only way we can help you. Otherwise we have no starting point and are just giving you an implementation without actually addressing the core issue, teach a man to fish and all that. – zero298 Sep 05 '18 at 15:56
  • 1
    Possible duplicate of [What is the most efficient method to groupby on a JavaScript array of objects?](https://stackoverflow.com/questions/14446511/what-is-the-most-efficient-method-to-groupby-on-a-javascript-array-of-objects) – c-smile Sep 05 '18 at 15:59
  • @c-smile I don't think it's a duplicate because I need the summary to work with multiple properties. (?) – Sean Sep 05 '18 at 16:15
  • You group your values then sum values in each group using `Array.reduce()` for example. Key point here is that you need to groupby your set first. – c-smile Sep 05 '18 at 18:17

5 Answers5

2

You need to iterate the array and compare based on what you have already processed. As you iterate, keep track of what you haven't already "reduced" with. If you already have a match based on your criteria, update the tracked item.

In your case, you need to keep a new array of each brew. As you iterate, if you already have that item in your new array (based on matching title, weight, and grind), you update the quantity. Otherwise you push the new item:

//const assert = require("assert");

const given = [{
    "title": "House Coffee",
    "weight": "1kg",
    "grind": "filter",
    "quantity": 5
  },
  {
    "title": "House Coffee",
    "weight": "1kg",
    "grind": "filter",
    "quantity": 5
  },
  {
    "title": "House Coffee",
    "weight": "250g",
    "grind": "Whole Beans",
    "quantity": 4
  },
  {
    "title": "House Coffee",
    "weight": "250g",
    "grind": "Whole Beans",
    "quantity": 2
  }
];

const expected = [{
    "title": "House Coffee",
    "weight": "1kg",
    "grind": "filter",
    "quantity": 10
  },
  {
    "title": "House Coffee",
    "weight": "250g",
    "grind": "Whole Beans",
    "quantity": 6
  }
];

function comparator(a, b) {
  return (
    a.title === b.title &&
    a.weight === b.weight &&
    a.grind === b.grind
  );
}

function doStuff(arr) {
  const reduced = [];
  arr.forEach(i => {
    const index = reduced.findIndex(e => comparator(e, i));
    if (index < 0) {
      reduced.push(Object.assign({}, i));
    } else {
      reduced[index].quantity += i.quantity;
    }
  });
  return reduced;
}

//assert.deepEqual(doStuff(given), expected);

console.log(doStuff(given));
zero298
  • 25,467
  • 10
  • 75
  • 100
2

You could try something like this:

function summarizeArray(arr)    {
    var result = [];
    for(var i = 0; i < arr.length; i++) {
        var added = false;
        for(var j = 0; j < result.length; j++)  {
            if((result[j].title == arr[i].title) && (result[j].weight == arr[i].weight) && (result[j].grind == arr[i].grind))   {
                added = true;
                result[j].quantity += arr[i].quantity;
                break;
            }
        }
        if(!added)  {
            result.push(arr[i]);
        }
    }
    return result;
}

It basically just builds a second array based on the first but searches before adding each item. If it finds a match, instead of just adding the object, it will combine the weights. http://jsfiddle.net/7hza2kxf/

Dylan
  • 21
  • 4
1

You could use a combined key of the values of the wanted properties for grouping.

var data = [{ title: "House Coffee", weight: "1kg", grind: "filter", quantity: 5 }, { title: "House Coffee", weight: "1kg", grind: "filter", quantity: 5 }, { title: "House Coffee", weight: "250g", grind: "Whole Beans", quantity: 4 }, { title: "House Coffee", weight: "250g", grind: "Whole Beans", quantity: 2 }],
    keys = ['title', 'grind', 'weight'],
    result = Array.from(
        data
            .reduce((m, o) => {
                var key = keys.map(k => o[k]).join('|');
                if (!m.has(key)) {
                    return m.set(key, Object.assign({}, o));
                }
                m.get(key).quantity += o.quantity;
                return m;
            }, new Map)
            .values()
    );

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
1

Fasted data structure would be HashMap here. It is a simple json with O(1) lookup complexity.

PsuedoCode

1. Traverse through the given array.
2. Compare the concatenated string with keys of the map.
3. If key is already present in the map, 
    - Add the quantity value.
4. If key is NOT present in the map, 
    - Add the concatenated string as new key in map. 
5. Convert map to an array

concatenated string - concatenate values of the required keys(title, weight, and grind) into a string. This string is added as key in the map, to check for duplicate values.

Code Snippet O(n)

var array = [{"title":"House Coffee","weight":"1kg","grind":"filter","quantity":5},{"title":"House Coffee","weight":"1kg","grind":"filter","quantity":5},{"title":"House Coffee","weight":"250g","grind":"Whole Beans","quantity":4},{"title":"House Coffee","weight":"250g","grind":"Whole Beans","quantity":2}];
var map = {};
var key, keys;
var summary = [];

array.forEach(function(elem) {
    key = elem.title + '-' + elem.weight +  '-' + elem.grind;

    if (map[key]) {
        map[key] += elem.quantity;
    } else {
        map[key] = elem.quantity;
    }
});

for (var key in map){
    keys = key.split('-');

    summary.push({
       "title": keys[0],
       "weight": keys[1],
       "grind": keys[2],
       "quantity": map[key]
    });
}

console.log('summary: ', summary);
nkshio
  • 1,060
  • 1
  • 14
  • 23
-1

You can try to use the filter, map and reduce functions. Take a look here https://codeburst.io/javascript-learn-to-chain-map-filter-and-reduce-acd2d0562cd4

Otherwise, you can create an empty array, and iterate over your objects array and check if the object were already added in the new array, if not, then add. If already added, then sum the new quantities, etc.

Working example:

    var array = [
        {
          "title": "House Coffee",
          "weight": "1kg",
          "grind": "filter",
          "quantity": 5
        },
        {
          "title": "House Coffee",
          "weight": "1kg",
          "grind": "filter",
          "quantity": 5
        },
        {
          "title": "House Coffee",
          "weight": "250g",
          "grind": "Whole Beans",
          "quantity": 4
        },
        {
          "title": "House Coffee",
          "weight": "250g",
          "grind": "Whole Beans",
          "quantity": 2
        }
    ];

    var result = [];

    for (var i = 0; i < array.length; i++) {
        var a = array[i];
        var foundIndex = -1;

        for (var j = 0; j < result.length && foundIndex == -1; j++) {
            var b = result[j];

            if(a['title'] == b['title'] && a['grind'] == b['grind'] && a['weight'] == b['weight']){
                foundIndex = j;
            }
        }   

        if(foundIndex == -1){
            result.push(a);
        }else{
            result[foundIndex]['quantity'] += a.quantity;
        }
    }
Pedro Simão
  • 283
  • 1
  • 11
  • I don't see why this was downvoted, it adheres to what makes a "good answer" in that it gives the OP a good direction to solve their problem. – Chris Cousins Sep 05 '18 at 16:09