skip to Main Content

I try to find the most effective way to compare two arrays of objects and return filtered difference in JavaScript.

I build an app to update stock in DB. But this app can use many users in the same time. I would like to avoid calling API and refresh all data in app each time changes are made, bcz database is too big and loading time is long. I have and old data locally, with help of MQTT I receive new data the moment its updated by someone. The goal is to find the difference between two objects and update locally only the one that has changes. Objects looks like this:

 let oldData = [
  { artnr: 12, affiliateid: 1, stock: 10 },
  { artnr: 12, affiliateid: 2, stock: 15 },
  { artnr: 12, affiliateid: 3, stock: 1 },
  { artnr: 13, affiliateid: 2, stock: 2 }
]
let newData = [
  { artnr: 12, affiliateid: 1, stock: 11 },
  { artnr: 12, affiliateid: 2, stock: 20 },
  { artnr: 12, affiliateid: 3, stock: 1 },
  { artnr: 13, affiliateid: 2, stock: 2 }
]

The only way I can think about it’s foreach. But in real life each of arrays will contain thousands of objects, so I would like to avoid iterations type of: (my working func)

 let oldData = [
  { artnr: 12, affiliateid: 1, stock: 10 },
  { artnr: 12, affiliateid: 2, stock: 1 },
  { artnr: 13, affiliateid: 2, stock: 2 }
]
 let newData = [
  { artnr: 12, affiliateid: 1, stock: 11 },
  { artnr: 12, affiliateid: 2, stock: 1 },
  { artnr: 13, affiliateid: 2, stock: 2 }
 ]

 const filterObjDiff = () => {
   var obj1 = oldData as StockUpdate.stockMqttResponse[]
   var obj2 = newData as StockUpdate.stockMqttResponse[]
   obj1.forEach(oldD => {
     obj2.forEach(newD => {
        if (
         oldD.affiliateid == newD.affiliateid &&
         oldD.artnr == newD.artnr &&
         oldD.stock != newD.stock
       ) {
        console.log('diff', newD)
       }
     })
   })
 }

Is there an effective way to white func

  const FilterObjectDiff = (oldData, newData) => {
   
  // filter here without iterations

    return [ 
      { artnr: 12, affiliateid: 1, stock: 11 },
      { artnr: 12, affiliateid: 2, stock: 20 }
     ]
   }

I would appreciate any ideas.

3

Answers


  1. I think the easiest way to improve the performance is with Hash Map since it’s O(1) of finding the specific value with a key. I’ve refactored it with Hash Map and it’s equivalent.

        let oldData = [
          { artnr: 12, affiliateid: 1, stock: 10 },
          { artnr: 12, affiliateid: 2, stock: 1 },
          { artnr: 13, affiliateid: 2, stock: 2 }
        ]
        let newData = [
          { artnr: 12, affiliateid: 1, stock: 11 },
          { artnr: 12, affiliateid: 2, stock: 1 },
          { artnr: 13, affiliateid: 2, stock: 2 }
        ]
        
        const createMap = (data) => {
          const map = new Map();
          data.forEach(stock => {
            map.set(`${stock.artnr}:${stock.affiliateid}`, stock);
          });
          return map;
        };
        
        const filterObjDiff = () => {
          var oldMap = createMap(oldData);
          var newMap = createMap(newData);
          const stockKeys = new Array(oldMap.keys());
          stockKeys.forEach(([key, oldStockData]) => {
            const newStockData = newMap.get(key);
            if (
              oldStockData.stock != newStockData.stock
            ) {
             console.log('diff', newStockData)
            }
          });
       }
       
       filterObjDiff();
    Login or Signup to reply.
  2. Two approaches:

    1. By creating a unique ID from artnr and affiliateid, mapping the old and the new data by that unique id and compare the values in that hashmap
    let oldData = [
      { artnr: 12, affiliateid: 1, stock: 10 },
      { artnr: 12, affiliateid: 2, stock: 1 },
      { artnr: 13, affiliateid: 2, stock: 2 }
    ]
    let newData = [
      { artnr: 12, affiliateid: 1, stock: 11 },
      { artnr: 12, affiliateid: 2, stock: 1 },
      { artnr: 13, affiliateid: 3, stock: 2 }
    ]
    
    // use an object as a hashmap
    const map = {};
    const getOrCreateEntry = ({ artnr, affiliateid }) => map[`${artnr}:${affiliateid}`] ??= [null, null];
    
    // fill the hashmap
    for (const item of oldData) getOrCreateEntry(item)[0] = item;  // old item
    for (const item of newData) getOrCreateEntry(item)[1] = item;  // new item
    
    Object.values(map).forEach((diff) => {
      if(diff[0]?.stock === diff[1]?.stock) return; // these stayed the same
      
      console.log("%o -> %o", ...diff);
    });
    1. by sorting oldData and newData in a similar way, then using two indices to iterate over both arrays at the same time. Using the same sort function to determine which index is behind the other.

    This minimizes the comparisons between the two arrays and doesn’t require the memory of a hashmap.

    let oldData = [
      { artnr: 12, affiliateid: 1, stock: 10 },
      { artnr: 12, affiliateid: 2, stock: 1 },
      { artnr: 13, affiliateid: 2, stock: 2 },
    ]
    let newData = [
      { artnr: 12, affiliateid: 1, stock: 11 },
      { artnr: 12, affiliateid: 2, stock: 1 },
      { artnr: 13, affiliateid: 3, stock: 2 },
    ]
    
    const order = (a,b) => a.artnr - b.artnr || a.affiliateid - b.affiliateid;
    
    oldData.sort(order);
    newData.sort(order);
    
    const diff = (oldItem, newItem) => {
      if (oldItem?.stock === newItem?.stock) return;
    
      console.log(oldItem, newItem);
    }
    
    // two indices one for oldData, one for newData
    let i = 0, j = 0;
    while (i < oldData.length && j < newData.length) {
      const sort = order(oldData[i], newData[j]);
      
           if (sort < 0) diff(oldData[i++], null);  // i has to catch up to j
      else if (sort > 0) diff(null, newData[j++]);  // j has to catch up to i
      else diff(oldData[i++], newData[j++]);        // same artnr && affiliateid
    }
    // I'm done with one of the arrays, nothing more to compare. pick up the rest
    while (i < oldData.length) diff(oldData[i++], null);
    while (j < newData.length) diff(null, newData[j++]);
    Login or Signup to reply.
  3. You could create a Map from oldData using a combination of affiliateid and artnr as the key, and then iterate newData in your FilterObjectDiff function, checking

    1. If the new affiliateid/artnr combination exists in oldData; and
    2. If it does, if the stock level is the same

    If neither of these cases are true, then the newData needs to update the DB.

    We use a Map as lookup time is O(1) and thus the lookup time for all of newData is O(n) where n is the length of newData.

    let oldData = [
      { artnr: 12, affiliateid: 1, stock: 10 },
      { artnr: 12, affiliateid: 2, stock: 15 },
      { artnr: 12, affiliateid: 3, stock: 1 },
      { artnr: 13, affiliateid: 2, stock: 2 }
    ]
    let newData = [
      { artnr: 12, affiliateid: 1, stock: 11 },
      { artnr: 12, affiliateid: 2, stock: 20 },
      { artnr: 12, affiliateid: 3, stock: 1 },
      { artnr: 13, affiliateid: 1, stock: 1 },
      { artnr: 13, affiliateid: 2, stock: 2 }
    ]
    
    let oldMap = new Map(oldData.map(o => [`${o.artnr}-${o.affiliateid}`, o.stock ]))
    
    var s
    
    const FilterObjectDiff = (newData) => newData
      .filter(o => (s = oldMap.get(`${o.artnr}-${o.affiliateid}`)) ?
              s != o.stock :
              true)
              
    console.log(FilterObjectDiff(newData))
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search