skip to Main Content

I have two arrays, each with a years object with key valued pairs of the year and a value. I want to create an updated version of array1 with the values for each row’s years reduced by the values from the corresponding values in array2

i.e. A/Fred/2013 should be reduced from 16 by 3, to 13 and so on.

So far I have, but I’m not managing to update the array correctly

array1.forEach(r1 => {
    // Get the corresponding row from array2
    let array2Record = array2.find(r2 => r2.key1 === r1.key1 && r2.key2 === r1.key2);
    // Go through each of the years for this row in array1
    Object.entries(r1.years).forEach(keyValuePair => {
        // And reduce it by the value in array2
        keyValuePair.value = keyValuePair.value - array2Record.years[keyValuePair.key].value;
    })
})

This is the simplified version of the data

let array1 = [
    {
        "key1": "A",
        "key2": "Fred",
        "years": {
            "2013": 16,
            "2014": 11,
            "2015": 17
        }
    },
    {
        "key1": "A",
        "key2": "Jim",
        "years": {
            "2013": 1,
            "2014": 4,
            "2015": 3
        }
    },
    {
        "key1": "B",
        "key2": "Mary",
        "years": {
            "2013": 1,
            "2014": 4,
            "2015": 3
        }
    }
]

let array2 = [
    {
        "key1": "A",
        "key2": "Fred",
        "years": {
            "2013": 3,
            "2014": 2,
            "2015": 7
        }
    },
    {
        "key1": "A",
        "key2": "Jim",
        "years": {
            "2013": 9,
            "2014": 3,
            "2015": 1
        }
    },
    {
        "key1": "B",
        "key2": "Mary",
        "years": {
            "2013": 8,
            "2014": 3,
            "2015": 6
        }
    }
]

3

Answers


  1. Solution

    You can do so by zipping the arrays using map(), then updating the years property value. To create the new years property value you can use Object.keys() and reduce().

    let array1 = [
      {
        key1: "A",
        key2: "Fred",
        years: {
          2013: 16,
          2014: 11,
          2015: 17,
        },
      },
      {
        key1: "A",
        key2: "Jim",
        years: {
          2013: 1,
          2014: 4,
          2015: 3,
        },
      },
      {
        key1: "B",
        key2: "Mary",
        years: {
          2013: 1,
          2014: 4,
          2015: 3,
        },
      },
    ];
    
    let array2 = [
      {
        key1: "A",
        key2: "Fred",
        years: {
          2013: 3,
          2014: 2,
          2015: 7,
        },
      },
      {
        key1: "A",
        key2: "Jim",
        years: {
          2013: 9,
          2014: 3,
          2015: 1,
        },
      },
      {
        key1: "B",
        key2: "Mary",
        years: {
          2013: 8,
          2014: 3,
          2015: 6,
        },
      },
    ];
    
    const result = array1.map((arr1Value, i) => {
      // here we zip i. e. we get the i-th value of both arrays 
      const arr2value = array2[i]; 
      
      // here we merge/ reduce both i-th values
      // by creating a new object which has the same keys
      // as the original objects but the value is set to the difference
      // between both input array values for a specific year
      const updatedYearsProperty = Object.keys(arr1Value.years).reduce(
        (obj, key) => (
          (obj[key] = (arr1Value.years[key] ?? 0) - (arr2value.years[key] ?? 0)), obj
        ),
        {}
      );
       
      // now we just return a new object which has all of the same properties as the original, BUT
      // we update the years value with the new object we've creatd above
      return {
        ...arr1Value,
        years: updatedYearsProperty,
      };
    });
    
    console.log(JSON.stringify(result, null, 4));
    /* Stackoverflow: show only console */
    .as-console-wrapper {
      max-height: 100% !important;
      top: 0;
    }

    Additional remarks on the solution

    This method assumes, that both the array contain the same number of elements and that objects that should be merged are in the same position within both array (i. e. e.g. both Freds are the first elements in the array). One can also make it work without that being the case but it’s a bit more involved then (a lookup table e.g. using a Map would be required to do that in linear time O(n). Something like find() would run in O(n²)).

    By using the nullish coalescing operator ?? I handle the cases where a particular year might only be present in the first or second array by using a default value of 0 in that case. You can remove that check if that cannot be the case with your data.

    In terms of runtime this runs in O(n) and only does a single iteration through your data and therefore is pretty much as fast as it gets.

    I also treated everything as immutable which is generally a good practice in order to prevent bugs. That also means the original input value has remained untouched or better unchanged. The result is (as the data currently is) a deep copy of your input.

    See also

    There are many possible ways to zip arrays in JavaScript e.g. using map() (as I did here) or generators: See this SO thread

    Login or Signup to reply.
  2. In short, you can do it by using reduce() to get a new array by combining them. Then reduce the year values by matching and looping them one by one with a forEach.
    It matches any records by matching key1 and key2 values so if you want to match more arrays, then you just need to combine them by using concat()

    let array1 = [
        {
            "key1": "A",
            "key2": "Fred",
            "years": {
                "2013": 16,
                "2014": 11,
                "2015": 17
            }
        },
        {
            "key1": "A",
            "key2": "Jim",
            "years": {
                "2013": 1,
                "2014": 4,
                "2015": 3
            }
        },
        {
            "key1": "B",
            "key2": "Mary",
            "years": {
                "2013": 1,
                "2014": 4,
                "2015": 3
            }
        }
    ]
    
    let array2 = [
        {
            "key1": "A",
            "key2": "Fred",
            "years": {
                "2013": 3,
                "2014": 2,
                "2015": 7
            }
        },
        {
            "key1": "A",
            "key2": "Jim",
            "years": {
                "2013": 9,
                "2014": 3,
                "2015": 1
            }
        },
        {
            "key1": "B",
            "key2": "Mary",
            "years": {
                "2013": 8,
                "2014": 3,
                "2015": 6
            }
        }
    ]
    
    const reducedArray = array1.concat(array2).reduce((tot, val) => { 
        const index = tot.findIndex(({ key1, key2 }) => key1 === val.key1 && key2 === val.key2);
        
        if(index > -1){
          Object.keys(val.years).forEach(yearKey => {
            tot[index].years[yearKey] -= val.years[yearKey];
          });
        }
        else {
            tot.push(val);
        }
    
       return tot;
    }, []);
    
    console.log(reducedArray);
    Login or Signup to reply.
  3. You can simply find each matching object in array2, and then for each year, update each count.

    Optional chaining ?. and the nullish coalescing operator ?? are used to handle cases where there is no matching object in array2, by providing an empty array {} to Object.entries.

    const array1 = [{"key1":"A","key2":"Fred","years":{"2013":16,"2014":11,"2015":17}},{"key1":"A","key2":"Jim","years":{"2013":1,"2014":4,"2015":3}},{"key1":"B","key2":"Mary","years":{"2013":1,"2014":4,"2015":3}}]
    const array2 = [{"key1":"A","key2":"Fred","years":{"2013":3,"2014":2,"2015":7}},{"key1":"A","key2":"Jim","years":{"2013":9,"2014":3,"2015":1}},{"key1":"B","key2":"Mary","years":{"2013":8,"2014":3,"2015":6}}]
    
    
    array1.forEach(({key1, key2, years}) => Object.entries(
        array2.find(({key1: a, key2: b}) => a===key1 && b===key2)?.years ?? {}
      ).forEach(([year, count]) => years[year] -= count)
    )
    
    console.log(array1)
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search