3

I have an array grouped by different categories and each category have nested more objects with different values (numeric and strings) which i have to reduce (except the strings). Reduce is working fine with numeric values making a sum of them but its overwriting the string values keeping just the last one.

I'm trying to reduce an object/dictionnary by category and fill a table with the sum of the units of each category but don't sum them if the subcategories and names are different.

Here is a demo:

var data = {
  'Category xxxx': [
    {
      units: 1234,
      subcategory: 'wolves',
      name: 'Starks'
    },
    {
      units: 1345354,
      subcategory: 'wolves',
      name: 'Starks'
    },
   {
      units: 666,
      subcategory: 'dragons',
      name: 'Targaryens'
    }
  ], 
  'Category yyyy': [
    {
      units: 7783,
      subcategory: 'lions',
      name: 'Lanisters'
    },
    {
      units: 1267878,
      subcategory: 'spires',
      name: 'Martells'
    }
  ]
}

var test = _.map(data, function (value, key) {
  var returnedData = {
    Category: key,
    units: _(value).reduce(function (memo, metrics) {
      return memo + metrics.units;
    }, 0),
    subcategory: _(value).reduce(function (memo, metrics) {
      return metrics.subcategory;
    }, 0),
    name: _(value).reduce(function (memo, metrics) {
      return metrics.name;
    }, 0),
  };
  return returnedData;
});

console.log(test)
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>

The units (integers) are adding up fine but the strings get overwritten by the last iterated string property.

I want to obtain something like this where if the strings are different the integers are not added up.

returnedData = {
  'Category xxxx': [
    {
      units: 1346588,
      chivalry: 'wolves',
      name: 'Starks'
    },
    {
      units: 666,
      subcategory: 'dragons',
      name: 'Targaryens'
    }
  ], 
  'Category yyyy': [
    {
      clicks: 7783,
      subcategory: 'lions',
      name: 'Lanisters'
    },
    {
      clicks: 1267878,
      subcategory: 'spires',
      name: 'Martells'
    }
  ]
}

What is the best way to do it?

Fraccier
  • 780
  • 3
  • 11
  • 21
  • Looks like simple `$.each` – Justinas Jun 21 '16 at 09:51
  • 5
    @Justinas: There's no [tag:jquery] tag, and even if there were, `$.each` is *so* 2008. :-) `Array#forEach` or possibly `Array#reduce` would be applicable. – T.J. Crowder Jun 21 '16 at 09:52
  • @phileras Can you provide an example of the expected results and what you tried? – Quentin Roy Jun 21 '16 at 09:54
  • yes @Justinas im trying with reduce, in this case underscore reduce but i will use javascript array.reduce if possible. – Fraccier Jun 21 '16 at 09:58
  • hi @T.J.Crowder i want the result similar to Nina answer, the array i showed here is a demo in this case the data is really big and i will have to do more subgroups but the idea and result of Nina is appropiated. After that i will need to check performance, but well this is a begging. Thanks :) – Fraccier Jun 21 '16 at 10:16

2 Answers2

5

Finally, this proposal uses an object as reference to the items of the result object.

If no result object is available, then it creates a new one with the wanted category. For subcategories it generates an element which is pushed to the result array.

The count is maintained with hash as reference to the elements of result.

var data = { 'Category xxxx': [{ clicks: 1234, subcategory: 'dogs', name: 'jhon doe' }, { clicks: 1345354, subcategory: 'dogs', name: 'jhon doe' }], 'Category yyyy': [{ clicks: 7783, subcategory: 'frogs', name: 'lanisters' }, { clicks: 1267878, subcategory: 'rats', name: 'perry' }] },
    result = {};

Object.keys(data).forEach(function (k) {
    var hash = Object.create(null);
    if (!result[k]) {
        result[k] = [];
    }
    data[k].forEach(function (a) {
        if (!hash[a.subcategory]) {
            hash[a.subcategory] = { clicks: 0, subcategory: a.subcategory, name: a.name };
            result[k].push(hash[a.subcategory]);
        }
        hash[a.subcategory].clicks += a.clicks;
    });
});

console.log(result);
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • Nice one, Nina! Though FWIW I would avoid the (perceived) complexity of using `this` as a map of category subcategories, presumably we only need to combine entries *within* an overall category. Was just modifying your original solution with an eye toward editing it in above (you'd done the bulk of the work, after all) when you did your own. FWIW, this is what it was: http://pastie.org/10885155 – T.J. Crowder Jun 21 '16 at 12:33
  • @T.J.Crowder, you are right, there is no need for category, just for sub. you solution mulate the original. – Nina Scholz Jun 21 '16 at 12:46
  • @NinaScholz: Did you mean "mutate"? Yes, it did; I figured that was desired. No need to create new objects if they don't need the old ones for anything. – T.J. Crowder Jun 21 '16 at 12:49
  • Guys im reusing @NinaScholz script and im planning to make it to recognize typeoff off property to add up if property is an integer. What do you think? i mean the script of nina is perfect but i need to work in a more generic version to accept bigger and more variated data :D – Fraccier Jun 21 '16 at 12:58
  • Hi @NinaScholz can the hash accept a object with a list of properties? – Fraccier Jun 23 '16 at 11:29
  • you mean the key? or the value of the property? the key is always a string and if not, it is converted to a string. – Nina Scholz Jun 23 '16 at 11:31
  • something like this @NinaScholz: hash = {a.subcategory, a.units....} the data im working with is much more big and complex and im looking to do it as compact as possible to handle also variables because the hash can change with some interaction of users. – Fraccier Jun 23 '16 at 11:37
  • 1
    i suggest to ask a new question. the comments is not the right place for it. – Nina Scholz Jun 23 '16 at 11:41
  • @NinaScholz Why `Object.create(null)` and not a simple `{}`? – Morteza Tourani Jun 28 '16 at 04:51
  • @phileras you can use `Map` or `WeakMap` objects if you want to use non-string as key. – Morteza Tourani Jun 28 '16 at 04:53
  • @mortezaT, because if you have the case, that `subcategory` is `toString`, then the whole mechanism does not work. the result is to generate an empty object without prototypes, or methods. – Nina Scholz Jun 28 '16 at 05:44
  • @NinaScholz I didn't get that can you explain more and maybe an example. – Morteza Tourani Jun 28 '16 at 05:47
  • @mortezaT, if you take `subcategory = 'toString'` and perform the first check, you get a false `true`, because `if (!hash[a.subcategory])`is never reached and this subcategory is assigned to the method and not to the result object. – Nina Scholz Jun 28 '16 at 05:51
  • @NinaScholz what about `a.subcategory in hash`. Sorry about questions in comment no other way to hang out. – Morteza Tourani Jun 28 '16 at 06:31
  • 1
    @mortezaT, maybe it is easier for you to look as this [answer](http://stackoverflow.com/a/38068553/1447675) of me. ther is a proerty with name `'toString'` in `arr2`. you may perfor the same just with `{}` instead of `Object.reate(null)`. you get the difference. – Nina Scholz Jun 28 '16 at 06:33
  • hi guys @mortezaT and @NinaScholz im trying to pass dinámic variables to `hash[a.dinamicVariable]` as reference to make it work with complex data. I have been trying with different sintax but its not working as expected. Any idea? – Fraccier Jul 04 '16 at 12:23
  • 1
    @phileras, so out of the blue, its no possible to say where's you problem. may it is easier for you to as a new question and highlight your special problem with *dynamic* variables (which is kind of tautology ...?). – Nina Scholz Jul 04 '16 at 12:31
1

I think this could be the answer too.

var data = {
 'Category xxxx': [
 { units: 1234, subcategory: 'wolves', name: 'Starks' }, 
 { units: 1345354, subcategory: 'wolves', name: 'Starks' }, 
 { units: 666, subcategory: 'dragons', name: 'Targaryens' }
 ],
 'Category yyyy': [
 { units: 7783, subcategory: 'lions', name: 'Lanisters' }, 
 { units: 1267878, subcategory: 'spires', name: 'Martells' }
 ]
};

var result = {};

Object.keys(data).forEach(function (key) {
 var hash = {};
 data[key].forEach(function (v, k) {
  isNaN(hash[(k = v.subcategory + v.name)]) ?
   (hash[k] = this.push(v) - 1) :
   (this[hash[k]].units += v.units);
 }, (this[key] = []));
}, result);

console.log(result)
Morteza Tourani
  • 3,506
  • 5
  • 41
  • 48
  • Thanks for your answer @mortezaT it works great too, now im looking and comparing both answers to get the best performance. Also im thinking about to recognize typeoff data properties to create the most generic function as possible because it will manage very different kinds of data. ;) – Fraccier Jul 04 '16 at 09:28
  • @phileras this is possible to manage some terms but as Nina mentioned in comment you should ask a new question and describe your terms completely. – Morteza Tourani Jul 04 '16 at 13:53
  • new question sent @mortezaT. Sorry if look not very specific about the question but my technical language with JS OO is not so good as i am self taught and more specialized on visual stuff so i have a lack of technical language related to it – Fraccier Jul 05 '16 at 07:37
  • @phileras would you give me the link for new question. I'm also a self thought in JS OO. – Morteza Tourani Jul 05 '16 at 07:50
  • Sorry @mortezaT the question is deleted because they marked it as duplicated so im reading and searching stuff to try to make it by myself, thank you very much for your help!! :) – Fraccier Jul 05 '16 at 10:19
  • @phileras Duplicate of which question? – Morteza Tourani Jul 05 '16 at 10:42
  • the new question i made about new features. But don´t worry, i made good progress on it. ;) @mortezaT – Fraccier Jul 06 '16 at 09:13