I am trying to determine the best data-type to sort an array of objects, into groups defined by properties. I have an array of objects, like so:
var people = [ { name: 'Pete', gender: 'Male', age: 22 }, { name: 'Samantha', gender: 'Female', age: 20 }, { name: 'Frank', gender: 'Male', age: 22 }, { name: 'Gary', gender: 'Male', age: 21 }, { name: 'Maria', gender: 'Female', age: 20 }, { name: 'Hannah', gender: 'Female', age: 21 }, { name: 'Pete', gender: 'Male', age: 20 } ];
I need to group these objects into an arbitrary-defined group. E.g.:
- Group 1:
gender
- Group 2:
age
(This can be defined by the server and can change to contain a third group if we wish.)
Which then gives me (visually):
Male: 21: Gary 22: Pete Frank Female 20: Samantha Maria 21: Hannah
I think the appropriate data type would be an object of objects. I.e.:
{ Male: { 21: { [ { name: 'Gary', gender: 'Male', age: 21 } ] }, 22: { [ { name: 'Pete', gender: 'Male', age: 22 }, { name: 'Frank', gender: 'Male', age: 22 } ] } }, Female: { 20: { [ { name: 'Samantha', gender: 'Female', age: 20 }, { name: 'Maria', gender: 'Female', age: 20 } ] }, 21: { [ { name: 'Hannah', gender: 'Female', age: 21 } ] } } }
But I cannot work out, for the life of me, an appropriate algorithm to sort these objects into a data-type which represents the above.
There is a useful utility in underscore.js
called _.groupBy(arr, callback)
which I can use as follows:
_.groupBy(people, function (person) { var props = ['gender', 'age'], // server-defined prop = []; for (var i = 0, length = props.length; i < length; i++) { prop.push(person[props[i]]); } return prop.join('|'); });
This gives me a 1-depth object which I can use a for...in
loop to iterate over the keys, and loop through to build the object above, but it’s that bit of the code I’m stuck on.
The returned object would be:
{ "Male|22": [ { "name": "Pete", "gender": "Male", "age": 22 }, { "name": "Frank", "gender": "Male", "age": 22 } ], "Female|20": [ { "name": "Samantha", "gender": "Female", "age": 20 }, { "name": "Maria", "gender": "Female", "age": 20 } ], "Male|21": [ { "name": "Gary", "gender": "Male", "age": 21 } ], "Female|21": [ { "name": "Hannah", "gender": "Female", "age": 21 } ], "Male|20": [ { "name": "Pete", "gender": "Male", "age": 20 } ] }
I’m thinking of then looping through each key in the object, splitting at the pipe (|
) and using recursion to construct a new object of objects containing the groups/array of data.
That’s such a horrendous way of achieving this but I have no idea how to do it otherwise.
Is there a better way I’m missing?
Advertisement
Answer
Maybe this helps you. It utilises an array with the properties of the object and the result is grouped by the content of the properties.
The forEach
loop iterates over the data. The reduce
loop is for generating grouped properties for every given group and if it is the last one it returns an array, if not already there.
The last step is to push the value of one of people to the array.
var people = [{ name: 'Pete', gender: 'Male', age: 22 }, { name: 'Samantha', gender: 'Female', age: 20 }, { name: 'Frank', gender: 'Male', age: 22 }, { name: 'Gary', gender: 'Male', age: 21 }, { name: 'Maria', gender: 'Female', age: 20 }, { name: 'Hannah', gender: 'Female', age: 21 }, { name: 'Pete', gender: 'Male', age: 20 }], groups = ['gender', 'age'], grouped = {}; people.forEach(function (a) { groups.reduce(function (o, g, i) { // take existing object, o[a[g]] = o[a[g]] || (i + 1 === groups.length ? [] : {}); // or generate new obj, or return o[a[g]]; // at last, then an array }, grouped).push(a); }); document.write('<pre>' + JSON.stringify(grouped, 0, 4) + '</pre>');