Iterate through nested loops and remove specific index that matches the condition

Viewed 83

I have a temp array which originally looks something like this :

[{emp_id:1, acitivty:'run'}, {emp_id: 2, activity:'climb'}, {emp_id:1,activity:'swim'} .....]

Now, what I want is to merge the objects with the same emp_id. It should look like this:

[{emp_id:1, activity:'run', activity2:'swim'}, {emp_id:'2',activity:'climb'} .....]

Instead, I only got this, not showing the rest of the objects in the array:

[{emp_id:'1', activity:'run', activity2:'swim'}]

In my code, when the condition is met and the merging and splicing is done, I decrement the z because I think after splicing, the array will be reindexed and I think the array length will still be as is. What seems to be the problem?

for(var z = 0; z < temp.length; z++) {
   for(var x = 1; x < temp.length; x++) {
      if(temp[z].emp_id == temp[x].emp_id) {
         var ot_key = 'ot_time'+x+1;
         var status_key = 'status'+x+1;
         var dtr_key = 'dtr_out'+x+1;
         Object.assign(temp[z], {
            [ot_key] : temp[x].ot_time,
            [status_key] : temp[x].status,
            [dtr_key] : temp[x].dtr_out
         })
         temp.splice(x, 1);
         z--;
      } 
   }
}
7 Answers

Beside the spelling, you could take an object and store the wanted postfix in the objects. later take the payload.

For example have a look to the final object before getting the values and mapping only the payload property:

{
    1: {
        index: 3,
        payload: { emp_id: 1, activity: "run", activity2: "swim" }
    },
    2: {
        index: 2,
        payload: { emp_id: 2, activity: "climb" }
    }
}

Here emp_id is taken as key for grouping and because of the wanted structure an index is grouped as well for having a value for futher activities.

var data = [{ emp_id: 1, activity: 'run' }, { emp_id: 2, activity: 'climb' }, { emp_id: 1, activity: 'swim' }],
    result = Object
        .values(data.reduce((r, { emp_id, activity }) => {
            if (r[emp_id]) r[emp_id].payload['activity' + r[emp_id].index++] = activity;
            else r[emp_id] = { index: 2, payload: { emp_id, activity } };
            return r;
        }, {}))
        .map(({ payload }) => payload);

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

A better approach is to use an array for the activities.

var data = [{ emp_id: 1, activity: 'run' }, { emp_id: 2, activity: 'climb' }, { emp_id: 1, activity: 'swim' }],
    result = Object.values(data.reduce((r, { emp_id, activity }) => {
        if (r[emp_id]) r[emp_id].activity.push(activity);
        else r[emp_id] = { emp_id, activity: [activity] };
        return r;
    }, {}));

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

You can use array .reduce() method to get an array of activities grouped by emp_id which I think much cleaner.

const temp = [{emp_id:1,activity:"run"},{emp_id:2,activity:"climb"},{emp_id:1,activity:"swim"}];
const res = Object.values(temp.reduce((ac, { emp_id, activity }) => {
  ac[emp_id] = ac[emp_id] || {emp_id, activity: []}
  ac[emp_id].activity.push(activity)
  return ac;
}, {}));

console.log(res);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Everything you can do in 2 loop, one for reduce another to collect output array.values. Fast and better.

Note : If you dont want to use Object.keys, use 2nd solution

const data = [
  { emp_id: 1, activity: "run" },
  { emp_id: 2, activity: "climb" },
  { emp_id: 1, activity: "swim" }
];
const result = data.reduce((obj, { emp_id, activity }) => {
  if (!obj[emp_id]) obj[emp_id] = { emp_id };
  const count = Object.keys(obj[emp_id]).length;
  obj[emp_id] = {
    ...obj[emp_id],
    [`activity_${count}`]: activity
  };
  return obj;
}, {});
let finalResult = Object.values(result)
console.log(finalResult);

// If you dont want to use Object.keys


const result2 = data.reduce((obj, { emp_id, activity }) => {
  if (!obj[emp_id]) obj[emp_id] = { count: 0, data: {} };
  const count = obj[emp_id].count + 1;
  obj[emp_id] = {
    data: {
      ...obj[emp_id].data,
      [`activity_${count}`]: activity
    },
    count: count
  };
  return obj;
}, {});
let finalResult2 = [];
for (let key in result2) {
  const { data } = result2[key];
  finalResult2.push(data);
}
console.log(finalResult2);
.as-console-wrapper { max-height: 100% !important; top: 0; color: blue!important; }

maybe like this

var data = [{
  emp_id: 1, activity: 'run'
}, {
  emp_id: 2, activity: 'climb'
}, {
  emp_id: 1, activity: 'swim'
}];

var lookup = {};
var counter = {};

for (var i = 0; i < data.length; i++) {
  var item = data[i];
  if (lookup[item.emp_id]) {
    var found = lookup[item.emp_id];
    lookup[item.emp_id]['activity' + counter[item.emp_id]] = item.activity;
    counter[item.emp_id]++;
    data.splice(i, 1);
    i--;
  } else {
    lookup[item.emp_id] = item;
    counter[item.emp_id] = 2;
  }
}

console.log(data);

You could use array.reduce() like this:

Be careful, your initial array has a spelling mistake in activity property.

let x =[
         {emp_id:1, activity:'run'},
         {emp_id: 2, activity:'climb'},
         {emp_id:1,activity:'swim'}
       ];

let finalResult =
     x.reduce((result, currentValue, currentIndex, arr) =>
       {
         return [...result, 
            arr.filter(f => f.emp_id === currentValue.emp_id &&
                            !result.find(r=>r.emp_id === f.emp_id)
                      )
               .reduce((subResult, currentItem, index) => 
                { 
                  return {emp_id:currentItem.emp_id, ...subResult, ...{[`activity${index ==0 ? '' : '_' + (index+1)}`]:currentItem.activity}}; 
                }, undefined)
              ];
       }, [])
      .filter(item=> !!item);

console.log(finalResult);

this solution will also take care if you have more then two activity have same id

var x=[{emp_id:1, acitivty:'run'}, {emp_id: 2, activity:'climb'}, {emp_id:1,activity:'swim'},{emp_id:1,activity:'swim3'} ]

let myMap = new Map()

x.forEach(element =>{
let alreadyExist=myMap.has(element.emp_id)  
if(alreadyExist){
let tempelement=myMap.get(element.emp_id)
tempelement.count=tempelement.count?++tempelement.count:2;
tempelement['acitivty'+tempelement.count]=element.activity
myMap.set(element.emp_id, tempelement)
}else{
myMap.set(element.emp_id, element)
}
});

//convert map to Array
let newArray=Array.from(myMap.values())

console.log(JSON.stringify(newArray))

//[{"emp_id":1,"acitivty":"run","count":3,"acitivty2":"swim","acitivty3":"swim3"},{"emp_id":2,"activity":"climb"}]

I would heed @NinaScholz's advice to use an array for activity values which will be much easier to handle afterwards when compared to testing for different related properties (and to fix the spelling of acitivty), but take a naive approach in creating a result array by using forEach(), which I find more readable. For example:

const source = [{emp_id: 1, activity: 'run'}, {emp_id: 2, activity: 'climb'}, {emp_id: 1, activity: 'swim'}];
let results = [];

// For each source item, check whether a result object with the current emp_id exists.
source.forEach(o => results.filter(r => r.emp_id === o.emp_id).length == 1
    // Add activity to existing result object.
    ? results[o.emp_id].activity.push(o.activity) 
    // Create a new result object
    : results.push({ emp_id: o.emp_id, activity: [o.activity] })
    );

console.log(results);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Related