How to group by an array of objects with similar names but not the same

Viewed 74

I have this type of array of objects:

  [{
    ID: 'MCHARPENT ',
    REA: 4,
    STO: 90,
    EFFCAR: 178,
  },
  {
    ID: 'I050MCHE  ',
    REA: 0,
    STO: 125,
    EFFCAR: 228,
  },
  {
    ID: 'I050MCHE  ',
    REA: 0,
    STO: 106,
    EFFCAR: 231,
  },
  {
    ID: 'DBALLAVOINE',
    REA: 0,
    STO: 107,
    EFFCAR: 172,
  },
  {
    ID: 'DBALLAVOINE',
    REA: 20,
    STO: 30,
    EFFCAR: 100,
  }]

I'm searching a way to group them when the IDs are:

  • either the same (second and third objects)
  • or when one starts with 3 letters that are found after 'I050', as in this case the letters 'MCH', which is the beginning of the first ID and is after I050 in the second

If there is a match, the ID without 'I050' take over the name. In addition, I have to sum the other values.

The result should look like this with these four objects :

  {
    ID: 'MCHARPENT',
    REA: 4,
    STO: 321,
    EFFCAR: 637,
  },
  {
    ID: 'DBALLAVOINE',
    REA: 20,
    STO: 137,
    EFFCAR: 272,
  }]

I don't manage to find a way to group by with these conditions.

3 Answers

Haven't done any sorting because luckely for the given example it is not needed. This could be a problem depending on the array of data you are getting.

let results = [];
let something = 'I050';
let items = [{ID: 'MCHARPENT ',REA: 4,STO: 90,EFFCAR: 178,},{ID: 'I050MCHE  ',REA: 0,STO: 125,EFFCAR: 228,},{ID: 'I050MCHE  ',REA: 0,STO: 106,EFFCAR: 231,},{ID: 'DBALLAVOINE',REA: 0,STO: 107,EFFCAR: 172,}];
  
items.forEach( item => {
    if (item.ID.startsWith(something)) {
        results.filter( result => {
            if (result.ID.startsWith(item.ID.trim().slice(4, -1))) {
               result.REA = result.REA + item.REA;
               result.STO = result.STO + item.STO;
               result.EFFCAR = result.EFFCAR + item.EFFCAR;
            } // could do an ELSE here pushing the item to results so it doesn't get lost if there is no match
        });
    } else {
        results.push(item);
    }
});

console.log(results);

Here is one approach using .sort() (Documentation) and .reduce() (Documentation)

First, we sort the objects to put all 'I050' IDs at the end (since they may need to be collapsed into existing objects that we need to create first)

Then, we reduce down the results and combine the objects where they match the requirements.

const data = [{ID: 'MCHARPENT ', REA: 4, STO: 90, EFFCAR: 178}, {ID: 'I050MCHE  ', REA: 0, STO: 125, EFFCAR: 228}, { ID: 'I050MCHE  ', REA: 0, STO: 106, EFFCAR: 231}, { ID: 'DBALLAVOINE', REA: 0, STO: 107, EFFCAR: 172 }]

const result = data
  // Move all of the 'I050' IDs to the end
  .sort(obj => obj.ID.startsWith('I050') ? 1 : -1)
  // Combine the data
  .reduce((acc, curr) => {
    // Find any existing object we would want to merge this into
    const existingObj = acc.find(obj =>
      // Has the same ID
      obj.ID === curr.ID ||
      // Or, if this is an 'I050', starts with the next 3 chars
      (curr.ID.startsWith('I050') && obj.ID.startsWith(curr.ID.substring(4, 7))))

    // If we find an object, merge them
    if (existingObj) {
      existingObj.REA += curr.REA
      existingObj.STO += curr.STO
      existingObj.EFFCAR += curr.EFFCAR
    } else {
    // If not, just add this as a new object
      acc.push(curr)
    }

    // Return the new array
    return acc
  }, [])

console.log(result)

Here 2 simple for loops

const data =  [

    {
        ID: 'MCHARPENT ',
        REA: 4,
        STO: 90,
        EFFCAR: 178,
      },
    {
        ID: 'I050MCHE  ',
        REA: 0,
        STO: 106,
        EFFCAR: 231,
    },
    

  {
    ID: 'I050MCHE  ',
    REA: 0,
    STO: 125,
    EFFCAR: 228,
  },

  {
    ID: 'DBALLAVOINE',
    REA: 0,
    STO: 107,
    EFFCAR: 172,
  }];
  


  const groupedData = [];
  
  function canBeMerged(item1, item2){

    if(item1.ID == item2.ID) return true;

    let item1ID = item1.ID.substring(4, 7); 
    let item2ID = item2.ID.substring(0, 3);

    if(item1ID == item2ID) return true;


    item2ID = item2.ID.substring(4, 7); 
    item1ID = item1.ID.substring(0, 3);

    if(item1ID == item2ID) return true;

    return false;
  }


  function merge(group, datapoint){
      group.REA += datapoint.REA;
      group.STO += datapoint.STO;
      group.EFFCAR += datapoint.EFFCAR;
  }
  
  for(let i = 0; i < data.length; i++){
        if(i === 0){
            groupedData.push(data[i]);
            continue;
        }

        let added = false;
        

        for(let j = 0; j < groupedData.length; j++){
            
            if(canBeMerged(groupedData[j], data[i])){
                merge(groupedData[j], data[i]);
                added = true;
                continue;
            }

        }

        if(added === false)
            groupedData.push(data[i]);

  }

  console.log(groupedData)

EDIT 1: function added to check the merge condition.

Related