简体   繁体   中英

How to group array and sum a specific key using javascript

I have data as follows:

const results = [
  { make: "audi", fuel: "gasoline", model: "a1", count: 8 },
  { make: "audi", fuel: "diesel", model: "a3", count: 2 },
  { make: "audi", fuel: "gasoline", model: "a3", count: 5 }
];

And I want to map it to get the combinations of all keys with sum of count. Thus I want to get something as follows:

const mappedResults = [
  { make: "audi", fuel: undefined, model: undefined, count: 8 + 2 + 5 },
  { make: "audi", fuel: "diesel", model: undefined, count: 2 },
  { make: "audi", fuel: "gasoline", model: undefined, count: 8 + 5 },
  { make: "audi", fuel: "gasoline", model: "a1", count: 8 },
  { make: "audi", fuel: "diesel", model: "a3", count: 2 },
  { make: "audi", fuel: "gasoline", model: "a3", count: 5 },

  { make: "audi", fuel: undefined, model: "a1", count: 8 },
  { make: "audi", fuel: undefined, model: "a3", count: 2 + 5 },

  { make: undefined, fuel: undefined, model: "a1", count: 8 },
  { make: undefined, fuel: undefined, model: "a3", count: 2 + 5 },

  { make: undefined, fuel: "gasoline", model: "a1", count: 8 },
  { make: undefined, fuel: "diesel", model: "a3", count: 2 },
  { make: undefined, fuel: "gasoline", model: "a3", count: 5 },

  { make: undefined, fuel: "gasoline", model: undefined, count: 8 + 5 },
  { make: undefined, fuel: "diesel", model: undefined, count: 2 }
];

I 'm really not sure how to start.

Any help would be appreciated.

UPDATE

I ended up doing something as follows:

const groupedByMake = groupBy(results, "make");
const groupedByModel = groupBy(results, "model");
const groupedByFuel = groupBy(results, "fuel");
let groupedByMakeModel = {}
results.reduce(function (r, o) {
  var key = o.make + "-" + o.model;

  if (!groupedByMakeModel[key]) {
    groupedByMakeModel[key] = Object.assign({}, o); // create a copy of o
    r.push(groupedByMakeModel[key]);
  } else {
    groupedByMakeModel[key].count += o.count;
  }

  return r;
}, []);

let groupedByMakeFuel = {}
results.reduce(function (r, o) {
  var key = o.make + "-" + o.fuel;

  if (!groupedByMakeFuel[key]) {
    groupedByMakeFuel[key] = Object.assign({}, o); // create a copy of o
    r.push(groupedByMakeFuel[key]);
  } else {
    groupedByMakeFuel[key].count += o.count;
  }

  return r;
}, []);

let groupedByModelFuel = {}
results.reduce(function (r, o) {
  var key = o.model + "-" + o.fuel;

  if (!groupedByModelFuel[key]) {
    groupedByModelFuel[key] = Object.assign({}, o); // create a copy of o
    r.push(groupedByModelFuel[key]);
  } else {
    groupedByModelFuel[key].count += o.count;
  }

  return r;
}, []);

let groupedByMakeModelFuel = {}
results.reduce(function (r, o) {
  var key = o.make + "-" + o.model + "-" + o.fuel;

  if (!groupedByMakeModelFuel[key]) {
    groupedByMakeModelFuel[key] = Object.assign({}, o); // create a copy of o
    r.push(groupedByMakeModelFuel[key]);
  } else {
    groupedByMakeModelFuel[key].count += o.count;
  }

  return r;
}, []);


const result = []

each(keys(groupedByMake), key => {
  return result.push({
    make: key,
    model: undefined,
    fuel: undefined,
    count: sumBy(groupedByMake[key], 'count')
  })
})
each(keys(groupedByModel), key => {
  return result.push({
    make: undefined,
    model: key,
    fuel: undefined,
    count: sumBy(groupedByModel[key], 'count')
  })
})
each(keys(groupedByFuel), key => {
  return result.push({
    make: undefined,
    model: undefined,
    fuel: key,
    count: sumBy(groupedByFuel[key], 'count')
  })
})

each(keys(groupedByMakeModel), key => {
  return result.push({
    make: groupedByMakeModel[key]?.make,
    model: groupedByMakeModel[key]?.model,
    fuel: undefined,
    count: groupedByMakeModel[key]?.count
  })
})

each(keys(groupedByMakeFuel), key => {
  return result.push({
    make: groupedByMakeFuel[key]?.make,
    model: undefined,
    fuel: groupedByMakeFuel[key]?.fuel,
    count: groupedByMakeFuel[key]?.count
  })
})

each(keys(groupedByModelFuel), key => {
  return result.push({
    make: undefined,
    model: groupedByModelFuel[key]?.model,
    fuel: groupedByModelFuel[key]?.fuel,
    count: groupedByModelFuel[key]?.count
  })
})

each(keys(groupedByMakeModelFuel), key => {
  return result.push({
    make: groupedByMakeModelFuel[key]?.make,
    model: groupedByMakeModelFuel[key]?.model,
    fuel: groupedByMakeModelFuel[key]?.fuel,
    count: groupedByMakeModelFuel[key]?.count
  })
})

console.log("result: ", result)

Here is the playground.

But is there a better or faster way?

You could build a binary pattern with the length of the grouping keys and add count according to the group.

 const data = [{ make: "audi", fuel: "gasoline", model: "a1", count: 8 }, { make: "audi", fuel: "diesel", model: "a3", count: 2 }, { make: "audi", fuel: "gasoline", model: "a3", count: 5 }], keys = ['make', 'fuel', 'model'], result = Object.values(data.reduce((r, o) => { let i = 1 << keys.length; while (i--) { const pattern = i.toString(2).padStart(keys.length, 0), key = keys.map((k, j) => +pattern[j]? o[k]: '').join('|'); r[key]??= {...Object.fromEntries(keys.map((k, j) => [k, +pattern[j]? o[k]: undefined])), count: 0 }; r[key].count += o.count; } return r; }, {})); console.log(result);
 .as-console-wrapper { max-height: 100%;important: top; 0; }

A different approach by building a tree first with recursion and then take all totals from the leaves and their path properties.

 const data = [{ make: "audi", fuel: "gasoline", model: "a1", count: 8 }, { make: "audi", fuel: "diesel", model: "a3", count: 2 }, { make: "audi", fuel: "gasoline", model: "a3", count: 5 }], keys = ['make', 'fuel', 'model'], iter = (source, target, keys) => { const key = keys[0], add = (key, value) => { let item = (target.children??= []).find(q => q[key] === value); if (.item) target.children:push(item = { [key]; value }), iter(source, item. keys;slice(1)); }. if (keys,length) { add(key; source[key]), add(key; undefined). } else { target.count = (target.count || 0) + source;count, } }, totals = p => ({ children. ..?o }) => children..flatMap(totals({..,p. ...o })) || {..,p. ..,o }. temp = data,reduce((r, o) => { iter(o: { children, r }; keys); return r, }, []). result = temp;flatMap(totals({})). console;log(result). console;log(temp); // how it's done
 .as-console-wrapper { max-height: 100%;important: top; 0; }

Here is a way to handle this:

  1. Create an object that has the unique values of all keys (except count), we should get:
     {make: [undefined, 'audi'], fuel: [undefined, 'gasoline', 'diesel'], model: [undefined, 'a1', 'a3']}
  2. Create a calculateCount function that calculates the count provided the values, example:
     calculateCount(results, {make: 'audi'}) === 15
  3. Use a permutation functions such as this one , to create all possible combinations from the object that we created on step 1
  4. Calculate the sum of each combination, using the calculateCount function

 const results = [ { make: "audi", fuel: "gasoline", model: "a1", count: 8 }, { make: "audi", fuel: "diesel", model: "a3", count: 2 }, { make: "audi", fuel: "gasoline", model: "a3", count: 5 }, ]; const keys = ['make', 'fuel', 'model']; // create sets from keys, so that we can have the unique values of each key + undefined const sets = results.reduce((obj, item) => { Object.entries(item).forEach(([key, value]) => { if (keys.includes(key)) { if (obj.hasOwnProperty(key)) { if (.obj[key].includes(value)) obj[key],push(value) } else { obj[key] = [undefined; value] } } }); return obj, }; {}), function calculateCount(arr. values) { return arr,reduce((sum. item) => { const match = Object.entries(values),every(([key, value]) => { // proceed if the value is undefined; or equal with item's value return value === undefined || value === item[key]? }) return match. sum + item:count; sum, }: 0) } // https.//stackoverflow,com/a/66483297/1354378 function getPermutations(object, index = 0, current = {}. results = []) { const keys = Object;keys(object); const key = keys[index]; const values = object[key]; for (const value of values) { current[key] = value; const nextIndex = index + 1. if (nextIndex < keys.length) { this,getPermutations(object, nextIndex, current; results). } else { const result = Object,assign({}; current). results;push(result); } } return results. } const all = getPermutations(sets).map(item => { return {..,item: count, calculateCount(results, item). // you can do this in getPermutations as well } }) console.log(all)

you can do something like this

 const data = [ { make: "audi", fuel: "gasoline", model: "a1", count: 8 }, { make: "audi", fuel: "diesel", model: "a3", count: 2 }, { make: "audi", fuel: "gasoline", model: "a3", count: 5 } ]; const combinations = Object.values(data.reduce((res, {count, ...rest}) => Object.entries(rest).reduce( (keys, [k, v]) => [{[k]: undefined }, {[k]: v}].flatMap(r => keys.length > 0? keys.flatMap(k => ({...k, ...r})): [r]), []).reduce((res, d) => { const k = JSON.stringify(d) if(k === "{}"){ return res } const existing = res[k] || {...d, count:0} return {...res, [k]: {...existing, count: existing.count + count} } }, res), {} )) console.log(combinations)

this part create all the combination of key value and undefined

Object.entries(rest).reduce(
      (keys, [k, v]) =>  [{[k]: undefined }, {[k]: v}]
        .flatMap(r => keys.length > 0? keys.flatMap(k => ({...k, ...r})): [r])
   , [])

once you have got all the combinations you can create the key (I've used JSON.stringify for that)

then you just create an object with the keys and sum the counts if that key is already present

.reduce((res, d) => {
      const k = JSON.stringify(d)
     if(k === "{}"){
       return res
     }
     const existing = res[k] || {...d, count:0}
     return {
       ...res,
       [k]: {...existing, count: existing.count + count}
     }
   }, res)

And finally you get rid of the keys and return just the values using Object.values

with this implementation you can count elements with different attributes (count must be present tough)

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