Riël
Riël

Reputation: 1321

Get max value per key in a javascript array

Say, I have the following array:

[ 
  {a:1, b:"apples"}, 
  {a:3, b:"apples"}, 
  {a:4, b:"apples"}, 
  {a:1, b:"bananas"}, 
  {a:3, b:"bananas"}, 
  {a:5, b:"bananas"}, 
  {a:6, b:"bananas"}, 
  {a:3, b:"oranges"}, 
  {a:5, b:"oranges"}, 
  {a:6, b:"oranges"}, 
  {a:10, b:"oranges"} 
]

I want to efficiëntly get for each type of 'b' the whole object with the highest a, so my function should produce:

[
  {a:4, b:"apples"},
  {a:6, b:"bananas"},
  {a:10, b:"oranges"}
]

Now I would do something like this:

var cache = {};
var resultobj = {};
result = [];
array.forEach(function (r) {
 if (cache[r.b] && cache[r.b] > r.a) {
   result[r.b] = r;
 }
})
for (var key in result) {
 result.push(result[key]);
}

That looks terrible inefficiënt...?

Upvotes: 1

Views: 399

Answers (7)

georg
georg

Reputation: 214969

It's a two-liner in ES5 and an one-liner is ES6:

ary = [ 
  {a: 0, b:"apples"}, 
  {a:-3, b:"apples"}, 
  {a:-4, b:"apples"}, 
  {a:1, b:"bananas"}, 
  {a:3, b:"bananas"}, 
  {a:5, b:"bananas"}, 
  {a:6, b:"bananas"}, 
  {a:3, b:"oranges"}, 
  {a:5, b:"oranges"}, 
  {a:6, b:"oranges"}, 
  {a:10, b:"oranges"} 
]

// ES5

maxes = {};
ary.forEach(function(e) {
  maxes[e.b] = e.b in maxes ? Math.max(maxes[e.b], e.a) : e.a;
});

document.write('<pre>'+JSON.stringify(maxes,0,3));
            
// ES6
 
maxes = ary.reduce((m, e) => 
  Object.assign(m, { [e.b]: e.b in m ? Math.max(m[e.b], e.a) : e.a }), {});

document.write('<pre>'+JSON.stringify(maxes,0,3));

Upvotes: 2

isvforall
isvforall

Reputation: 8926

You are close, you have some little wrong logic in the code, you have declared the cache, but don't have used it.

if (cache[r.b] && cache[r.b] > r.a) //This always will be "false"

See working example

var array = [ { a: 1, b: "apples" }, { a: 3, b: "apples" }, { a: 4, b: "apples" }, { a: 1, b: "bananas" }, { a: 3, b: "bananas" }, { a: 5, b: "bananas" }, { a: 6, b: "bananas" }, { a: 3, b: "oranges" }, { a: 5, b: "oranges" }, { a: 6, b: "oranges" }, { a: 10, b: "oranges" } ];

var cache = {};
array.forEach(function(e) {
    var t = cache[e.b];
    if (t) {
        t.a = t.a > e.a ? t.a : e.a;
    } else {
        cache[e.b] = e;
    }
});

var res = Object.keys(cache).map(e => cache[e]);

document.write(JSON.stringify(res));

Upvotes: 1

RomanPerekhrest
RomanPerekhrest

Reputation: 92854

Short solution with Array.foreach and Math.max methods:

var map = {},result = [];

obj.forEach(function(v){
   (!(v['b'] in map)) ? map[v['b']] = [v['a']] : map[v['b']].push(v['a']);
});
for (var prop in map) {
    result.push({a: Math.max.apply(null, map[prop]), b: prop});
}

console.log(result);
// the output:
[
  {a:4, b:"apples"},
  {a:6, b:"bananas"},
  {a:10, b:"oranges"}
]

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max

Upvotes: 1

Rajesh
Rajesh

Reputation: 24915

You can try something like this:

Logic:

  • loop over array
  • check if value exist in the last element. (Assumption: Data is sorted based on b).
  • replace if match found, else push it.

var data = [ 
  {a:1, b:"apples"}, 
  {a:3, b:"apples"}, 
  {a:4, b:"apples"}, 
  {a:1, b:"bananas"}, 
  {a:3, b:"bananas"}, 
  {a:5, b:"bananas"}, 
  {a:6, b:"bananas"}, 
  {a:3, b:"oranges"}, 
  {a:5, b:"oranges"}, 
  {a:6, b:"oranges"}, 
  {a:10, b:"oranges"} 
]

var distinctB = data.slice(0,1);
data.forEach(function(o){
  if(distinctB[distinctB.length-1] && o.b !== distinctB[distinctB.length-1].b){
    distinctB.push(o);
  }
  else{
    distinctB[distinctB.length-1] = o;
  }
});

document.write("<pre>" + JSON.stringify(distinctB,0,4) + "</pre>");

Upvotes: 1

Andy
Andy

Reputation: 63524

Really, if your fruit names are going to be unique the best data structure is an object rather than an array of objects.

var out = arr.reduce(function (p, c) {
  var key = c.b;
  p[key] = p[key] || 0;
  if (c.a > p[key]) p[key] = c.a;
  return p;
}, {}); // { apples: 4, bananas: 6, oranges: 10 }

DEMO

Upvotes: 1

Nina Scholz
Nina Scholz

Reputation: 386604

It works with a little help from an object for the indices.

var data = [{ a: 1, b: "apples" }, { a: 3, b: "apples" }, { a: 4, b: "apples" }, { a: 1, b: "bananas" }, { a: 3, b: "bananas" }, { a: 5, b: "bananas" }, { a: 6, b: "bananas" }, { a: 3, b: "oranges" }, { a: 5, b: "oranges" }, { a: 6, b: "oranges" }, { a: 10, b: "oranges" }],
    result = function (array) {
        var r = [], o = {};
        array.forEach(function (a) {
            if (!(a.b in o)) {
                o[a.b] = r.push(a) - 1;
                return;
            }
            if (r[o[a.b]].a < a.a) {
                r[o[a.b]] = a;
            }
        });
        return r;
    }(data);

document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');

Upvotes: 1

gurvinder372
gurvinder372

Reputation: 68393

You are setting the values in result result[r.b] = r; but comparing the values in cache by doing this if (cache[r.b] && cache[r.b] > r.a) {

You need to set the values in cache rather than result and then iterate cache for your intended result.

Replace the forEach and for loop with

array.forEach(function (r) {
 if (cache[r.b] && cache[r.b] > r.a) {
   cache[r.b] = r.a;
 }
});
for (var key in cache) 
{
 result.push(result[key]);
}

Upvotes: 0

Related Questions