Reputation: 35
I have an array of objects:
[
{'year': 2019, 'a_academic': 3, 'f_security': 4, ..., 'k_services': 3},
{'year': 2019, 'a_academic': 2, 'f_security': 2, ..., 'k_services': 4},
{'year': 2019, 'a_academic': 3, 'f_security': 3, ..., 'k_services': 1},
...
{'year': 2019, 'a_academic': 3, 'f_security': 3, ..., 'k_services': 3},
]
How to count multiple properties values, then grouping it, and save it in a new object, e.g.:
{
'a_academic': {
4: 0,
3: 3,
2: 1,
1: 0
},
'f_security': {
4: 1,
3: 2,
2: 1,
1: 0
},
...,
'k_services': {
4: 1,
3: 2,
2: 0,
1: 1
}
}
I'm able to do it using reduce and manually accessing the key, but only for one property:
let count = array.reduce((res, cur) => {
res[cur.a_academic] = res[cur.a_academic] ? res[cur.a_academic] + 1 : 1;
return res;
}, {});
console.log(count);
Result:
{
3: 3,
2: 1
}
How to implement this efficiently so it works for all other properties, without manually accessing it?
Upvotes: 2
Views: 1911
Reputation: 11001
Just add Object.keys
loop for your logic to access keys dynamically.
const array = [
{ year: 2019, a_academic: 3, f_security: 4, k_services: 3 },
{ year: 2019, a_academic: 2, f_security: 2, k_services: 4 },
{ year: 2019, a_academic: 3, f_security: 3, k_services: 1 },
{ year: 2019, a_academic: 3, f_security: 3, k_services: 3 }
];
let count = array.reduce((res, cur) => {
Object.keys(cur)
.filter(key => key.includes("_"))
.forEach(key => {
const obj = res[key] || {};
res[key] = {
...obj,
[cur[key]]: (obj[cur[key]] || 0) + 1
};
});
return res;
}, {});
console.log(count);
Upvotes: 0
Reputation: 50759
You can grab the keys from one of your objects (excluding year
), and then map each key to a tally array. For example, a_academic
has the value array of [3, 2, 3, 3]
which is mapped to [0, 0, 1, 3, 0]
. Using this total
array, you can use Object.assign()
to assign its indexes to an object, which you can use for each group like so:
const arr = [
{'year': 2019, 'a_academic': 3, 'f_security': 4, 'k_services': 3},
{'year': 2019, 'a_academic': 2, 'f_security': 2, 'k_services': 4},
{'year': 2019, 'a_academic': 3, 'f_security': 3, 'k_services': 1},
{'year': 2019, 'a_academic': 3, 'f_security': 3, 'k_services': 3},
];
const groupArr = ([{year, ...first}, ...arr]) => {
const ent = Object.entries(first);
const gr = ent.map(([key, v]) => [v, ...arr.map(o => o[key])]);
const maxN = Math.max(...gr.flat());
const minN = Math.min(...gr.flat());
const total = gr.map(arr =>
arr.reduce((tally, n) => (tally[n]++, tally), Array(minN).concat(Array(maxN).fill(0)))
);
return Object.assign({}, ...ent.map(([k], i) => ({[k]: Object.assign({}, total[i])})));
}
console.log(groupArr(arr));
Upvotes: 0
Reputation: 122057
You could define an array of keys that you want to count and then use reduce
method with nested forEach
loop on Object.entries
and count the occurring values for each of those defined keys.
const data = [
{'year': 2019, 'a_academic': 3, 'f_security': 4, 'k_services': 3},
{'year': 2019, 'a_academic': 2, 'f_security': 2, 'k_services': 4},
{'year': 2019, 'a_academic': 3, 'f_security': 3, 'k_services': 1},
{'year': 2019, 'a_academic': 3, 'f_security': 3, 'k_services': 3},
]
const props = ['a_academic', 'f_security', 'k_services']
const result = data.reduce((r, e) => {
Object.entries(e).forEach(([k, v]) => {
if (props.includes(k)) {
if (!r[k]) r[k] = {}
r[k][v] = (r[k][v] || 0) + 1
}
})
return r;
}, {})
console.log(result)
To fill empty values in each object you would have to first find min and max values for the whole data but also for only defined props.
const data = [
{'year': 2019, 'a_academic': 3, 'f_security': 4, 'k_services': 3},
{'year': 2019, 'a_academic': 2, 'f_security': 2, 'k_services': 4},
{'year': 2019, 'a_academic': 3, 'f_security': 3, 'k_services': 1},
{'year': 2019, 'a_academic': 3, 'f_security': 3, 'k_services': 3},
]
const props = ['a_academic', 'f_security', 'k_services']
const values = data.map(e => {
return Object.entries(e)
.filter(([k]) => props.includes(k))
.map(([k, v]) => v)
}).flat()
const max = Math.max(...values)
const min = Math.min(...values)
const result = data.reduce((r, e) => {
Object.entries(e).forEach(([k, v]) => {
if (props.includes(k)) {
if (!r[k]) {
r[k] = {}
for (let i = min; i <= max; i++) {
r[k][i] = 0
}
}
r[k][v] += 1
}
})
return r;
}, {})
console.log(result)
Upvotes: 2
Reputation: 3487
What you have done is to use the value of cur.academic
instead of the key name. To better explain this, consider this
const obj = { alpha: 20, beta: 30, gamma: 40 };
console.log(obj['alpha']); // outputs 20
What you can do is this:
const array = [
{'year': 2019, 'a_academic': 3, 'f_security': 4, 'k_services': 3},
{'year': 2019, 'a_academic': 2, 'f_security': 2, 'k_services': 4},
{'year': 2019, 'a_academic': 3, 'f_security': 3, 'k_services': 1},
{'year': 2019, 'a_academic': 3, 'f_security': 3, 'k_services': 3},
];
const excludedKeys = ['year'];
let count = array.reduce((res, curr) => {
// Select the keys you are going to use and exclude the ones you won't
const keys = Object.keys(curr).filter(key => !excludedKeys.includes(key));
keys.forEach(key => {
res[key] = res[key] ? res[key] + 1 : 1;
});
return res;
}, {});
console.log(count);
Upvotes: 0
Reputation: 386654
You need a nested grouping by omitting year
from the objects.
var data = [{ year: 2019, a_academic: 3, f_security: 4, k_services: 3 }, { year: 2019, a_academic: 2, f_security: 2, k_services: 4 }, { year: 2019, a_academic: 3, f_security: 3, k_services: 1 }, { year: 2019, a_academic: 3, f_security: 3, k_services: 3 }],
grouped = data.reduce((r, { year, ...o }) => {
Object.entries(o).forEach(([k, v]) => {
r[k] = r[k] || {};
r[k][v] = (r[k][v] || 0) + 1;
});
return r;
}, {});
console.log(grouped);
Upvotes: 1