1

I want to 'reduce' the array to only max values for each x (or index 0) value in a JavaScript multidimensional array.

My Array is as follows:

var mulitple = [["1/2013", 1],
                ["1/2013", 5],
                ["1/2013", 7],
                ["1/2013", 6],
                ["1/2013", 5],
                ["2/2013", 7],
                ["2/2013", 10],
                ["2/2013", 10],
                ["3/2013", 7],
                ["3/2013", 10],
                ["3/2013", 10],
                ["4/2013", 1],
                ["4/2013", 5],
                ["4/2013", 7],
                ["4/2013", 6],
                ["4/2013", 5],
                ["5/2013", 7]];

So the final result should be as follows:

[["1/2013", 7],
 ["2/2013", 10],
 ["3/2013", 10],
 ["4/2013", 7],
 ["5/2013", 7]];

How can I achieve this in JavaScript.

EDIT:

Aww man who voted my question down.

Anyhow, this is what I have come up with.

var max = 0;
var newarray = [];
for (var i = 1; i < mulitple.length; i++) {
    if (mulitple[i - 1][0] == mulitple[i][0]) {
        if (mulitple[i - 1][1] > max) {
            max = mulitple[i - 1][1];
        }
    }
    else {
        if (mulitple[i - 1][1] > max) {
            max = mulitple[i - 1][1];
        }
        newarray.push([mulitple[i - 1][0], max]);
        max = 0;
    }
}
newarray.push([mulitple[mulitple.length - 1][0], max]);

The problem that I am having is that I can't get that last value (for the lone record) to get in the array. This was my result after I ran the code above.

[["1/2013", 7], ["2/2013", 10], ["3/2013", 10], ["4/2013", 7], ["5/2013", 0]]
7
  • Are those first values meant to be parsed as month/year dates or as math expressions? And, are you using the second value to break ties? Commented Feb 13, 2015 at 2:44
  • It would involve a loop, a second array, and capturing the items that have the max value for each group.... What have you tried? Are you having some specific problem other than "solve this whole thing for me"? Commented Feb 13, 2015 at 2:44
  • This can give you some tips: stackoverflow.com/questions/14446511/… Commented Feb 13, 2015 at 2:46
  • @jfriend00 the first values are month and year NOT math expressions Commented Feb 13, 2015 at 2:47
  • @TLS I understand much of array operations in JavaScript, to be honest I don't know how to go on about this. Just a simple nudge might help me solve this. Commented Feb 13, 2015 at 2:50

3 Answers 3

2

This assumes that original array is already sorted. If not, you will have to write additional function to sort out.

function findMaximums(data) {
    var out = [], maximums = {}, order = new Set;

    data.reduce(function(acc, pair) {
        if (
            // Accumulator has value and it's lower than current
            (acc[pair[0]] && acc[pair[0]][1] < pair[1]) ||
            // Accumulator doesn't have value
            !acc[pair[0]]
        ) {
            acc[pair[0]] = pair; // Store maximum in accumulator
            order.add(pair[0]) // Store order in set
        }
        return acc;
    }, maximums);

    order.forEach(function(key) {
        out.push(maximums[key]); // Populate out with maximums by order
    });

    return out;
}

findMaximums(multiple);

/*[
    [
        "1/2013",
        7
    ],
    [
        "2/2013",
        10
    ],
    [
        "3/2013",
        10
    ],
    [
        "4/2013",
        7
    ],
    [
        "5/2013",
        7
    ]
]*/

Update 1: same, but without Set.

function findMaximums(data) {
    var order = [];

    var maximums = data.reduce(function(acc, pair) {
        if (
            // Accumulator has value and it's lower than current
            (acc[pair[0]] && acc[pair[0]][2] < pair[1]) ||
            // Accumulator doesn't have value
            !acc[pair[0]]
        ) {
            // Store maximum
            acc[pair[0]] = pair;
            // Store order
            if (order.indexOf(pair[0]) === -1) {
                order.push(pair[0])
            }
        }
        return acc;
    }, {});

    return order.map(function(key) {
        return maximums[key]; // Populate out with maximums by order
    });
}

Update 2: Shorter version.

function findMaximums(data) {
  return data.filter(function(p1, i1) {
    return !data.some(function(p2, i2) {
      return p1[0] === p2[0] && ( (p1[1] < p2[1]) || (p1[1] === p2[1] && i1 > i2) );
    });
  });
}

In this version I let pair to remain in output if there are no other pairs in input data that:

  1. Have same month.
  2. Have bigger value.

or

  1. Have same value, but occur earlier than tested pair. This prevents duplicates.

Read here more about used Array methods: filter, some.

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

10 Comments

Well, look at that! JavaScript has a reduce method!
It should be said that this uses the Set object who's use either requires careful consideration of browser support (IE 11 or greater) or a polyfill. FYI, a set polyfill is here.
I don't see the output getting sorted into month/year order? Does this assume the input is already sorted and that order is retained? Does the Set object guarantee to preserve order? Or does this not guarantee any particular output order?
I tried this here and it does not sort the output by month/year (not sure if that was a requirement, but thought I'd mention it). In Chrome, it appears to return the data in the order encountered in the original array.
@progrAmmar didn't mention anything about order, so I've made an assumption about it right at the start.
|
1

Assuming the array as defined in the original question, which is sorted to have each grouping together...

Completely untested code:

var reduced = [];
var groupName = '';
var groupMax;
var groupIndex;
var l = multiple.length; // Grab the array length only once

for (var i = 0; i < l; i++){
    // Current Group Name doesn't match last Group Name
    if (multiple[i][0] !== groupName) {
        // Last Group Name is not empty (it's not the first Group)
        if (groupName !== '') {
            // Assume groupIndex has been set and grab the item
            reduced.push(multiple[groupIndex]);
        }
        // Grab the new Group Name and set the initial Max and Index
        groupName = multiple[i][0];
        groupMax = multiple[i][1];
        groupIndex = i;
    }

    // Current Value is bigger than last captured Group Max
    if (multiple[i][1] > groupMax) {
        // Grab the new Group Max and the current Index
        groupMax = multiple[i][1];
        groupIndex = i;
    }
}

// Grab the last index, since there's no new group after the last item
reduced.push(multiple[groupIndex]);

There could be some syntax or logic errors. I didn't actually run this code, but I think the concept is correct.

Comments

1

Here's a tested version using a map to collect all the unique values, then the output is sorted by month/year and is independent of the order of the input data. This also works in all browsers (IE6+).

Working demo: http://jsfiddle.net/jfriend00/dk1tc73s/

function findLargest(list) {
    var map = {}, output = [], item, key, val, current;
    for (var i = 0; i < list.length; i++) {
        item = list[i];
        key = item[0];
        val = item[1];
        current = map[key];
        if (current) {
            // this date is in the map, so make sure to save the largest
            // value for this date
            if (val > current) {
                map[key] = val;
            }            
        } else {
            // this date is not yet in the map, so add it
             map[key] = val;
        }
    }
    // map contains all the largest values, output it to an array
    // the map is not in a guaranteed order
    for (var key in map) {
        output.push([key, map[key]])
    }

    // actually parse to numbers in sort so the comparison
    // works properly on number strings of different lengths
    function parseDate(str) {
        var split = str.split("/");
        return {m: +split[0], y: +split[1]};
    }
    // now sort the output
    output.sort(function(t1, t2) {
        var diffYear, a, b;
        a = parseDate(t1[0]);
        b = parseDate(t2[0]);
        diffYear = a.y - b.y;
        if (diffYear !== 0) {
            return diffYear;
        } else {
            return a.m - b.m;
        }
    });
    return output;
}

Comments

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.