简体   繁体   中英

How to count and group multiple properties values in array of objects?

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, eg:

{
  '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?

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)

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

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

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM