Home > other >  Get occurrences of students from array of objects
Get occurrences of students from array of objects

Time:04-21

I have this array of students:

var studentsArray = [{
    "studentId": "1",
    "courses": ["A", "A", "B", "D", "A"],
    "classPeriods": ["Chemistry", "Chemistry", "Maths"]
  },
  {
    "studentId": "1",
    "courses": ["E", "F", "E"],
    "classPeriods": ["Biology", "Chemistry", "Biology"]
  },
  {
    "studentId": "1",
    "courses": ["A", "D", "D"],
    "classPeriods": ["Computer Science", "Computer Scienc", "Biology", "Biology"]
  },
  {
    "studentId": "1",
    "courses": ["G", "K"],
    "classPeriods": ["Computer Science", "Chemistry", "Biology", "Chemistry"]
  },
  {
    "studentId": "1",
    "courses": ["A", "B", "D"],
    "classPeriods": ["Maths", "Maths", "Biology", "Chemistry"]
  },
  {
    "studentId": "2",
    "courses": ["A", "C", "F"],
    "classPeriods": ["Arts", "Maths", "Arts", "Maths"]
  },
  {
    "studentId": "2",
    "courses": ["B", "B", "D"],
    "classPeriods": ["Arts", "Arts", "Maths", "Maths"]
  },
  {
    "studentId": "3",
    "courses": ["H", "B", "R"],
    "classPeriods": ["Biology", "Biology", "Maths", "Maths"]
  }
]

Here I need total occurrences of students with their courses and classPeriods merged.

E.g:

A single object of Student 2 should be:

{
    "studentId": "2",
    "occurrence": 2,
    "courses": ["A", "C", "F", "B", "B", "D"],
    "classPeriods": ["Arts", "Maths", "Arts", "Chemistry", "Chemistry", "Arts", "Arts", "Maths", "Maths"]
}

What I have tried so far is:

function getOccurrences(arr, key) {

  let arr2 = [];

  arr.forEach((x) => {

    if (arr2.some((val) => {
        return val[key] == x[key]
      })) {

      arr2.forEach((k) => {
        if (k[key] === x[key]) {
          k["occurrence"]  
        }
      })
    } else {

      let a = {}
      a[key] = x[key]
      a["occurrence"] = 1
      arr2.push(a);
    }
  })

  return arr2
}


var studentsArray = [{
    "studentId": "1",
    "courses": ["A", "A", "B", "D", "A"],
    "classPeriods": ["Chemistry", "Chemistry", "Maths"]
  },
  {
    "studentId": "1",
    "courses": ["E", "F", "E"],
    "classPeriods": ["Biology", "Chemistry", "Biology"]
  },
  {
    "studentId": "1",
    "courses": ["A", "D", "D"],
    "classPeriods": ["Computer Science", "Computer Scienc", "Biology", "Biology"]
  },
  {
    "studentId": "1",
    "courses": ["G", "K"],
    "classPeriods": ["Computer Science", "Chemistry", "Biology", "Chemistry"]
  },
  {
    "studentId": "1",
    "courses": ["A", "B", "D"],
    "classPeriods": ["Maths", "Maths", "Biology", "Chemistry"]
  },
  {
    "studentId": "2",
    "courses": ["A", "C", "F"],
    "classPeriods": ["Arts", "Maths", "Arts", "Maths"]
  },
  {
    "studentId": "2",
    "courses": ["B", "B", "D"],
    "classPeriods": ["Arts", "Arts", "Maths", "Maths"]
  },
  {
    "studentId": "3",
    "courses": ["H", "B", "R"],
    "classPeriods": ["Biology", "Biology", "Maths", "Maths"]
  }
]

getOccurrences(studentsArray, "studentId");

Now the issue I am facing here is that it is not returning all the properties of object and also not merging the arrays of course and classPeriods.

Can I get some help with this?

CodePudding user response:

This feels like a textbook example for reduction, but you could use any kind of looping, and preferably a Map to track id-aggregateddata pairs. Then its values() can get the actual result:

var studentsArray = [{
    "studentId": "1",
    "courses": ["A", "A", "B", "D", "A"],
    "classPeriods": ["Chemistry", "Chemistry", "Maths"]
  },
  {
    "studentId": "1",
    "courses": ["E", "F", "E"],
    "classPeriods": ["Biology", "Chemistry", "Biology"]
  },
  {
    "studentId": "1",
    "courses": ["A", "D", "D"],
    "classPeriods": ["Computer Science", "Computer Scienc", "Biology", "Biology"]
  },
  {
    "studentId": "1",
    "courses": ["G", "K"],
    "classPeriods": ["Computer Science", "Chemistry", "Biology", "Chemistry"]
  },
  {
    "studentId": "1",
    "courses": ["A", "B", "D"],
    "classPeriods": ["Maths", "Maths", "Biology", "Chemistry"]
  },
  {
    "studentId": "2",
    "courses": ["A", "C", "F"],
    "classPeriods": ["Arts", "Maths", "Arts", "Maths"]
  },
  {
    "studentId": "2",
    "courses": ["B", "B", "D"],
    "classPeriods": ["Arts", "Arts", "Maths", "Maths"]
  },
  {
    "studentId": "3",
    "courses": ["H", "B", "R"],
    "classPeriods": ["Biology", "Biology", "Maths", "Maths"]
  }
];

let stuff=[...studentsArray.reduce((res,student)=>{
  if(!res.has(student.studentId))
    res.set(student.studentId,{...student,occurrences:1});
  else{
    let s=res.get(student.studentId);
    s.courses=s.courses.concat(student.courses);
    s.classPeriods=s.classPeriods.concat(student.classPeriods);
    s.occurrences  ;
  }
  return res;
},new Map()).values()];
console.log(stuff);

CodePudding user response:

No need to create a object... you can just append occurrence to x before pushing it.

function getOccurrences(arr, key) {
  let arr2 = [];

  arr.forEach((x) => {

    if (arr2.some((val) => {
        return val[key] == x[key]
      })) {

      arr2.forEach((k) => {
        if (k[key] === x[key]) {
          k["occurrence"]  
        }
      })
    } else {

      // let a = {}
      // a[key] = x[key]
      // a["occurrence"] = 1
      // arr2.push(a);
      x["occurrence"] = 1;   // <-----
      arr2.push(x);          // <-----
    }
  })

  return arr2
}


var studentsArray = [{
    "studentId": "1",
    "courses": ["A", "A", "B", "D", "A"],
    "classPeriods": ["Chemistry", "Chemistry", "Maths"]
  },
  {
    "studentId": "1",
    "courses": ["E", "F", "E"],
    "classPeriods": ["Biology", "Chemistry", "Biology"]
  },
  {
    "studentId": "1",
    "courses": ["A", "D", "D"],
    "classPeriods": ["Computer Science", "Computer Scienc", "Biology", "Biology"]
  },
  {
    "studentId": "1",
    "courses": ["G", "K"],
    "classPeriods": ["Computer Science", "Chemistry", "Biology", "Chemistry"]
  },
  {
    "studentId": "1",
    "courses": ["A", "B", "D"],
    "classPeriods": ["Maths", "Maths", "Biology", "Chemistry"]
  },
  {
    "studentId": "2",
    "courses": ["A", "C", "F"],
    "classPeriods": ["Arts", "Maths", "Arts", "Maths"]
  },
  {
    "studentId": "2",
    "courses": ["B", "B", "D"],
    "classPeriods": ["Arts", "Arts", "Maths", "Maths"]
  },
  {
    "studentId": "3",
    "courses": ["H", "B", "R"],
    "classPeriods": ["Biology", "Biology", "Maths", "Maths"]
  }
]

let result = getOccurrences(studentsArray, "studentId");

console.log(result);

CodePudding user response:

My approach is as below:

// this function is written in Arrow syntax, and declared as a const (constant)
// since I don't expect it to change during the lifetime of the document;
// the function takes two arguments, the array through which it must search
// and the key it's looking for:
const getOccurrences = (arr, key) => {

  // our first action is to filter the supplied array in order to discard
  // unnecessary information, using Array.prototype.filter(), this iterates
  // over each Array-element, and discards any element(s) that don't return
  // true/truthy for the supplied assessment:
  return arr.filter(
    // we use destructuring assignment to retrieve the value of the
    // studentId property from the current Object of the Array of
    // Objects:
    ({
      studentId
      // here we check that the current studentId, parsed as a number
      // (in base 10) is exactly equal to the similarly parsed value
      // of the key passed into the function:
    }) => parseInt(studentId, 10) === parseInt(key, 10)
    // we then pass over to Array.prototype.reduce, to reduce the Array to
    // to another data-structure, here an Object:
    // we have three arguments passed in:
    // acc: the accumulator, the Object that we're creating as we iterate,
    // the property-values of the named properties 'courses' and 'classPeriods'
    // which are retrieved from the current Array-element of the Objects that
    // remain in the Array, and then
    // index: the index of the current Array-element in the remaining Array:
  ).reduce((acc, {
    courses,
    classPeriods
  }, index) => {
    // if the 'courses' property doesn't exist on the accumulator:
    if (!acc.courses) {
      // we define that property, and set it's initial value to be
      // equal to the courses property-value of the current Object:
      acc.courses = courses;
    } else {
      // otherwise we set the value to a new Array comprised of the
      // existing values, with the new values from the current Object:
      acc.courses = [...acc.courses, ...courses];
    }
    // this next step is exactly the same as above:
    if (!acc.classPeriods) {
      acc.classPeriods = classPeriods;
    } else {
      acc.classPeriods = [...acc.classPeriods, ...classPeriods];
    }
    // here we set the occurrences value to the index of the
    // current Array-element, and increment by 1 (since at
    // index 0, the first Array-element, the occurrence is
    // 1, not zero):
    acc.occurences = index   1;
    // returning the accumulator Object for the next iteration:
    return acc;
    // defining the accumulator Object, and setting its studentId
    // property to be equal to the key we're looking for:
  }, {
    studentId: key,
  });
};

// your existing Array of Objects:
const studentsArray = [{
      "studentId": "1",
      "courses": ["A", "A", "B", "D", "A"],
      "classPeriods": ["Chemistry", "Chemistry", "Maths"]
    },
    {
      "studentId": "1",
      "courses": ["E", "F", "E"],
      "classPeriods": ["Biology", "Chemistry", "Biology"]
    },
    {
      "studentId": "1",
      "courses": ["A", "D", "D"],
      "classPeriods": ["Computer Science", "Computer Scienc", "Biology", "Biology"]
    },
    {
      "studentId": "1",
      "courses": ["G", "K"],
      "classPeriods": ["Computer Science", "Chemistry", "Biology", "Chemistry"]
    },
    {
      "studentId": "1",
      "courses": ["A", "B", "D"],
      "classPeriods": ["Maths", "Maths", "Biology", "Chemistry"]
    },
    {
      "studentId": "2",
      "courses": ["A", "C", "F"],
      "classPeriods": ["Arts", "Maths", "Arts", "Maths"]
    },
    {
      "studentId": "2",
      "courses": ["B", "B", "D"],
      "classPeriods": ["Arts", "Arts", "Maths", "Maths"]
    },
    {
      "studentId": "3",
      "courses": ["H", "B", "R"],
      "classPeriods": ["Biology", "Biology", "Maths", "Maths"]
    }
  ],

  // a simple function to display the results on screen, this is again an Arrow
  // function, and a constant:
  displayResults = (results) => {
    // we get the class-names we're looking for from the passed-in results Object's
    // property-keys, using Object.keys() to retrieve those keys as an Array:
    let classes = Object.keys(results);
    // iterating over the Array of class-names, using Array.prototype.forEach():
    classes.forEach(
      // c is the current key, which is the class-name we're looking for, using
      // a template-literal and document.querySelector, and then we're updating
      // the text-content of that element:
      (c) => document.querySelector(`.${c}`).textContent =
      // if the current property-value of the results Object is an Array
      // (Array.isArray() returns a Boolean, true or false) we join the
      // Array-elements together using Array.prototype.join() to spread
      // out the elements in the string. Otherwise, we simply return the value:
      Array.isArray(results[c]) ? results[c].join(', ') : results[c]
    )
  }

// retrieving the <select> element via its id of 'studentNumber', and binding the
// anonymous Arrow function as the event-handler for the 'change' event:
document.querySelector('#studentNumber').addEventListener('change', (evt) => {
  // the anonymous function calls the displayResults() function, which
  displayResults(
    // receives the result from the getOccurrences(), function,
    // passing in the studentsArray, and the value of the <select> element:
    getOccurrences(studentsArray, evt.currentTarget.value)
  );
});
*,
 ::before,
 ::after {
  box-sizing: border-box;
  font-family: Montserrat, sans-serif;
  margin: 0;
  padding: 0;
}

form {
  width: clamp(30em, 70vw, 1200px);
  margin-block: 1em;
  margin-inline: auto;
}

ul,
li {
  list-style-type: none;
}

ul {
  margin-block-start: 1em;
  margin-inline-start: 1em;
}

li {
  color: #696;
  display: flex;
  gap: 0.5em;
  justify-content: start;
  min-height: 2em;
}

li::before {
  color: #363;
  font-weight: bold;
  content: attr(class)": ";
  text-transform: capitalize;
}
<form action="">
  <fieldset>
    <legend>Student details</legend>
    <label>
      <span>Details for Student: </span>
      <select id="studentNumber">
        <option value="-1" disabled selected>Please choose</option>
        <option value="1">Student 1</option>
        <option value="2">Student 2</option>
        <option value="3">Student 3</option>
      </select>
    </label>
    <div >
      <ul>
        <li ></li>
        <li ></li>
        <li ></li>
        <li ></li>
      </ul>
    </div>
  </fieldset>
</form>

CodePudding user response:

You can use Array#reduce and Array#map as in the following demo:

NOTE: This combines data for all studentId values; I misunderstood the question. It's now clear to me that you want the function to return the result for one ID. See next demo.

const 
studentsArray = [{ "studentId": "1", "courses": ["A", "A", "B", "D", "A"], "classPeriods": ["Chemistry", "Chemistry", "Maths"] }, { "studentId": "1", "courses": ["E", "F", "E"], "classPeriods": ["Biology", "Chemistry", "Biology"] }, { "studentId": "1", "courses": ["A", "D", "D"], "classPeriods": ["Computer Science", "Computer Scienc", "Biology", "Biology"] }, { "studentId": "1", "courses": ["G", "K"], "classPeriods": ["Computer Science", "Chemistry", "Biology", "Chemistry"] }, { "studentId": "1", "courses": ["A", "B", "D"], "classPeriods": ["Maths", "Maths", "Biology", "Chemistry"] }, { "studentId": "2", "courses": ["A", "C", "F"], "classPeriods": ["Arts", "Maths", "Arts", "Maths"] }, { "studentId": "2", "courses": ["B", "B", "D"], "classPeriods": ["Arts", "Arts", "Maths", "Maths"] }, { "studentId": "3", "courses": ["H", "B", "R"], "classPeriods": ["Biology", "Biology", "Matchs", "Maths"] } ],

getOccurrences = (arr, key) => Object.entries(
  arr.reduce((acc,cur) => 
    acc[cur[key]] ? 
    ({...acc, [cur[key]]: {
        occurrence: acc[cur[key]].occurrence   1,
        courses: acc[cur[key]].courses.concat(cur.courses),
        classPeriods: acc[cur[key]].classPeriods.concat(cur.classPeriods)
     }}) :
    ({...acc, [cur[key]]: {
        occurrence: 1,
        courses:cur.courses, 
        classPeriods:cur.classPeriods
    }}), {}
  )
)
.map(
    ([studentId,courseInfo]) =>
    ({studentId, ...courseInfo})
);

console.log( getOccurrences(studentsArray,"studentId") );

Single ID

The function in this demo returns results for one value of studentId supplied as key as you intended:

const 
studentsArray = [{ "studentId": "1", "courses": ["A", "A", "B", "D", "A"], "classPeriods": ["Chemistry", "Chemistry", "Maths"] }, { "studentId": "1", "courses": ["E", "F", "E"], "classPeriods": ["Biology", "Chemistry", "Biology"] }, { "studentId": "1", "courses": ["A", "D", "D"], "classPeriods": ["Computer Science", "Computer Scienc", "Biology", "Biology"] }, { "studentId": "1", "courses": ["G", "K"], "classPeriods": ["Computer Science", "Chemistry", "Biology", "Chemistry"] }, { "studentId": "1", "courses": ["A", "B", "D"], "classPeriods": ["Maths", "Maths", "Biology", "Chemistry"] }, { "studentId": "2", "courses": ["A", "C", "F"], "classPeriods": ["Arts", "Maths", "Arts", "Maths"] }, { "studentId": "2", "courses": ["B", "B", "D"], "classPeriods": ["Arts", "Arts", "Maths", "Maths"] }, { "studentId": "3", "courses": ["H", "B", "R"], "classPeriods": ["Biology", "Biology", "Matchs", "Maths"] } ],

getOccurrences = (arr, key) => {
    let d = arr.filter(({studentId}) => studentId === key);
    return {
        studentId: key,
        occurrence: d.length,
        courses: [].concat(...d.map(c => c.courses)),
        classPeriods: [].concat(...d.map(c => c.classPeriods))
    }
};

console.log( getOccurrences(studentsArray,"1") );

CodePudding user response:

If you are concerned in performance, this approach would do a good job. Look that instead of using the concat method, which returns a new array, or using the array findIndex method (which involves iteration through the array), I'm using a subIndex array to save already loaded subObjects indexes. Hope it helps!

const studentsArray = [{ "studentId": "1", "courses": ["A", "A", "B", "D", "A"], "classPeriods":["Chemistry","Chemistry","Maths"] }, {"studentId": "1", "courses": ["E", "F", "E"], "classPeriods": ["Biology", "Chemistry", "Biology"] }, {"studentId": "1", "courses": ["A", "D", "D"], "classPeriods": ["Computer Science", "Computer Scienc", "Biology", "Biology"] }, {"studentId": "1", "courses": ["G", "K"], "classPeriods": ["Computer Science", "Chemistry", "Biology", "Chemistry"] }, {"studentId": "1", "courses": ["A", "B", "D"], "classPeriods": ["Maths", "Maths", "Biology", "Chemistry"] }, {"studentId": "2", "courses": ["A", "C", "F"], "classPeriods": ["Arts", "Maths", "Arts", "Maths"] }, {"studentId": "2", "courses": ["B", "B", "D"], "classPeriods": ["Arts", "Arts", "Maths", "Maths"] }, {"studentId": "3", "courses": ["H", "B", "R"], "classPeriods": ["Biology", "Biology", "Maths", "Maths"] } ];

// Array where loaded subindexes would be saved
// Example:
// (StudentId: 1) would be in the index '0' of the 'result' array
// (StudentId: 2) would be in the index '1' of the 'result' array
// (StudentId: 3) would be in the index '2' of the 'result' array
// So, 'subIndexes' would be equal to { '1': 0, '2': 1, '3': 2}
const subIndexes = {};
// Reduce the 'studentsArray', with an array accumulator '[]'
const result = studentsArray.reduce((acc, item) => {
  // Let's check if the subIndex 'item.studentId' was loaded.
  if (subIndexes[item.studentId] === undefined) {
    // If the subIndex is not loaded (undefined), it means that
    // we need to push the new object into the accumulator.
    // Also, remember that the 'subIndexes' array at key 'item.studentId'
    // must have the index of the new subObject. Remember: `{ '1': 0, '2': 1, '3': 2}`
    subIndexes[item.studentId] = 
      // Push the new object
      acc.push({
        ...item,
        occurrence: 1
        // Subtract 1, because the 'push' method returns the new lenght
        // of the array, and we need the index
      }) - 1;
      // In case the subIndex was already loaded
  } else {
    // Just use the subIndex value to modify the occurences number,
    // and push the new values of 'courses' and 'classPeriods'
    // Look that I'm using the spread operator to break
    // the reference to the original array,. I you don't break it,
    // it may cause unexpected results later.
      acc[subIndexes[item.studentId]].occurrence;
    acc[subIndexes[item.studentId]].courses.push(...item.courses);
    acc[subIndexes[item.studentId]].classPeriods.push(...item.classPeriods);
  }
  return acc;
  // initial value of the accumlator, empty array
}, []);

console.log(result);

Inline

const studentsArray = [{ "studentId": "1", "courses": ["A", "A", "B", "D", "A"], "classPeriods":["Chemistry","Chemistry","Maths"] }, {"studentId": "1", "courses": ["E", "F", "E"], "classPeriods": ["Biology", "Chemistry", "Biology"] }, {"studentId": "1", "courses": ["A", "D", "D"], "classPeriods": ["Computer Science", "Computer Scienc", "Biology", "Biology"] }, {"studentId": "1", "courses": ["G", "K"], "classPeriods": ["Computer Science", "Chemistry", "Biology", "Chemistry"] }, {"studentId": "1", "courses": ["A", "B", "D"], "classPeriods": ["Maths", "Maths", "Biology", "Chemistry"] }, {"studentId": "2", "courses": ["A", "C", "F"], "classPeriods": ["Arts", "Maths", "Arts", "Maths"] }, {"studentId": "2", "courses": ["B", "B", "D"], "classPeriods": ["Arts", "Arts", "Maths", "Maths"] }, {"studentId": "3", "courses": ["H", "B", "R"], "classPeriods": ["Biology", "Biology", "Maths", "Maths"] } ];

const getOcurrences = (b, a = {}) => b.reduce((s,e)=>(void 0===a[e.studentId]?a[e.studentId]=s.push({...e,occurrence:1})-1:(  s[a[e.studentId]].occurrence,s[a[e.studentId]].courses.push(...e.courses),s[a[e.studentId]].classPeriods.push(...e.classPeriods)),s),[]);

console.log(getOcurrences(studentsArray));

  • Related