marielle
marielle

Reputation: 438

Count how many times a value occurs in a array of objects

Suppose I have:

const map1 = [
  { id: 1, res: { a: `1-a`, b: `1-b` } },
  { id: 2, res: { a: `2-a`, b: `2-b`, c: `2-c`, d: `2-d` } },
  { id: 3, res: { a: `3-a`, b: `3-b`, c: `3-c` } }
]
const map2 = [
  { map1: { 1: 'a', 2: 'c', 3: 'b' } },
  { map1: { 1: 'b', 2: 'c', 3: 'a' } },
  { map1: { 1: 'a', 2: 'a' } },
  { map1: { 1: 'a', 2: 'a', 3: 'b' } },
  { map1: { 2: 'd',         3: 'a' } },
  { map1: { 1: 'a', 2: 'c', 3: 'c' } },
  { map1: { 1: 'b', 2: 'd', 3: 'c' } },
  { map1: { 1: 'b',         3: 'a' } }
]

This is the result I would like to obtain:

const result = { 
  1: { a: 4, b: 3 }, 
  2: { a: 2, b: 0, c: 3, d: 1 }, 
  3: { a: 3, b: 2, c: 2 } 
}

result is an object whose keys are map1 ids and values are objects containing the cumulative sum. For example:

result[2] = { a: 2, b: 0, c: 3, d: 1 } because looping each object inside map2.map1 and looking at value with key 2 (2 because we are looking for result[2]) there are 2 times a, 0 times b, 3 times c and 1 time d.

I suppose I have to use reduce but how? It seems too complicate to me.. Here my starting point:

const results = map2.reduce((accumulator, current) => {
  // ??  
  return accumulator
}, [])

Upvotes: 0

Views: 95

Answers (4)

marielle
marielle

Reputation: 438

I solved in this way:

const result = map1.reduce((counterAcc, m1) => {
  const { id, res } = m1
  const resIds = Object.keys(res)
  const res2Ids = map2.map(({ map1 }) => map1[id])
  const res2IdsWithoutUndefined = res2Ids.filter(Boolean)
  
  counterAcc[id] = resIds.reduce((accumulator, resId) => {    
    accumulator[resId] = 0
    const counterBy = res2IdsWithoutUndefined.reduce((resAcc, ansId) => {
      const answerText = { text: res[ansId] }
      if (!resAcc[ansId]) {
        resAcc[ansId] = 1
      } else {
        resAcc[ansId] += 1
      }
      return resAcc;
    }, {})
    
    return { ...accumulator, ...counterBy }
  }, {})
  return counterAcc
}, {})

Upvotes: -1

Nick Parsons
Nick Parsons

Reputation: 50854

You can first create a lookup table for quick retrieval by looping over map2 to create an object which is keyed by by 1-a, 2-b, etc. where each value stores the count/occurrences of each key. Then, once you have the lookup table, you can use .map() and Object.fromEntries() on your map1 to build your resulting object using the previously build lookup table.

const map1 = [
  { id: 1, res: { a: `1-a`, b: `1-b` } },
  { id: 2, res: { a: `2-a`, b: `2-b`, c: `2-c`, d: `2-d` } },
  { id: 3, res: { a: `3-a`, b: `3-b`, c: `3-c` } }
]
const map2 = [
  { map1: { 1: 'a', 2: 'c', 3: 'b' } },
  { map1: { 1: 'b', 2: 'c', 3: 'a' } },
  { map1: { 1: 'a', 2: 'a' } },
  { map1: { 1: 'a', 2: 'a', 3: 'b' } },
  { map1: { 2: 'd',         3: 'a' } },
  { map1: { 1: 'a', 2: 'c', 3: 'c' } },
  { map1: { 1: 'b', 2: 'd', 3: 'c' } },
  { map1: { 1: 'b',         3: 'a' } }
];


const summed = map2.reduce((acc, {map1}) => {
  Object.entries(map1).forEach(([key, val]) => {
    acc[`${key}-${val}`] = (acc[`${key}-${val}`] || 0) +1;
  });
  return acc;
}, {});

const result = Object.fromEntries(map1.map(({id, res}) => [
  id,
  Object.fromEntries(Object.entries(res).map(([key, val]) => [key, summed[val] || 0]))
]));

console.log(result);

Upvotes: 1

Nina Scholz
Nina Scholz

Reputation: 386680

You could count all key/value pairs of map2 and generate the wanted result from map1.

This approach takes only one loop for every given array.

const
    map1 = [{ id: 1, res: { a: `1-a`, b: `1-b` } }, { id: 2, res: { a: `2-a`, b: `2-b`, c: `2-c`, d: `2-d` } }, { id: 3, res: { a: `3-a`, b: `3-b`, c: `3-c` } }],
    map2 = [{ map1: { 1: 'a', 2: 'c', 3: 'b' } }, { map1: { 1: 'b', 2: 'c', 3: 'a' } }, { map1: { 1: 'a', 2: 'a' } }, { map1: { 1: 'a', 2: 'a', 3: 'b' } }, { map1: { 2: 'd', 3: 'a' } }, { map1: { 1: 'a', 2: 'c', 3: 'c' } }, { map1: { 1: 'b', 2: 'd', 3: 'c' } }, { map1: { 1: 'b', 3: 'a' } }],
    temp = map2.reduce((r, { map1 }) => {
        Object.entries(map1).forEach(e => (k => r[k] = (r[k] || 0) + 1)(e.join('-')));
        return r;
    }, {}),
    result = map1.reduce((r, { id, res }) => {
        r[id] ??= {};
        Object.entries(res).forEach(([k, v]) => r[id][k] = temp[v] || 0);
        return r;
    }, {});

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Upvotes: 0

Nenad Vracar
Nenad Vracar

Reputation: 122087

You can do this as a combination of reduce, map and filter methods.

const map1 = [{"id":1,"res":{"a":"1-a","b":"1-b"}},{"id":2,"res":{"a":"2-a","b":"2-b","c":"2-c","d":"2-d"}},{"id":3,"res":{"a":"3-a","b":"3-b","c":"3-c"}}]
const map2 = [{"map1":{"1":"a","2":"c","3":"b"}},{"map1":{"1":"b","2":"c","3":"a"}},{"map1":{"1":"a","2":"a"}},{"map1":{"1":"a","2":"a","3":"b"}},{"map1":{"2":"d","3":"a"}},{"map1":{"1":"a","2":"c","3":"c"}},{"map1":{"1":"b","2":"d","3":"c"}},{"map1":{"1":"b","3":"a"}}]

const result = map1.reduce((r, { id }) => {
  r[id] = map2
    .map(({ map1 }) => map1[id])
    .filter(Boolean)
    .reduce((a, e) => {
      if (!a[e]) a[e] = 0;
      a[e] += 1
      return a
    }, {})

  return r
}, {})

console.log(result)

Upvotes: 1

Related Questions