Best Way to Get Objects with Highest Property Valu

2019-03-04 18:34发布

问题:

I have the following multidimensional array of student objects:

var students = [
{name: "Jack", age: "NYN", attempts: 3, wrong: 2},
{name: "Phil", age: "NNNY", attempts: 4, wrong: 3},
{name: "Tom", age: "", attempts: 0, wrong: 0},
{name: "Lucy", age: "YYNY", attempts: 4, wrong: 1},
{name: "Ben", age: "NYNN", attempts: 4, wrong: 3},
{name: "Hardest", age: "NNN", attempts: 3, wrong: 3}
]

I'm trying to create an array 'peopleMostNeedingHelp' which only comprises of the students with the highest 'wrong' property values. So 'peopleMostNeedingHelp' should only contain the objects Phil, Ben and Hardest. The problem is, the way I've done it also includes the unwanted 'Jack' as he is the first comparison.

How can I create a function that only returns the people with the most wrong answers?

var s2 = "Jack:NYN,Phil:NNNY,Tom:,Lucy:YYNY,Ben:NYNN,Hardest:NNN";
var s2Arr = s2.split(','); // convert string to an array
var s2MdArr = s2Arr.map(function(e) {return e.split(':'); }); // convert to MD array
var totalWrongAnswers = 0;

for(i=0; i < s2Arr.length; i++) {
    var attempts = s2MdArr[i][1].length;
	var noWrong = (s2MdArr[i][1].match(/N/g) || []).length;
	s2MdArr[i].push(attempts); // add to array[i][2]
	s2MdArr[i].push(noWrong); // add to array[i][3]
	totalWrongAnswers += noWrong; // update total wrong
}

var s2ArrObj = s2MdArr.map(function(e) { return {name: e[0], age: e[1], attempts: e[2], wrong: e[3]} }); // create objects in MD Array

    var firstPerson = s2ArrObj[0]; // initialise so can make a comparison
    var person = firstPerson;
    var peopleMostNeedingHelp = [];
// update person to the person with the highest no. of wrong answers
function something() {
for (i = 0; i < s2ArrObj.length; i++) { // for each person
    if (s2ArrObj[i].wrong >= person.wrong) { // problem = first instance always true
        person = s2ArrObj[i]; // update person variable so can compare next person
        peopleMostNeedingHelp.push(person);
    }
}
}

something();
console.log(peopleMostNeedingHelp);

回答1:

The most straight-forward algorithm to achieve your goal would be:

  1. Find the maximum value for wrong within students array.
  2. Use filter to leave only the relevant students (with the property wrong equals the maximum).

var students = [{name: "Jack", age: "NYN", attempts: 3, wrong: 2},{name: "Phil", age: "NNNY", attempts: 4, wrong: 3},{name: "Tom", age: "", attempts: 0, wrong: 0},{name: "Lucy", age: "YYNY", attempts: 4, wrong: 1},{name: "Ben", age: "NYNN", attempts: 4, wrong: 3},{name: "Hardest", age: "NNN", attempts: 3, wrong: 3}];

// Find the maximum 'wrong' value
let maxValue = 0;
for(let student of students) {
  if(student.wrong > maxValue) {
    maxValue = student.wrong;
  }
}

// filter out the students with 'wrong' value different than the maximum
let onlyMax = students.filter(item => item.wrong == maxValue);
console.log(onlyMax);

Note all the algorithm does is iterating the array twice, resulting in run-time of O(2n) = O(n).


The more general solution allows one to find the items with maximum value of property in an objects array:

var students = [{name: "Jack", age: "NYN", attempts: 3, wrong: 2},{name: "Phil", age: "NNNY", attempts: 4, wrong: 3},{name: "Tom", age: "", attempts: 0, wrong: 0},{name: "Lucy", age: "YYNY", attempts: 4, wrong: 1},{name: "Ben", age: "NYNN", attempts: 4, wrong: 3},{name: "Hardest", age: "NNN", attempts: 3, wrong: 3}];

function filterByMax(arr, property) {
  // Find the maximum 'wrong' value
  let maxValue = 0;
  for(let item of arr) {
    if(item[property] > maxValue) {
      maxValue = item[property];
    }
  }
  
  // filter out the students with 'wrong' value different than the maximum
  return arr.filter(item => item[property] == maxValue);
}

console.log(filterByMax(students, 'wrong'));



回答2:

You could reduce the array by checking the wrong property.

var students = [{ name: "Jack", age: "NYN", attempts: 3, wrong: 2 }, { name: "Phil", age: "NNNY", attempts: 4, wrong: 3 }, { name: "Tom", age: "", attempts: 0, wrong: 0 }, { name: "Lucy", age: "YYNY", attempts: 4, wrong: 1 }, { name: "Ben", age: "NYNN", attempts: 4, wrong: 3 }, { name: "Hardest", age: "NNN", attempts: 3, wrong: 3 }],
    topWrong = students.reduce((r, o) => {
        if (!r || o.wrong > r[0].wrong) {
            return [o];
        }
        if (o.wrong === r[0].wrong) {
            r.push(o);
        }
        return r;
    }, undefined);
    
console.log(topWrong);
.as-console-wrapper { max-height: 100% !important; top: 0; }



回答3:

You have to reset the peopleMostNeedingHelp when a new student with a higher number of errors is discovered:

let mostNeedingHelp = [students[0]];

for(const student of students.slice(1)) {
  if(student.errors === mostNeedingHelp[0].errors) {
    mostNeedingHelp.push(student);
  } else if(student.errors >= mostNeedingHelp[0].errors) {
    mostNeedingHelp = [student]; // <<<<
  }
}

This can be shortified with reduce:

const mostNeedingHelp = students.slice(1).reduce((arr, student) => 
 arr[0].errors === student.errors ? arr.concat(student) : arr[0].errors < student.errors ? [student] : arr, [students[0]]);


回答4:

Your code will also fail if the "wrong" property values are in ascending order like

var students = [
    {name: "Jack", age: "NYN", attempts: 3, wrong: 2},
    {name: "Phil", age: "NNNY", attempts: 4, wrong: 3},
    {name: "Tom", age: "", attempts: 0, wrong: 0},
    {name: "Lucy", age: "YYNY", attempts: 4, wrong: 1},
    {name: "Ben", age: "NYNN", attempts: 4, wrong: 3},
    {name: "Hardest", age: "NNN", attempts: 3, wrong: 3}
    {name: "Mad", age: "NYN", attempts: 3, wrong: 5},
]

The result will include Jack, Phil, Ben, Hardest & Mad


You have to discard the previous results once you find new person with greater "wrong" value, see the snippet below...

var s2 = "Jack:NYN,Phil:NNNY,Tom:,Lucy:YYNY,Ben:NYNN,Hardest:NNN";
var s2Arr = s2.split(','); // convert string to an array
var s2MdArr = s2Arr.map(function(e) {return e.split(':'); }); // convert to MD array
var totalWrongAnswers = 0;

for(i=0; i < s2Arr.length; i++) {
    var attempts = s2MdArr[i][1].length;
	var noWrong = (s2MdArr[i][1].match(/N/g) || []).length;
	s2MdArr[i].push(attempts); // add to array[i][2]
	s2MdArr[i].push(noWrong); // add to array[i][3]
	totalWrongAnswers += noWrong; // update total wrong
}

var s2ArrObj = s2MdArr.map(function(e) { return {name: e[0], age: e[1], attempts: e[2], wrong: e[3]} }); // create objects in MD Array

    var firstPerson = s2ArrObj[0]; // initialise so can make a comparison
    var person = firstPerson;
    var peopleMostNeedingHelp = [];
// update person to the person with the highest no. of wrong answers
function something() {
    for (i = 0; i < s2ArrObj.length; i++) {
        // for each person
        if (s2ArrObj[i].wrong > person.wrong) {
            // discard previous results and create new list with single new person only
            person = s2ArrObj[i];
            // update person variable so can compare next person
            peopleMostNeedingHelp = [person];
        }
        else if (s2ArrObj[i].wrong == person.wrong) {
            // add the person to list
            person = s2ArrObj[i];
            // update person variable so can compare next person
            peopleMostNeedingHelp.push(person);
        }
    }
}

something();
console.log(peopleMostNeedingHelp);