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