Skip to content
Advertisement

Grouping by multiple fields per object

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.:

  1. Group 1: gender
  2. 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>');
User contributions licensed under: CC BY-SA
6 People found this is helpful
Advertisement