progrAmmar
progrAmmar

Reputation: 2670

Find Max value of each x value in JavaScript multidimensional array

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]]

Upvotes: 1

Views: 1075

Answers (3)

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.

Upvotes: 2

jfriend00
jfriend00

Reputation: 707326

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;
}

Upvotes: 1

TLS
TLS

Reputation: 3150

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.

Upvotes: 1

Related Questions