1

I'm stucked and need help. I have an object and need to merge objects by name and sum their counts.

products = {
    fruit: [
        { name: orange, count: 1 },
        { name: orange, count: 2 },
        { name: apple, count: 3 },
        { name: apple, count: 2 },
    ],
    vegetables: [
        { name: tomato, count: 3 },
        { name: tomato, count: 4 },
        { name: onion, count: 1 },
        { name: onion, count: 3 },
    ]
}

Final goal is to get object like this

products = {
    fruit: [
        { name: orange, count: 3 },
        { name: apple, count: 5 },
    ],
    vegetables: [
        { name: tomato, count: 7 },
        { name: onion, count: 4 },
    ]
}

Using for in loop I got into products object. I tried with reduce function but didn't succeed. Also I tried with [... new Set()] and I managed to merge objects by name, but counts didn't sum their values.

let temp = {}
for(let category in products){
    const product = products[category]
    temp = product.reduce((acc, curr) => {
        if(acc.name === curr.name){
            return{
                name: acc.name,
                count: acc.count + curr.count
            }
        }
    }, {})
}
  • 3
    Your `reduce` doesn’t return anything in case `acc.name === curr.name` is `false`. `acc.name` also doesn’t seem to make any sense. Please see [this Q&A](/q/19233283/4642212) and read the [documentation](//developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce). `.reduce((acc, curr) => {`…`}, {})` means `acc` is your intermediate result (“acc” is short for “accumulator”; the intermediate result is also the initial `{}` at the start and the final result at the end; you want this to be a `{ name: count,`…`}` structure); `curr` is the current `{ name, count }` object. – Sebastian Simon Apr 06 '22 at 12:09

3 Answers3

2

You can use grouping by hash approach:

const products={fruit:[{name:'orange',count:1},{name:'orange',count:2},{name:'apple',count:3},{name:'apple',count:2}],vegetables:[{name:'tomato',count:3},{name:'tomato',count:4},{name:'onion',count:1},{name:'onion',count:3}]};

const result = Object.keys(products).reduce((prods, key) => {
    const items = products[key];
    const grouped = items.reduce((acc, { name, count }) => {
        acc[name] ??= { name, count: 0 };
        acc[name].count += count;
        return acc;
    }, {});
    prods[key] = Object.values(grouped);
    
    return prods;
}, {});

console.log(result);
.as-console-wrapper { max-height: 100% !important; top: 0; }
A1exandr Belan
  • 4,442
  • 3
  • 26
  • 48
  • Note that this doesn’t add anything new to the [existing answers](/q/19233283/4642212). You’re not addressing the flaws in OP’s code. – Sebastian Simon Apr 06 '22 at 13:08
  • 1
    @SebastianSimon So, why you don't flag this Q as a dublicate? – A1exandr Belan Apr 06 '22 at 13:14
  • Because of lack of certainty. The OP does have code; the flaws in the code might need to be addressed by more specific duplicate targets than the general _“How to do this”_ Q&A. – Sebastian Simon Apr 06 '22 at 14:28
1

The below may be one possible implementation to obtain the desired objective.

Code Snippet

// helper method to manipulate 
// the fruit, vegetables arrays
const arrReduce = arr => (
  Object.entries(         // extract key-value pairs from below ".reduce()" result
    arr.reduce(           // iterate over the array with "acc" as accumulator
      (acc, {name, count}) => ({
        ...acc,           // retain existing "acc" entries
        [name]: (acc?.[name] ?? 0) + count
      }),                 // accumulate the "count" as-per "name"
      {}                  // initial "acc" is an empty-object 
    )
  ).map(
    ([k, v]) => ({        // transform the key-value pairs into objects
      name: k, count: v   // "name" and "count" props with corresponding values
    })
  )
);

// the method that does the transformation
const groupAndSum = obj => (
  Object.fromEntries(     // re-construct object from below result
    Object.entries(obj)   // transform key-value pairs into [k, v] array
    .map(                 // iterate over the array created
      ([k, v]) => ([      // use the "arrReduce()" helper method on the value-array
        k, arrReduce(v)
      ])
    )
  )
);


const products = {
  fruit: [{
    name: 'orange',
    count: 1
  }, {
    name: 'orange',
    count: 2
  }, {
    name: 'apple',
    count: 3
  }, {
    name: 'apple',
    count: 2
  }],
  vegetables: [{
    name: 'tomato',
    count: 3
  }, {
    name: 'tomato',
    count: 4
  }, {
    name: 'onion',
    count: 1
  }, {
    name: 'onion',
    count: 3
  }]
};


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

Explanation

Inline comments describe the various steps

References

This solution employs the following JavaScript features

  1. Object.fromEntries()
  2. Object.entries()
  3. Array.map()
  4. Arrow functions
  5. Array.reduce()
  6. De-structuring
  7. ... spread operator (to effect a shallow-copy)
  8. ?. optional chaining
  9. ?? nullish coalescing operator

EDIT

As noted in comments by Sebastian Simon, this answer does not provide any feedback on OP's attempt. Consequently, this edit is added here. Thank you Sebastian Simon for your feedback. Appreciate it.

Fixing OP's code

const products={fruit:[{name:'orange',count:1},{name:'orange',count:2},{name:'apple',count:3},{name:'apple',count:2}],vegetables:[{name:'tomato',count:3},{name:'tomato',count:4},{name:'onion',count:1},{name:'onion',count:3}]};

let temp = {}
for(let category in products){
    const product = products[category]
    temp = {
      ...temp,      // one needs to append results to "temp"
      [category]: Object.entries( // extract key-value pairs from result of ".reduce()"
        product
          .reduce(    // the ".reduce()" result needs to go within "category"
            (acc, curr) => {
            return {  // no need of "if" here. Always return an object
              ...acc, // retain existing "accumulator"
              [curr.name]: (    // add / update key "curr.name" of "acc" object
                acc
                ?.[curr.name]   // use existing "count" if "name" exists in "acc"
                ?? 0            // nullish coalescing to zero (if "name" not in "acc")
              ) + curr.count    // always add "curr.count" to the value
            };
          },
          {}
        )
      ).map(      // transform the key-value pair into the desired "name", "count" object
        ([k, v]) => ({
          name: k, count: v
        })
      )
    }
};

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

Explanation

The changes made along with description is added to the above code-snippet.

jsN00b
  • 3,584
  • 2
  • 8
  • 21
  • 2
    Note that this doesn’t really add anything new to the [existing answers](/q/19233283/4642212). You’re not addressing the flaws in OP’s code. – Sebastian Simon Apr 06 '22 at 13:08
1

Use Array#map, Object.fromEntries and Object.entries methods as follows:

const input = { fruit: [ { name: "orange", count: 1 }, { name: "orange", count: 2 }, { name: "apple", count: 3 }, { name: "apple", count: 2 } ], vegetables: [{ name: "tomato", count: 3 },{ name: "tomato", count: 4 },{ name: "onion", count: 1 }, { name: "onion", count: 3 }]};

const output =  Object.fromEntries( Object.entries(input)
    .map(([category, produce]) =>
        [
            category,
            Object.entries(
                produce.reduce((prev,{name,count}) =>
                    ({...prev,[name]:(prev[name] || 0) + count}), {}
                )
            )
            .map(([name, count]) => ({name,count}))
        ]
    )
);

console.log( output );

YOUR CODE

You could also edit your code as follows; you have first have to look in acc to see if a matching item (by name) exists - Array#find. If found, update the count, otherwise push curr into acc. BE SURE TO RETURN acc! The initial value should be an empty array, [].

const products = { fruit: [ { name: "orange", count: 1 }, { name: "orange", count: 2 }, { name: "apple", count: 3 }, { name: "apple", count: 2 } ], vegetables: [{ name: "tomato", count: 3 },{ name: "tomato", count: 4 },{ name: "onion", count: 1 }, { name: "onion", count: 3 }]};

const output = {}
for(let category in products) {
    const product = products[category]
    output[category] = product.reduce((acc, curr) => {
        let target = acc.find(item => item.name === curr.name);
        if( target ) {
            target.count += curr.count;
        } else {
            acc.push( curr );
        }
        return acc;
    }, [])
};

console.log( output );
PeterKA
  • 24,158
  • 5
  • 26
  • 48