Is there an efficient way to take a large Array of Objects and return the totals on certain properties, whilst still retaining some of the orginal data?
In our example we have an Array similar to the following:
[
{
Name: 'Bob',
Month: January 2021,
WorkCode: 'Fee1'
Hours: 20,
Salary: 400.00,
...
},
{
Name: Bob',
Month: January 2021,
WorkCode: 'Fee2'
Hours: 10,
Salary: 200.00,
...
},
{
Name: 'Bob',
Month: January 2021,
WorkCode: 'Fee3'
Hours: 5,
Salary: 100.00,
...
},
{
Name: 'Bob',
Month: February 2021,
WorkCode: 'Fee1'
Hours: 15,
Salary: 300.00,
...
},
{
Name: 'Alice',
Month: January 2021,
WorkCode: 'Fee1'
Hours: 10,
Salary: 300.00,
...
}
...
]
I want to be able to return each person in the array with the sum of their hours and salary up to a selected date (assume that the Months in the example are Date
objects).
At the moment I am using Array.filter()
and Array.some()
to give me a subset of the original data that contains all the raw data that I need.
let weekRows = data.filter((currentRow) => {
return currentRow.Month <= maxDate
})
if (this.workCodeFilter.length !== 0) {
weekRows = weekRows.filter((currentRow) => {
return this.workTypeFilter.some((f) => {
return f.WorkCode === currentRow.WorkCode
})
})
}
if (this.peopleFilter.length !== 0) {
weekRows = weekRows.filter((currentRow) => {
return this.peopleFilter.some((f) => {
return f.psRef === currentRow.ps_ref
})
})
}
I then pass the weekRows
data to a for
loop which uses findIndexByKeyValue
to lookup the Person and the WorkCode in a result array, and either add a new entry if its the first instance, or increment the totals for Hours and Salary if they already have an entry. This last stage is very slow
The end result should be totals for Salary and Hours, per person, up to the required Date.
It should also retain the additional data represented by the ...
Filters should be possible for Date (always), and optionally WorkCode and/or Person. Both WorkCode and Person can have multiple entries, hence Array.some()
Am I right that Array.reduce()
should be able to do what I am looking for, whilst also replacing the Array.filter()
and Array.some()
calls
CodePudding user response:
If you seek for Array.prototype.reduce()
, you may leverage that together with Map
:
- build up
Map
, havingName
as a key and corresponding group total object as value (with date criteria as a bail out condition); - extract array of group totals with
Map.prototype.values()
E.g.
const srcData = [{Name:"Bob",Month:"January 2021",WorkCode:"Fee1",Hours:20,Salary:400},{Name:"Bob",Month:"January 2021",WorkCode:"Fee2",Hours:10,Salary:200},{Name:"Bob",Month:"January 2021",WorkCode:"Fee3",Hours:5,Salary:100},{Name:"Bob",Month:"February 2021",WorkCode:"Fee1",Hours:15,Salary:300},{Name:"Alice",Month:"January 2021",WorkCode:"Fee1",Hours:10,Salary:300}],
strToDate = str => {
const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'November', 'December'],
[month, year] = str.split(' ')
return new Date(
year,
months.indexOf(month),
1
)
}
groupTotalTillDate = (src, maxDateStr) => [
...src
.reduce((acc,{Name, Hours, Salary, Month}) => {
if(strToDate(Month)>=strToDate(maxDateStr)) return acc
const group = acc.get(Name)
if(group){
group.Hours = Hours
group.Salary = Salary
} else {
acc.set(Name, {Name, Hours, Salary})
}
return acc
}, new Map)
.values()
]
console.log(groupTotalTillDate(srcData, 'February 2021'))
.as-console-wrapper{min-height:100%}
CodePudding user response:
Hope this is what you are looking for.
const data = [
{
Name: "Bob",
Month: new Date("01 January 2020"),
WorkCode: "Fee1",
Hours: 20,
Salary: 400.0,
},
{
Name: "Bob",
Month: new Date("01 January 2021"),
WorkCode: "Fee2",
Hours: 10,
Salary: 200.0,
},
{
Name: "Bob",
Month: new Date("01 February 2021"),
WorkCode: "Fee3",
Hours: 5,
Salary: 100.0,
},
{
Name: "Bob",
Month: new Date("01 March 2021"),
WorkCode: "Fee1",
Hours: 15,
Salary: 300.0,
},
{
Name: "Alice",
Month: new Date("01 January 2021"),
WorkCode: "Fee1",
Hours: 10,
Salary: 300.0,
},
];
const getTotals = (data, maxDate) => {
const isEmployeeAddedInAcc = (acc, item) =>
acc.find((emp) => emp.Name === item.Name);
const updateDetails = (item) => (employee) =>
employee.Name === item.Name
? {
...employee,
Salary: employee.Salary item.Salary,
Hours: employee.Hours item.Hours,
}
: item;
const salaryReducer = (acc, item) =>
isEmployeeAddedInAcc(acc,item)
? acc.map(updateDetails(item))
: [...acc, { Name: item.Name, Hours: item.Hours, Salary: item.Salary }];
return data.filter((item) => item.Month < maxDate).reduce(salaryReducer, []);
};
console.log(getTotals(data, new Date("27 February 2021")));
CodePudding user response:
You could filter first and then take the accumulation of wanted values.
const
data = [{ Name: 'Bob', Month: '2021-01', WorkCode: 'Fee1', Hours: 20, Salary: 400 }, { Name: 'Bob', Month: '2021-01', WorkCode: 'Fee2', Hours: 10, Salary: 200 }, { Name: 'Bob', Month: '2021-01', WorkCode: 'Fee3', Hours: 5, Salary: 100 }, { Name: 'Bob', Month: '2021-02', WorkCode: 'Fee1', Hours: 15, Salary: 300 }, { Name: 'Alice', Month: '2021-01', WorkCode: 'Fee1', Hours: 10, Salary: 300 }],
keys = ['Hours', 'Salary'],
zeroProps = Object.fromEntries(keys.map(k => [k, 0])),
result = data
.filter(({ Month }) => Month >= '2021-01')
.reduce((hash => (r, { Name, ...o }) => {
if (!hash[Name]) r.push(hash[Name] = { Name, ...zeroProps });
keys.forEach(k => hash[Name][k] = o[k]);
return r;
})({}), []);
console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
CodePudding user response:
Use the Array#reduce()
method to eliminate elements that have a month greater than the specified one; I would use the format YYYY-MM
. Something like:
let fhours = hours.filter(h => h.Month < Month);
Then in the following demo you would replace the variable hours
for fhours
to get the summary of the filtered hours.
let hours = [{
Name: 'Bob',
Month: '2021-01',
WorkCode: 'Fee1',
Hours: 20,
Salary: 400.00
},
{
Name: 'Bob',
Month: '2021-01',
WorkCode: 'Fee2',
Hours: 10,
Salary: 200.00
},
{
Name: 'Bob',
Month: '2021-01',
WorkCode: 'Fee3',
Hours: 5,
Salary: 100.00
},
{
Name: 'Bob',
Month: '2021-02',
WorkCode: 'Fee1',
Hours: 15,
Salary: 300.00
},
{
Name: 'Alice',
Month: '2021-01',
WorkCode: 'Fee1',
Hours: 10,
Salary: 300.00
}
];
console.log(hours.length);
console.log(hours.filter(h => h.Month < '2021-02').length);
let summary = hours.reduce((ac, cu) => ({
...ac,
[cu.Name]: {
Hours: ac[cu.Name].Hours cu.Hours,
Salary: ac[cu.Name].Salary cu.Salary
}
}), [...new Set(hours.map(h => h.Name))].reduce((a, c) => ({
...a,
[c]: {
Hours: 0,
Salary: 0
}
}), {}));
console.log(summary);