简体   繁体   中英

How can I remove an object from array of objects based on max value in javascript

I've got an array of objects that looks like this:

[ { person: 'Fred', scoreTotal: 29 },
{ person: 'Alice', scoreTotal: 34 },
{ person: 'Alice', scoreTotal: 22 },
{ person: 'Mary', scoreTotal: 14 },
{ person: 'Bob', scoreTotal: 33 },
{ person: 'Bob', scoreTotal: 13 },
{ person: 'Bob', scoreTotal: 22 },
{ person: 'Joe', scoreTotal: 28 }]

and where there are multiple objects for a given person -> I want to keep the top "X". For example:

a. top 1 result for a person Result would look like this:

    [ { person: 'Fred', scoreTotal: 29 },
    { person: 'Alice', scoreTotal: 34 },
    { person: 'Mary', scoreTotal: 14 },
    { person: 'Bob', scoreTotal: 33 },
    { person: 'Joe', scoreTotal: 28 }]

b. top 2 results for a person Result would look like this:

   [ { person: 'Fred', scoreTotal: 29 },
  { person: 'Alice', scoreTotal: 34 },
   { person: 'Alice', scoreTotal: 22 },
   { person: 'Mary', scoreTotal: 14 },
   { person: 'Bob', scoreTotal: 33 },
   { person: 'Bob', scoreTotal: 22 },
   { person: 'Joe', scoreTotal: 28 }]

Is there a way to achieve this using something like Lodash?

I think I'm heading in the right direction with this but not quite there yet:

for (var i = 0; i < t.length - 1; i++) {
if (
  t[i].golfer === t[i + 1].golfer &&
  t[i].scoreTotal < t[i + 1].scoreTotal
) {
  delete t[i];
}

}

// remove the "undefined entries"

t = t.filter(function(el) {
    return typeof el !== "undefined";
});
console.log(t);

You can do this pretty simply without Underscore/Lodash. Sort the array highest to lowest by score. Then use Array.filter . As filter goes through the array keep track of how many times you see each person and start return false after the top number you want has been reached for a person.

 let arr = [ { person: 'Fred', scoreTotal: 29 },{ person: 'Alice', scoreTotal: 34 },{ person: 'Alice', scoreTotal: 22 },{ person: 'Mary', scoreTotal: 14 },{ person: 'Bob', scoreTotal: 33 },{ person: 'Bob', scoreTotal: 13 },{ person: 'Bob', scoreTotal: 22 },{ person: 'Joe', scoreTotal: 28 }] function filterTop(arr, top) { let counts = {} return [...arr].sort((a, b) => b.scoreTotal - a.scoreTotal) .filter(score => (counts[score.person] = (counts[score.person] || 0) +1 ) <= top) } console.log(filterTop(arr, 1)) console.log(filterTop(arr, 2)) 

Using only ES6, you can use 2 reduce s. First is to group the arrays. the second is to get the number of top per group.

 let arr = [{"person":"Fred","scoreTotal":29},{"person":"Alice","scoreTotal":34},{"person":"Alice","scoreTotal":22},{"person":"Mary","scoreTotal":14},{"person":"Bob","scoreTotal":33},{"person":"Bob","scoreTotal":13},{"person":"Bob","scoreTotal":22},{"person":"Joe","scoreTotal":28}]; let getTop = (a, t) => { //Parameters a = array. t = top return Object.values(a.reduce((c, v) => { //Group the array using the person property c[v.person] = c[v.person] || []; c[v.person].push(v); return c; }, {})).reduce((c, v) => { v.sort((a, b) => b.scoreTotal - a.scoreTotal); //Sort the sub array c = c.concat(v.slice(0, t)); //Add the top to accumulator return c; }, []); } let result1 = getTop(arr, 1); //Get top 1 let result2 = getTop(arr, 2); //Get top 2 console.log('Top 1', result1); console.log('Top 2', result2); 

Here is a solution with lodash...

function pickTopX(data, x) {
  let grouped = _.groupBy(data, 'person');
  let sorted = _.sortBy(grouped, 'scoreTotal');
  let sliced = _.map(sorted, function(pair) {
    return _.take(pair, x)
  });
  let result = _.flatten(sliced);
  return result;
}

This does exactly what you want in the exact order you want using the exact library you're trying to use (lodash). I broke the process down into the following steps:

Step 1: group the data. There should be a an array containing all "Bob" records, and another array containing all "Joe" records, etc. I decided to go a step further and only store the scoreTotal rather than the entire record.

Step 2: loop through each of these arrays, sort them in descending order, and slice to the desired "top" results.

Step 3: filter the original data, using our previous findings and only include a record in the results if we have an exact scoreTotal match for a specific person, and only once per such score, removing it from our temporary array along the way so we don't get duplicate records and return more records than the "top" records desired.

 // The magical function function showTopResults(input, topResults) { // Step one: group like data together var groupedData = _.reduce(input, function(result, value, key) { if(typeof result[value.person] == 'undefined'){ result[value.person] = []; } result[value.person].push(value.scoreTotal); return result; }, {}); // Step two: loop through person keys, sort it, then grab only the first "x" elements _.forEach(groupedData, function(value, key) { value = value.sort(function(a,b){ var n = b - a; return n ? n < 0 ? -1 : 1 : 0}); // first element is largest groupedData[key] = value.slice(0, topResults); // we only want first x results, so we get largest only }); // Step three: filter our elements only where we have a match var filterResults = _.filter(input,function(o){ var idx = _.indexOf(groupedData[o.person],o.scoreTotal); if( idx > -1) { groupedData[o.person].splice(idx, 1); // remove element so we don't get multiple elements of equal value return true; // We have a match } else { return false; // not a match } }); return filterResults; } // Our input var input = [ { person: 'Fred', scoreTotal: 29 }, { person: 'Alice', scoreTotal: 34 }, { person: 'Alice', scoreTotal: 22 }, { person: 'Mary', scoreTotal: 14 }, { person: 'Bob', scoreTotal: 33 }, { person: 'Bob', scoreTotal: 13 }, { person: 'Bob', scoreTotal: 22 }, { person: 'Joe', scoreTotal: 28 } ]; // Tests using our magical function console.log(showTopResults(input, 1)); console.log(showTopResults(input, 2)); 
 <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.10/lodash.min.js"></script> 

Not exactly what you asked but worth considering:

The best way to handle this would be to restructure your data

 var people = {}, personArray = [ { person: 'Fred', scoreTotal: 29 }, { person: 'Alice', scoreTotal: 34 }, { person: 'Alice', scoreTotal: 22 }, { person: 'Mary', scoreTotal: 14 }, { person: 'Bob', scoreTotal: 33 }, { person: 'Bob', scoreTotal: 13 }, { person: 'Bob', scoreTotal: 22 }, { person: 'Joe', scoreTotal: 28 } ]; //loop person Array to group array by person //and sort their top scores from best to worst for(var i = 0; i < personArray.length; i++){ var person = personArray[i].person, score = personArray[i].scoreTotal, scores = people[person] || [], pushed = false; if(scores.length){ for(var n = 0; n < scores.length; n++){ if(score > scores[n]){ pushed = true; scores.splice(n, 0, score); break; } } } if(!pushed) scores.push(score); people[person] = scores; } console.log(people); //return top `n` scores for each person from best to worst function topScores(nScores){ var result = []; for(var name in people){ if(people.hasOwnProperty(name)){ for(var r = 0; ((r < people[name].length) && (r < nScores)); r++){ result.push({person: name, scoreTotal: people[name][r]}); } } } return result } console.log(topScores(2)) console.log(topScores(3)) console.log(topScores(1)) 

Reduce the array to an object, using the person prop as key. For each person add to object, if key doesn't exists or if scoreTotal is greater than current scoreTotal . Convert back to array using Object.values() :

 const data = [{"person":"Fred","scoreTotal":29},{"person":"Alice","scoreTotal":34},{"person":"Alice","scoreTotal":22},{"person":"Mary","scoreTotal":14},{"person":"Bob","scoreTotal":33},{"person":"Bob","scoreTotal":13},{"person":"Bob","scoreTotal":22},{"person":"Joe","scoreTotal":28}]; const result = Object.values(data.reduce((r, p) => { if(!r[p.person] || r[p.person].scoreTotal < p.scoreTotal) { r[p.person] = p; } return r; }, {})); console.log(result); 

This isn't a full answer, just a tip. Don't use delete array[index] , use array.splice . See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice for more info.

The next suggestion is to clarify whether you actually need to modify the array, or just get a new array without some of the original array's data. If you do not need to modify the original, it is far better to just use a function like reduce, or use a for loop to selectively copy items into the new array.

Using underscore: https://underscorejs.org/

 const db = [ { person: 'Fred', scoreTotal: 29 }, { person: 'Fred', scoreTotal: 10 }, { person: 'Fred', scoreTotal: 2 }, { person: 'Alice', scoreTotal: 34 }, { person: 'Alice', scoreTotal: 5 }, { person: 'Alice', scoreTotal: 15 }, { person: 'Alice', scoreTotal: 40 }, { person: 'Mary', scoreTotal: 23 }, { person: 'Mary', scoreTotal: 32 }, { person: 'Mary', scoreTotal: 98 }, { person: 'Mary', scoreTotal: 4 }, { person: 'Bob', scoreTotal: 70 }, { person: 'Bob', scoreTotal: 65 }, { person: 'Bob', scoreTotal: 35 }, { person: 'Bob', scoreTotal: 5 }, { person: 'Joe', scoreTotal: 28 }]; const nOfItens = 2; const persons = _.map(db, item => item.person); const names = _.uniq(persons); let newList = []; for (var i = 0; names.length > i; i++) { let filter = _.filter(db, person => person.person === names[i]); let sort = _.sortBy(filter, num => -num.scoreTotal); let items = sort.splice(0, nOfItens) newList = [...newList, ...items] } console.log(newList); 
 <script src="https://underscorejs.org/underscore-min.js"></script> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM