Home > OS >  Grouping an array of objects and assign new combined value to each object
Grouping an array of objects and assign new combined value to each object

Time:06-14

From an array of objects, I would like to add an alumni attribute for each object. The value of this attribute is a concatenation separated by "-" of the names of all the objects that have the same school value.

Now the tricky part is that I need to make it performance efficient as possible, as the initial object array is very large.

I figured out a solution using groupBy school, in each group map the different names of and concat them into a string, then looping on each object to add the new attribute and value, and finally loop on each group to place all the objects in a new array (do undo the groups).

I am certain that there is a much cleaner way to do this. Any suggestion is more than welcome.

Below is the initial array, and the expected result with the new attribute added to each object in the array.

var initialArr = [
  {name:"A", school:"LFM"},
  {name:"B", school:"LFM"},
  {name:"C", school:"PBE"},
  {name:"D", school:"LFM"},
  {name:"E", school:"BPE"},
  {name:"F", school:"LFM"}
];

var expectedResult = [
  {name:"A", school:"LFM", alumni:"A-B-D-F"},
  {name:"B", school:"LFM", alumni:"A-B-D-F"},
  {name:"C", school:"PBE", alumni:"C-E"},
  {name:"D", school:"LFM", alumni:"A-B-D-F"},
  {name:"E", school:"BPE", alumni:"C-E"},
  {name:"F", school:"LFM", alumni:"A-B-D-F"}
];

CodePudding user response:

I'd say the most efficient way to do it would be to create an object which maps school to an array of names. Then map the initial array:

var initialArr= [
  {name:"A", school:"LFM"},
  {name:"B", school:"LFM"},
  {name:"C", school:"PBE"},
  {name:"D", school:"LFM"},
  {name:"E", school:"BPE"},
  {name:"F", school:"LFM"}
];

var schoolObj = initialArr.reduce((a, { name, school }) => {
  a[school] = (a[school] || []);
  a[school].push(name);
  return a;
}, {});

var expectedResult = initialArr.map(e => {
  e.alumni = schoolObj[e.school].join("-");
  return e;
});

console.log(expectedResult);

This is O(n) time complexity but also O(n) space complexity (I think - still learning in that regard). You could make a more inefficient O(n^2) solution that had constant space but since you mentioned the input array to be very large, I assumed time would be your largest constraint.

Note that you may have to perform a sort on the array of alumni, in which case the time complexity will likely be O(n log n) if you use an efficient sort (I leave the choice of algorithm to you).

CodePudding user response:

Filter and map the data to your liking.

    const initialArr= [
      {name:"A", school:"LFM"},
      {name:"B", school:"LFM"},
      {name:"C", school:"PBE"},
      {name:"D", school:"LFM"},
      {name:"E", school:"BPE"},
      {name:"F", school:"LFM"}
    ]

    console.log(
      initialArr.map(x => ({
        name: x.name, 
        school: x.school, 
        alumni: `${x.name}-${initialArr.filter(y => x.school === y.school && y.name !== x.name).map(y => y.name).join("-")}`
      }))
    )

CodePudding user response:

The way how you describe it is efficient.

It depends only on how exactly you do the groupBy and other parts. If there is no cycle-in-cycle (and there has not to be), the complexity is O(n) (linear), which is as good as you can get. There can be space for some microoptimization but that is something that is not usually very effective and in most projects not worth the time.

CodePudding user response:

Edit: The fastest way is the basic way. Here is a test done by someone and for is 20x faster then forEach and map and the rest.

var initialArr= [
    {name:"A", school:"LFM"},
    {name:"B", school:"LFM"},
    {name:"C", school:"PBE"},
    {name:"D", school:"LFM"},
    {name:"E", school:"BPE"},
    {name:"F", school:"LFM"}
];

var expectedArr = [...initialArr]; //if you want even more speed, remove [...], but it will alter initialArr
var schoolObject = Object.fromEntries(expectedArr.map(e => [e.school, []]));

expectedArr.forEach((e) => schoolObject[e.school].push(e.name));
var keys = Object.keys(schoolObject);

for(let k = 0; k< keys.length;k  )
    schoolObject[keys[k]] = schoolObject[keys[k]].join("-");

for(let i=0;i<expectedArr.length;i  )
    expectedArr[i].alumni = schoolObject[expectedArr[i].school];

console.log(initialArr);

The fastest way I could think of:

  1. Creates an object that has as properties all schools
  2. Adds all names to schoolObject
  3. Append result with joined values.

const initialArr= [
    {name:"A", school:"LFM"},
    {name:"B", school:"LFM"},
    {name:"C", school:"PBE"},
    {name:"D", school:"LFM"},
    {name:"E", school:"BPE"},
    {name:"F", school:"LFM"}
];

var schoolObject = Object.fromEntries(initialArr.map(e => [e.school, []]));
initialArr.forEach((e) => schoolObject[e.school].push(e.name))
var expectedResult = initialArr.map(e => {
    return {...e,alumni: schoolObject[e.school].join("-")};
});
console.log(expectedResult);

  • Related