the_people1
the_people1

Reputation: 35

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

Answers (5)

Siva Kondapi Venkata
Siva Kondapi Venkata

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

Nick Parsons
Nick Parsons

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

Nenad Vracar
Nenad Vracar

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

cross19xx
cross19xx

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

Nina Scholz
Nina Scholz

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

Related Questions