FabZbi
FabZbi

Reputation: 548

How to filter array of objects by duplicate and property?

I have a array like this:

let data = [
    {
        name: 'foo',
        score: 10,
    },
    {
        name: 'bar',
        score: 20
    },
    {
        name: 'foo',
        score: 15,
    },
];

Now this represents a track records of scores by player. I want to get the highest score of each player. How can I achieve this? I tried different combinations of filters and I can't find a way without iterating multiple times over the array.

Upvotes: 0

Views: 1041

Answers (6)

kuroi neko
kuroi neko

Reputation: 8671

What is the problem with iterating over the array to begin with? God doesn't kill a kitten each time you solve a problem in JavaScript without calling array.filter or array.reduce, you know :)

Here is another deliciously impure solution (sorry kitties, nothing personal) taking advantage of JavaScript's marvellous plasticity (using an object like an associative array)

var data = [
  { name: 'foo', score: 10 },
  { name: 'bar', score: 20 },
  { name: 'foo', score: 15 },
];

var high_score = {};
for (record of data) { // note it's the ES6 "of", not the ill-fated legacy "in" !!!
   if ((high_score[record.name] ?? 0) < record.score) { 
      high_score[record.name] = record.score;
   }
}


// "high_score" is just an object with a property named after each player
console.log(high_score)
>>> Object { foo: 15, bar: 20 }

// This object can be accessed like an associative array (a circa 1995
// ancestor of a dictionary, if you like), in a pretty intuitive way:    
console.log(high_score["foo"])    
>>> 15
console.log(high_score["bar"])
>>> 20

Upvotes: 0

dacodr
dacodr

Reputation: 167

You can solve this with reduce() or you can do a classic solving pattern.

With Reduce()

const data = [{ name: 'foo', score: 10 }, { name: 'bar', score: 20 }, { name: 'foo', score: 15 } ];
const highsObj = data.reduce((acc, currentItem) => {
    if (!acc[currentItem.name] || acc[currentItem.name].score < currentItem.score) {
        acc[currentItem.name] = currentItem;
    }
    return acc;
}, {})

// As an array
const highsArray = Object.values(highsObj);
console.log(highsArray);

Classic pattern

const data = [{ name: 'foo', score: 10 }, { name: 'bar', score: 20 }, { name: 'foo', score: 15 } ];
const highScores = {};
for (const item of data) {
    const { name, score } = item;
    const currentValue = highScores[name];
    if (!currentValue || currentValue.score < score) {
        highScores[name] = item;
    }
}

// As an array
const highScoresArray = Object.values(highScores);
console.log(highScoresArray);

Upvotes: 1

it-xp
it-xp

Reputation: 77

let data = [
  {
    name: "foo",
    score: 10,
  },
  { name: "bar", 
    score: 20 },
  {
    name: "foo",
    score: 15,
  },
];

const highestIndex = data.reduce(result, {name, score}) => {
  result[name] = !result[name] || score > result[name] 
  ? score 
  : result[name];
  return result;
}, {})

if you need result in same structure as data then just redeem result to array using Object.entries mapping:

const highestScores = 
  Object
  .entries(highestIndex)
  .map(([name, score]) => ({name, score}))

Upvotes: 1

Kinglish
Kinglish

Reputation: 23664

Reduce() is perfect for this kind of thing.

let data = [{
    name: 'foo',
    score: 10,
  },
  {
    name: 'bar',
    score: 20
  },
  {
    name: 'foo',
    score: 15,
  },
];

let hs = data.reduce((b, a) => {
    let i = b.findIndex(e => e.name === a.name)
    if (i > -1) b[i].score = Math.max(b[i].score, a.score)
    else b.push(a)
    return b;
  }, []);
console.log(hs)

Upvotes: 1

Kelvin Schoofs
Kelvin Schoofs

Reputation: 8718

I'd suggest something like this:

const data = [
    {
        name: 'foo',
        score: 10,
    },
    {
        name: 'bar',
        score: 20
    },
    {
        name: 'foo',
        score: 15,
    },
];

// We'll keep track of the max scores here
const maxScore = {};
// Iterating with destructuring to directly access name/score
for (const { name, score } of data) {
    const currentScore = maxScore[name];
    if (!currentScore) {
        // No score saved yet, save this one
        maxScore[name] = score;
    } else if (currentScore < score) {
        // This score is higher than the saved one
        maxScore[name] = score;
    } else {
        // Score is lower (or equal) so do nothing
    }
}
console.log(maxScore);

// If you want to convert it back to an array:
const list = Object.entries(maxScore).map(
    // Object.entries returns [key, value][]
    // so we map it, extract the key/value (name/score) from each
    // pair and create an object from it which we return
    ([name, score]) => ({ name, score }));
console.log(list);

Upvotes: 1

mr rogers
mr rogers

Reputation: 3260

You can use reduce for this.

const highScores = data.reduce((memo, entry) => {
  if (!memo[entry.name] || memo[entry.name] < entry.score) {
    memo[entry.name]=entry.score
  }
  return memo; 
},{})

Upvotes: 2

Related Questions