1

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...?

2
  • 2
    That's an efficient way to do it. You only make one pass through the array and then one more pass over the results. Commented Mar 16, 2016 at 14:11
  • looks pretty good - O(n) is OK speed for such issue ) Commented Mar 16, 2016 at 14:12

7 Answers 7

2

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

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

4 Comments

You should include in your answer how and why it works.
@Ginden: "how" would be no problem, but "why" is really puzzling me ;)
This does what I need, and I can quite follow it. Smart in the Math argument: maxes[e.b] || e.a I think I go further with this one!
ah. Nice. Well actually [a] is a timestamp in my case, so always positive.
1

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>');

2 Comments

Nice also, how many comments here... :) For me this one is harder to read.
but it's pretty straight forward. and it works for unsorted data.
1

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

3 Comments

In my case, an array is preferred, because the fruit names cán be unique, but there can be new ones. Also, this data has to be sorted also. Thank you for your answer!
Just curious - if an array is preferred why did you choose an answer that returns an object? @Riël
That is a good catch. actually I just noticed now you mention that. I think my last part, doing the for key in ... loop, is going to be there anyway, for both answers. Your answer is close to my own suggestion, without the ned for caching. Helpful for sure! Thank you!
1

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>");

Comments

1

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

Comments

1

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

Comments

0

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

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.