Search code examples
javascriptarrayslodashrelationshipdenormalization

Simultaneously grouping and transforming data in JavaScript (with Lodash)


Given the following data set:

const users = {
  "1": { id: "1", name: "Alex" },
  "2": { id: "2", name: "John" },
  "3": { id: "3", name: "Paul" }
};

const memberships = [
  { userId: "1", groupId: "1" },
  { userId: "2", groupId: "2" },
  { userId: "3", groupId: "1" }
];

What is an effective way to achieve following desired result?

const usersByGroupId = {
  "1": [{ id: "1", name: "Alex" }, { id: "3", name: "Paul" }],
  "2": [{ id: "2", name: "John" }]
}

I came up with the following (using Lodash):

const usersByGroupId = mapValues(
  groupBy(memberships, "groupId"),
  memberships => memberships.map(membership => users[membership.userId])
);

I'm not that familiar with big O notation, but I can imagine the performance of the above solution is pretty terrible on large sets. Any suggestions for improvement?


Solution

  • You don't really need lodash — you can do this in one step with reduce(). Just check if the key exists, if so push, if not set a new array and push. It just requires one iteration of the membership array and for each a lookup in the users object (which is more-or-less constant time) making this a linear time operation.

    const users = {"1": { id: "1", name: "Alex" },"2": { id: "2", name: "John" },"3": { id: "3", name: "Paul" }};
    const memberships = [{ userId: "1", groupId: "1" },{ userId: "2", groupId: "2" },{ userId: "3", groupId: "1" }];
    
    let groups = memberships.reduce((obj, {userId, groupId}) => {
        (obj[groupId] || (obj[groupId] = []) ).push(users[userId])
        return obj
    }, {})
    
    console.log(groups)