4

In JavaScript, given n number of arrays as input in this format: (n=2)

array1:
[{x: 1, y: 5},{x: 2, y: 3},{x: 3, y: 6}]

array2:
[{x: 1, y: 2},{x: 2, y: 6},{x: 3, y: 2}]

How do I aggregate the Y-values easily and get this resulting array:

arrayOutput:
[{x: 1, y: 7},{x: 2, y: 9},{x: 3, y: 8}]

Thank you.

5
  • Can you give us more information about the values? For instance, in your example, each array's entries have unique x values (e.g., the same x doesn't appear in the same array). Is that guaranteed? Is x the deciding factor for whether you combine y values, or is it the position in the array? Things like that. Commented Jun 17, 2011 at 8:33
  • @T.J. Crowder: You can assume that all arrays have the same length with same unique x-values in same order. The number of arrays will though be very large so an efficient function would be very useful. Thank you. Commented Jun 17, 2011 at 8:36
  • Ah, okay, that changes things. Commented Jun 17, 2011 at 8:41
  • Incase you up for jquery, you should check jquery extend.api.jquery.com/jQuery.extend Commented Jun 17, 2011 at 9:22
  • @Neeraj jQuery and .extend are not the right tools here. ES5 shim & underscore are the right tools. Commented Jun 17, 2011 at 10:06

2 Answers 2

3

Update: The additional comment about the x values and their positions in the arrays makes the below irrelevant.

There's no particular trick, you just loop through the arrays and build up your result. It's nothing more than a nested loop. If you're trying to be maximally efficient across a broad range of JavaScript engines, avoid unnecessary function calls.

Something along the lines of:

function sumYValues(arrays) {
    var outer, inner, array, entry, sum, result, x;

    // Create our result array with a copy of the first array
    result = [];
    if (arrays.length > 0) {
        array = arrays[0];
        for (inner = 0; inner < array.length; ++inner) {
            entry = array[inner];
            result[inner] = {x: entry.x, y: entry.y};
        }

        // Add in the remaining values
        for (outer = 1; outer < arrays.length; ++outer) {
            array = arrays[outer];
            // You might want an assert here verifying that result.length == array.length
            for (inner = 0; inner < array.length; ++inner) {
                entry = array[inner];
                // You might want an assert here verifying that result[inner].x == entry.x
                result[inner].y += entry.y;
            }
        }
    }

    return result;
}

Those loops count from 0 (or 1) to array.length - 1. You might profile whether going backwards (array.length - 1 to 0 (or 1)) is any faster, mostly the "down to 0" one. I used to assume it was because it was in C when I was a fresh-faced youth (comparisons to 0 are faster than comparisons to another variable), but that assumption may or may not be valid in JavaScript.


There's no particular shortcut, you just loop through the arrays, do your comparisons, and build up your result.

If the x values will be unique in each array, it may be easier to keep track of your ongoing sum by using an object rather than an array and using x values as keys, and then converting it into an array when you're done. E.g.:

function sumYValues(arrays) {
    var outer, inner, ar, entry, sum, result, x;

    sum = {};
    for (outer = 0; outer < arrays.length; ++outer) {
        ar = arrays[outer];
        for (inner = 0; inner < arrays.length; ++inner) {
            entry = ar[inner];
            sum[entry.x] = (sum[entry.x] || 0) + entry.y;
        }
    }

    result = [];
    for (x in sum) {
        result.push({x: x, y: sum[x]});
    }

    return result;
}

The above is mostly just to demonstrate using sum, an object, as a map of x => y values, although it does implement at least some of the summing logic as well.

This line may need some explanation:

            sum[entry.x] = (sum[entry.x] || 0) + entry.y;

If sum doesn't have an entry for that x value, sum[entry.x] will be undefined, which is a "falsey" value. So we use the curiously-powerful || operator to either get the value for that x from sum or 0, and then add the current entry's y to it and store the result.

Sign up to request clarification or add additional context in comments.

3 Comments

Thank you so much :) I'll check the profiling aspects as well.
J.Crowder Benchmark. inner < arrays.length should be inner < ar.length. My implementation is faster on chrome ;) you underestimate the JIT.
@TJCrowder yes it's become noise. (cleaned it up) @dani in terms of optimisation, native methods create new objects. If you want a new array (like the array your returning) use the native methods. If you don't need a new object then use for/while loops to edit the array in memory.
3

Benchmark Example

Note that the mixed code is faster followed by loops followed by native/underscore.

function createOutput(arr1, arr2, arr3 /*, ... */) {
    return Array.prototype.reduce.call(arguments, function (prev, curr) {
        return prev.map(function(val, index) {
            return {
                x: val.x,
                y: val.y + curr[index].y
            }
        });
    });
}

Assumes arrays are sorted and contain all values of x in order 1..n with no gaps.

Kind of requires ES5. This can be swapped out for _ which gives this kind of functionality in a cross browser manner.

With underscore it is

function createOutput() {
    return _.reduce(arguments, function (memo, arr) {
        return _.map(memo, function(val, index) {
            return { x: val.x, y: val.y + arr[index].y };
        });
    });
}

3 Comments

I think this fails the "efficient" test. It's l33t, but it's a huge number of function calls, and the first thing you do is create a copy of the "very large number of arrays". :-)
@T.J.Crowder yeah that copy was unneccesary because _ does that anyway. It's still O(N). Yes there is overhead but it's terse and high level. I'm sure the Google closure compiler can factor away the inefficient bits.
@TJCrowder it actually wins the "efficient" test ;)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.