frosty4321
frosty4321

Reputation: 59

group, filter and count in array

I am using js and have an dynamic array of object from an response which looks like the following:

[
  {fromCountry: "TE", toCountry: "FE", type: "new", status: "created"},
  {fromCountry: "TE", toCountry: "FE", type: "old", status: "cold"},
  {fromCountry: "CD", toCountry: "EG", type: "used", status: "hot"},
  {fromCountry: "CD", toCountry: "EG", type: "old", status: "hot"},
  {fromCountry: "CD", toCountry: "EG", type: "old", status: "cold"}
];

I would like to create a new array and therefore I want to group them by fromCountry, count the fromCountry and add a nested array with the types and the value. It should look like is:

[
  { name: "TE", value: 2, child: [{ name: "new", value: 1 }, { name: "old", value: 1 }]}, 
  { name: "CD", value: 3, child: [{ name: "used", value: 1}, { name: "old", value: 2 }]}
]

(Note: I would not like to use extra javascript libraries)

Could you help me please?

Upvotes: 1

Views: 483

Answers (4)

Isaac
Isaac

Reputation: 12884

let arr = [
  {fromCountry:"TE",toCountry:"FE",type:"new",status:"created"},
  {fromCountry:"TE",toCountry:"FE",type:"old",status:"cold"},
  {fromCountry:"CD",toCountry:"EG",type:"used",status:"hot"},
  {fromCountry:"CD",toCountry:"EG",type:"old",status:"hot"},
  {fromCountry:"CD",toCountry:"EG",type:"old",status:"cold"}
];

let answer = [];

arr.forEach(x=> {
   if(!answer.some(y => y.name === x.fromCountry)){
     let newAnswer = {};
     newAnswer.name = x.fromCountry;
     newAnswer.value = 1;
     newAnswer.child = [];
    
     let child = {name: x.type, value: 1};
     newAnswer.child.push(child);
     answer.push(newAnswer);
  }else{
     let existAnswer = answer.find(y=>y.name === x.fromCountry);
     existAnswer.value++;
    
     if(existAnswer.child.some(z=>z.name === x.type)){
        let childObj = existAnswer.child.find(z=>z.name === x.type);
        childObj.value++;
    }else{
       let newChildObj = {name: x.type, value: 1}
       existAnswer.child.push(newChildObj);
    }
  }
})

console.log(answer)

We loop thru the whole array, find the existence of element, if found we increment, if not found we push a new object. And same logic applies to child Obj.

This may not be elegant way but I believe is much easier to understand. Thou it's very subjective.

Upvotes: 1

Nina Scholz
Nina Scholz

Reputation: 386680

You could use a function for an arbitrary count of groups.

This proposal features an object which keeps the single object as part result, the grouping result and the hash (the value of the given key) and takes it as an accessor for the group.

The underscore property _ is necessary to separate a playload object from the hashing function of the object.

A possible needed children array is created on the fly.

The result is a nested structure which has the same depth as the given groups array.

function getGouped(array, groups) {
    var result = [],
        object = { _: { children: result } };

    array.forEach(function (a) {
        groups.reduce(function (r, k) {
            var name = a[k];
            if (!r[name]) {
                r[name] = { _: { name, count: 0 } };
                r._.children = r._.children || [];
                r._.children.push(r[name]._);
            }
            r[name]._.count++;
            return r[name];
        }, object);
    });
    return result;
}

var data = [{ fromCountry: "TE", toCountry: "F​​E", type: "new", status: "created" }, { fromCountry: "TE", toCountry: "F​​E", type: "old", status: "cold" }, { fromCountry: "CD", toCountry: "E​​G", type: "used", status: "hot" }, { fromCountry: "CD", toCountry: "E​​G", type: "old", status: "hot" }, { fromCountry: "CD", toCountry: "EG", type: "old", status: "cold" }];

console.log(getGouped(data, ['fromCountry', 'type']));
console.log(getGouped(data, ['fromCountry', 'type', 'status']));
.as-console-wrapper { max-height: 100% !important; top: 0; }

Upvotes: 1

Eddie
Eddie

Reputation: 26844

You can use reduce to group the array into an object. Use Object.values to convert the object into an array.

var arr = [{fromCountry:"TE",toCountry:"FE",type:"new",status:"created"},{fromCountry:"TE",toCountry:"FE",type:"old",status:"cold"},{fromCountry:"CD",toCountry:"EG",type:"used",status:"hot"},{fromCountry:"CD",toCountry:"EG",type:"old",status:"hot"},{fromCountry:"CD",toCountry:"EG",type:"old",status:"cold"}];

var result = Object.values(arr.reduce((c, {fromCountry,type}) => {
  c[fromCountry] = c[fromCountry] || {name: fromCountry,value: 0,child: {}};
  c[fromCountry].child[type] = c[fromCountry].child[type] || {name: type,value: 0};
  c[fromCountry].child[type].value++;
  c[fromCountry].value++;
  return c;
}, {})).map(o => {
  o.child = Object.values(o.child);
  return o;
});

console.log(result);

Upvotes: 1

Andrew Bone
Andrew Bone

Reputation: 7291

Here is a slow old way of doing it, it makes temp objects and increments the values based on what it sees.

I'm sure there is a much faster way to do it so will be keeping an eye out for other answers.

I've commented the code but feel free to ask if something doesn't make sense.

var objs  = [{fromCountry:"TE",toCountry:"FE",type:"new",status:"created"},{fromCountry:"TE",toCountry:"FE",type:"old",status:"cold"},{fromCountry:"CD",toCountry:"EG",type:"used",status:"hot"},{fromCountry:"CD",toCountry:"EG",type:"old",status:"hot"},{fromCountry:"CD",toCountry:"EG",type:"old",status:"cold"}];

// temp object and output array
let holding = {};
let output = [];

// for each object
for (let obj of objs) {
  // if it's the first time we've seen this fromCountry make it
  // and give it a value of 0
  if (!holding[obj.fromCountry]) holding[obj.fromCountry] = {
    value: 0
  };
  // if it's the first time we've seen this type make it and
  // give it a value of 0
  if (!holding[obj.fromCountry][obj.type]) holding[obj.fromCountry][obj.type] = {
    value: 0
  };
  
  // increment values
  holding[obj.fromCountry].value++;
  holding[obj.fromCountry][obj.type].value++
}

// Now we need to reformat the object

// for each key in the holding object
for (let key of Object.keys(holding)) {
  // make a new temp object in the right format
  let temp = {
    name: key,
    value: holding[key].value,
    child: []
  };
  // for each inner key in the holding object
  for (let innerKey of Object.keys(holding[key])) {
    // skip over value
    if (innerKey == "value") continue
    // make another temp object to be pushed to child
    let innertemp = {
      name: innerKey,
      value: holding[key][innerKey].value
    }
    // push inner temp object to child
    temp.child.push(innertemp);
  }
  //push whole temp object, now formatted correctly, to the output array
  output.push(temp);
}

console.log(output);

I hope this is helpful 🙂

Upvotes: 1

Related Questions