skip to Main Content

I’m trying to combine two arrays with nested components into a new array. Best explained with example:

InputValues1 = [
    {
        "Values": [
            {"Input1": 1, "Input2": -1},
            {"Input1": 1, "Input2": -2},
            {"Input1": 2, "Input2": -2},
            {"Input1": 4, "Input2": -4}
        ],
        "Name": "AA",
        "key": 1
    },
    {
        "Values": [
            {"Input1": 1, "Input2": -2},
            {"Input1": 3, "Input2": 5},
            {"Input1": 3, "Input2": 2},
            {"Input1": 1, "Input2": -2}
        ],
        "Name": "AB",
        "key": 2
    }
]
InputValues2 = [
    {
        "Values": [
            {"Input1": 3, "Input2": 2},
            {"Input1": 2, "Input2": 1},
            {"Input1": 1, "Input2": 1},
            {"Input1": 1, "Input2": 6}
        ],
        "Name": "BA",
        "key": 1
    },
    {
        "Values": [
            {"Input1": 30, "Input2": 1},
            {"Input1": 6, "Input2": 2},
            {"Input1": 1, "Input2": 2},
            {"Input1": 1, "Input2": 8}
        ],
        "Name": "BB",
        "key": 2
    }
]

The new combined output needs to be:

CombinedOutput = [
    {
        "Values": [
            {"Input1": 4, "Input2": 1},
            {"Input1": 3, "Input2": -1},
            {"Input1": 3, "Input2": -1},
            {"Input1": 5, "Input2": 2}
        ],
        "Name": "AABA",
        "key": 11
    },
    {
        "Values": [
            {"Input1": 31, "Input2": -1},
            {"Input1": 9, "Input2": 7},
            {"Input1": 4, "Input2": 4},
            {"Input1": 2, "Input2": 6}
        ],
        "Name": "ABBB",
        "key": 22
    }
]    

So I’m looking to sum the nested Values arrays together, and concatenate the Name and key values.

I’m thinking the map function might be useful here, but I’m a little lost as to how to deal with the nested aspect as well as not create a mess with for loops.

I’ve shown the InputValues as having 2 entries each, but this could be larger, so I’m worried about my crude thinking of for loops.

6

Answers


  1. As you mentioned, you can use .map(). Below, I’m using .map() in a function I’ve created called merge to map each object to a transformed object. Each transformed object is built by using Objeect.fromEntries() and Object.entries() to iterate and access the current object’s keys/values. The transformed object has the same keys from the current object, but the values are transformed. If the current value is an array, I recursively call merge() to perform the same logic on the nested arrays and transform the inner objects. If it’s not an array, then we merge the values together based on your rules – If the key is either Name or key, you concatenate, otherwise we add. I’ve defined these rules in an object, that’s used by a callback function you can pass to merge and then called with the two values we’re trying to combine together:

    const InputValues1 = [{"Values": [{"Input1": 1, "Input2": -1}, {"Input1": 1, "Input2": -2}, {"Input1": 2, "Input2": -2}, {"Input1": 4, "Input2": -4}], "Name": "AA", "key": 1}, {"Values": [{"Input1": 1, "Input2": -2}, {"Input1": 3, "Input2": 5}, {"Input1": 3, "Input2": 2}, {"Input1": 1, "Input2": -2}], "Name": "AB", "key": 2}];
    const InputValues2 =  [{"Values": [{"Input1": 3, "Input2": 2}, {"Input1": 2, "Input2": 1}, {"Input1": 1, "Input2": 1}, {"Input1": 1, "Input2": 6}], "Name": "BA", "key": 1}, {"Values": [{"Input1": 30, "Input2": 1}, {"Input1": 6, "Input2": 2}, {"Input1": 1, "Input2": 2}, {"Input1": 1, "Input2": 8}], "Name": "BB", "key": 2}];
    
    const merge = (arr1, arr2, op) => {
      return arr1.map((obj, i) => Object.fromEntries(
        Object.entries(obj).map(([key, val]) => [
          key, 
          Array.isArray(val) 
            ? merge(val, arr2[i][key], op)
            : op(val, arr2[i][key], key)
        ])
      ));
    }
    
    // Define the rules for combining
    const ops = {
      key: (a, b) => +`${a}${b}`, // string concatenation but we convert the value back to a number (using the unary plus `+`) 
      default: (a, b) => a + b // string concatenation or numeric addition, depending on a and b types
    };
    const combine = (a, b, key) => ops[key]?.(a, b) ?? ops.default(a, b); // try and call the ops[key] if that key exists in `ops`, otherwisee call `ops.default` function to merge `a` and `b` 
    const combined = merge(InputValues1, InputValues2, combine);
    console.log(combined);
    Login or Signup to reply.
  2. I suggest keeping your head and breaking the problem down into small manageable chunks. Don’t be afraid to use lots of functions.

    const InputValues1 = [{"Values": [{"Input1": 1, "Input2": -1}, {"Input1": 1, "Input2": -2}, {"Input1": 2, "Input2": -2}, {"Input1": 4, "Input2": -4}], "Name": "AA", "key": 1}, {"Values": [{"Input1": 1, "Input2": -2}, {"Input1": 3, "Input2": 5}, {"Input1": 3, "Input2": 2}, {"Input1": 1, "Input2": -2}], "Name": "AB", "key": 2}]
    
    const InputValues2 =  [{"Values": [{"Input1": 3, "Input2": 2}, {"Input1": 2, "Input2": 1}, {"Input1": 1, "Input2": 1}, {"Input1": 1, "Input2": 6}], "Name": "BA", "key": 1}, {"Values": [{"Input1": 30, "Input2": 1}, {"Input1": 6, "Input2": 2}, {"Input1": 1, "Input2": 2}, {"Input1": 1, "Input2": 8}], "Name": "BB", "key": 2}]
    
    const expectedOutput = [{"Values": [{"Input1": 4, "Input2": 1}, {"Input1": 3, "Input2": -1}, {"Input1": 3, "Input2": -1}, {"Input1": 5, "Input2": 2}], "Name": "AABA", "key": 11}, {"Values": [{"Input1": 31, "Input2": -1}, {"Input1": 9, "Input2": 7}, {"Input1": 4, "Input2": 4}, {"Input1": 2, "Input2": 6}], "Name": "ABBB", "key": 22}]
    
    const output = combineInputValues(InputValues1, InputValues2)
    
    if (JSON.stringify(output) === JSON.stringify(expectedOutput))
        console.log("Boy, we did it!")
    else
        console.log("Back to the drawing board, son", output)
    
    function combineInputValues(inputValuesOne, inputValuesTwo) {
        if (inputValuesOne.length !== inputValuesTwo.length) throw new Error("Input arrays not of same length")
        const combinedArray = []
        for (let i=0; i < inputValuesOne.length; i++)
            combinedArray[i] = combineSingleInputValue(inputValuesOne[i], inputValuesTwo[i])
        return combinedArray
    }
    
    function combineSingleInputValue(inputValueOne, inputValueTwo) {
        return {
            Values: combineValues(inputValueOne.Values, inputValueTwo.Values),
            Name: inputValueOne.Name + inputValueTwo.Name,
            key: Number(inputValueOne.key.toString() + inputValueTwo.key.toString()),
        }
    }
    
    function combineValues(valuesOne, valuesTwo) {
        if (valuesOne.length !== valuesTwo.length) throw new Error("Input arrays not of same length")
            const combinedArray = []
        for (let i=0; i < valuesOne.length; i++)
            combinedArray[i] = combineSingleValuesEntry(valuesOne[i], valuesTwo[i])
        return combinedArray
    }
    
    function combineSingleValuesEntry(entryOne, entryTwo) {
        return {
            Input1: entryOne.Input1 + entryTwo.Input1,
            Input2: entryOne.Input2 + entryTwo.Input2,
        }
    }
    Login or Signup to reply.
  3. You can use the map function to iterate over the arrays and combine them.

    // Define the input arrays
    let InputValues1 = [
        {
            "Values": [
                {"Input1": 1, "Input2": -1},
                {"Input1": 1, "Input2": -2},
                {"Input1": 2, "Input2": -2},
                {"Input1": 4, "Input2": -4}
            ],
            "Name": "AA",
            "key": 1
        },
        {
            "Values": [
                {"Input1": 1, "Input2": -2},
                {"Input1": 3, "Input2": 5},
                {"Input1": 3, "Input2": 2},
                {"Input1": 1, "Input2": -2}
            ],
            "Name": "AB",
            "key": 2
        }
    ];
    
    let InputValues2 = [
        {
            "Values": [
                {"Input1": 3, "Input2": 2},
                {"Input1": 2, "Input2": 1},
                {"Input1": 1, "Input2": 1},
                {"Input1": 1, "Input2": 6}
            ],
            "Name": "BA",
            "key": 1
        },
        {
            "Values": [
                {"Input1": 30, "Input2": 1},
                {"Input1": 6, "Input2": 2},
                {"Input1": 1, "Input2": 2},
                {"Input1": 1, "Input2": 8}
            ],
            "Name": "BB",
            "key": 2
        }
    ];
    
    // Define the function
    function combineArrays(arr1, arr2) {
        return arr1.map((item1, index) => {
            let item2 = arr2[index];
            return {
                Values: item1.Values.map((value, i) => {
                    return {
                        Input1: value.Input1 + item2.Values[i].Input1,
                        Input2: value.Input2 + item2.Values[i].Input2
                    };
                }),
                Name: item1.Name + item2.Name,
                key: parseInt(item1.key.toString() + item2.key.toString())
            };
        });
    }
    
    // Call the function
    let CombinedOutput = combineArrays(InputValues1, InputValues2);
    
    // Log the result
    console.log(CombinedOutput);

    Using a Map to iterate over two input arrays in parallel. For each pair of elements, a new object is created with the concatenated value, name, and key. Values ​​are combined by iterating through the "values" array in parallel and creating a new object for each pair of values.
    The Map function is used to iterate over the "Values" array in parallel, and the "+" operator adds values. The parseInt function is used to convert concatenated keys to integers. I assume that the two input arrays are the same length and that each object’s "value" array is the same length. If not, you may need to add an error-checking code.

    Login or Signup to reply.
  4. You could separate the functions for merging.

    const
        a = [{ Values: [{ Input1: 1, Input2: -1 }, { Input1: 1, Input2: -2 }, { Input1: 2, Input2: -2 }, { Input1: 4, Input2: -4 }], Name: "AA", key: 1 }, { Values: [{ Input1: 1, Input2: -2 }, { Input1: 3, Input2: 5 }, { Input1: 3, Input2: 2 }, { Input1: 1, Input2: -2 }], Name: "AB", key: 2 }],
        b = [{ Values: [{ Input1: 3, Input2: 2 }, { Input1: 2, Input2: 1 }, { Input1: 1, Input2: 1 }, { Input1: 1, Input2: 6 }], Name: "BA", key: 1 }, { Values: [{ Input1: 30, Input2: 1 }, { Input1: 6, Input2: 2 }, { Input1: 1, Input2: 2 }, { Input1: 1, Input2: 8 }], Name: "BB", key: 2 }],
        addByKey = (a, b) => Object.fromEntries(Object
            .entries(a)
            .map(([k, v]) => [k, v + b[k]])
        ),
        outerObject = (a, b) => ({
            Values: map(addByKey)(a.Values, b.Values),
            Name: a.Name + b.Name,
            key: '' + a.key + a.key
        }),
        map = fn => (a, b) => {
            const result = [];
            for (let i = 0; i < a.length; i++) result.push(fn(a[i], b[i]));
            return result;
        },
        merge = map(outerObject);
        
    console.log(merge(a, b));
    .as-console-wrapper { max-height: 100% !important; top: 0; }
    Login or Signup to reply.
  5. It looks like you can solve that with this small generic function

    function zipWith(a, b, fn) {
        let res = Array.isArray(a) ? [] : {}
        for (let k of Object.keys(a)) {
            res[k] = fn(a[k], b[k], k)
        }
        return res
    }
    

    It accepts two arrays or objects and creates a new array/object by applying a callback function to the "left" and "right" values.

    The callback in your case would be a function that either adds arguments if they are scalars or recursively zips them otherwise:

    function adder(a, b, key) {
        if (typeof a === 'object')
            return zipWith(a, b, adder)
        return a + b
    }
    

    Putting it all together:

     CombinedOutput = zipWith(InputValues1, InputValues2, adder)
    

    It’s not clear from your post what you’re doing with the key key, so this is left as an exercise (hint: in the adder add a branch if (key === 'key') { ... }).

    Login or Signup to reply.
  6. Just iterate items in the second array, find corresponding items from the first one (create a copy of it) and add values.

    The trick here that the key should be combined too, but we need to find the items from the first array, so combine keys under _key property and then replace it to key in the end.

    As you see this make the code very fast more than 15x faster than the other solutions.

    const result = InputValues1.slice();
    InputValues2.forEach(e => {
      const found = result.find(e2 => e.key === e2.key);
      if(found){
        e.Values.forEach((v, idx, arr, v2 = found.Values[idx]) => (v2.Input1 += v.Input1, v2.Input2 += v.Input2));
        found.Name += e.Name;
        found._key ??= found.key;
        found._key += '' + e.key;
      } else result.push(e);
    });
    result.forEach(e => (e.key = +e._key, delete e._key));
    // ready, display result
    result.forEach(e => console.log(JSON.stringify(e)));
    <script>
    const InputValues1 = [{"Values": [{"Input1": 1, "Input2": -1}, {"Input1": 1, "Input2": -2}, {"Input1": 2, "Input2": -2}, {"Input1": 4, "Input2": -4}], "Name": "AA", "key": 1}, {"Values": [{"Input1": 1, "Input2": -2}, {"Input1": 3, "Input2": 5}, {"Input1": 3, "Input2": 2}, {"Input1": 1, "Input2": -2}], "Name": "AB", "key": 2}]
    const InputValues2 =  [{"Values": [{"Input1": 3, "Input2": 2}, {"Input1": 2, "Input2": 1}, {"Input1": 1, "Input2": 1}, {"Input1": 1, "Input2": 6}], "Name": "BA", "key": 1}, {"Values": [{"Input1": 30, "Input2": 1}, {"Input1": 6, "Input2": 2}, {"Input1": 1, "Input2": 2}, {"Input1": 1, "Input2": 8}], "Name": "BB", "key": 2}]
    </script>
    ` Chrome/120
    --------------------------------------------------------
    Alexander    1.00x  |  x1000000  128  129  132  135  140
    Nina        15.86x  |   x100000  203  204  205  206  209
    Nick        22.50x  |   x100000  288  293  294  297  299
    --------------------------------------------------------
    https://github.com/silentmantra/benchmark `
    
    const InputValues1 = [{"Values": [{"Input1": 1, "Input2": -1}, {"Input1": 1, "Input2": -2}, {"Input1": 2, "Input2": -2}, {"Input1": 4, "Input2": -4}], "Name": "AA", "key": 1}, {"Values": [{"Input1": 1, "Input2": -2}, {"Input1": 3, "Input2": 5}, {"Input1": 3, "Input2": 2}, {"Input1": 1, "Input2": -2}], "Name": "AB", "key": 2}]
    const InputValues2 =  [{"Values": [{"Input1": 3, "Input2": 2}, {"Input1": 2, "Input2": 1}, {"Input1": 1, "Input2": 1}, {"Input1": 1, "Input2": 6}], "Name": "BA", "key": 1}, {"Values": [{"Input1": 30, "Input2": 1}, {"Input1": 6, "Input2": 2}, {"Input1": 1, "Input2": 2}, {"Input1": 1, "Input2": 8}], "Name": "BB", "key": 2}]
    
    // @benchmark Nick
    const merge = (arr1, arr2, op) => {
      return arr1.map((obj, i) => Object.fromEntries(
        Object.entries(obj).map(([key, val]) => [
          key, 
          Array.isArray(val) 
            ? merge(val, arr2[i][key], op)
            : op(val, arr2[i][key], key)
        ])
      ));
    }
    
    // Define the rules for combining
    const ops = {
      key: (a, b) => + ('' + a + b), // string concatenation but we convert the value back to a number (using the unary plus `+`) 
      default: (a, b) => a + b // string concatenation or numeric addition, depending on a and b types
    };
    const combine = (a, b, key) => ops[key]?.(a, b) ?? ops.default(a, b); // try and call the ops[key] if that key exists in `ops`, otherwisee call `ops.default` function to merge `a` and `b` 
    merge(InputValues1, InputValues2, combine);
    
    // @benchmark Nina
    {
    
    const addByKey = (a, b) => Object.fromEntries(Object
            .entries(a)
            .map(([k, v]) => [k, v + b[k]])
        ),
        outerObject = (a, b) => ({
            Values: map(addByKey)(a.Values, b.Values),
            Name: a.Name + b.Name,
            key: '' + a.key + a.key
        }),
        map = fn => (a, b) => {
            const result = [];
            for (let i = 0; i < a.length; i++) result.push(fn(a[i], b[i]));
            return result;
        },
        merge = map(outerObject);
    merge(InputValues1, [...InputValues2]);
    }
    // @benchmark Alexander
    
    const result = InputValues1.slice();
    InputValues2.forEach(e => {
      const found = result.find(e2 => e.key === e2.key);
      if(found){
        e.Values.forEach((v, idx, arr, v2 = found.Values[idx]) => (v2.Input1 += v.Input1, v2.Input2 += v.Input2));
        found.Name += e.Name;
        found._key ??= found.key;
        found._key += '' + e.key;
      } else result.push(e);
    });
    result.forEach(e => (e.key = +e._key, delete e._key));
    result;
    
    /*@end*/eval(atob('e2xldCBlPWRvY3VtZW50LmJvZHkucXVlcnlTZWxlY3Rvcigic2NyaXB0Iik7aWYoIWUubWF0Y2hlcygiW2JlbmNobWFya10iKSl7bGV0IHQ9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic2NyaXB0Iik7dC5zcmM9Imh0dHBzOi8vY2RuLmpzZGVsaXZyLm5ldC9naC9zaWxlbnRtYW50cmEvYmVuY2htYXJrL2xvYWRlci5qcyIsdC5kZWZlcj0hMCxkb2N1bWVudC5oZWFkLmFwcGVuZENoaWxkKHQpfX0='));
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search