3

I am trying to sum the object values based on selected keys which are available in every object of the array. My problem is how to handle the percentage rate. In the sample of code below, the satisfaction_rate should have 48.

My output should be:

{ 'students' : 87, 'books': 32, 'satisfaction_rate': 48 }

const myData = [
  {
  'students' : 30,
  'books': 10,
  'satisfaction_rate': "60%",
  'returning_students': 14
  }, {
'students' : 25,
'books': 8,
'satisfaction_rate': "45%",
'returning_students': 14
  }, {
'students' : 32,
'books': 14,
'satisfaction_rate': "39%",
'returning_students': 19
  }
];


const keysToConsider = ['students', 'books', 'satisfaction_rate'];

function getSumOrAvgValuesOfKeys(data){
      const obj = {};
      let val = 0;
      keysToConsider.forEach(el => {
        data.forEach(element => {
          if (typeof(element[el] === 'string')) {
            val += parseInt(element[el], 10);
          } else {
            val += element[el];
          }
      });
        obj[el] = val;
        // Reset value
        val = 0;
    });
    return obj;
  }
  
console.log(getSumOrAvgValuesOfKeys(myData));

2
  • How is satisfaction_rate calculated for each item? Commented Aug 8, 2018 at 11:59
  • @MadhanVaradhodiyil The API data is in that format. I don't know how it was calculated. Commented Aug 8, 2018 at 12:03

5 Answers 5

6

You could use reduce method and then on the last iteration calculate average for each element where value is type of string.

const myData = [{"students":30,"books":10,"satisfaction_rate":"60%","returning_students":14},{"students":25,"books":8,"satisfaction_rate":"45%","returning_students":14},{"students":32,"books":14,"satisfaction_rate":"39%","returning_students":19}]

const keys = ['students', 'books', 'satisfaction_rate'];

const result = myData.reduce((r, e, i, a) => {
  keys.forEach(k => r[k] = (r[k] || 0) + parseInt(e[k]));
  if(!a[i + 1]) Object.keys(e)
    .filter(k => typeof e[k] == 'string')
    .forEach(k => r[k] /= myData.length)
  return r;
}, {})

console.log(result)

Sign up to request clarification or add additional context in comments.

Comments

1

You may have another array with keys to be averaged, and after the preparation of the summed result, you can calculate the average for those keys only.

const myData = [{
  'students': 30,
  'books': 10,
  'satisfaction_rate': "60%",
  'returning_students': 14
}, {
  'students': 25,
  'books': 8,
  'satisfaction_rate': "45%",
  'returning_students': 14
}, {
  'students': 32,
  'books': 14,
  'satisfaction_rate': "39%",
  'returning_students': 19
}];


const keysToConsider = ['students', 'books', 'satisfaction_rate'];
let keysWithPercent = ['satisfaction_rate'];

const summed = myData.reduce((result, item) => {
  keysToConsider.forEach(k => {
    if (!result[k]) {
      result[k] = 0;
    }
    
    let v = parseInt(item[k], 10);
    result[k] += v;
  });
  
  return result;
}, {});

keysWithPercent.forEach(k => {
  summed[k] = summed[k] / myData.length;
})

console.log(summed);

Comments

1

You could reduce the objects by taking the previous values or a default value. For the percent value take a third for summing.

var data = [{ students: 30, books: 10, satisfaction_rate: "60%", returning_students: 14 }, { students: 25, books: 8, satisfaction_rate: "45%", returning_students: 14 }, { students: 32, books: 14, satisfaction_rate: "39%", returning_students: 19 }],
    result = data.reduce((r, { students, books, satisfaction_rate }, _, a) => ({
        students: (students || 0) + students,
        books: (r.books || 0) + books,
        satisfaction_rate: (r.satisfaction_rate || 0) + satisfaction_rate.slice(0, -1) / a.length
    }), {});
      
console.log(result);

With a dynamic key list

var data = [{ students: 30, books: 10, satisfaction_rate: "60%", returning_students: 14 }, { students: 25, books: 8, satisfaction_rate: "45%", returning_students: 14 }, { students: 32, books: 14, satisfaction_rate: "39%", returning_students: 19 }],
    keys = ['students', 'books', 'satisfaction_rate'],
    result = data.reduce((r, o, _, a) => 
        Object.assign(
            ...keys.map(k =>
                ({ [k]: (r[k] || 0) + (o[k].toString().endsWith('%')
                    ? o[k].slice(0, -1) / a.length
                    : o[k])
                }))
        ),
        {}
    );
      
console.log(result);

Comments

0

IMHO, what you are trying to do can be done using reduce like this:

var myData = [{ students: 30, books: 10, satisfaction_rate: "60%", returning_students: 14 }, { students: 25, books: 8, satisfaction_rate: "45%", returning_students: 14 }, { students: 32, books: 14, satisfaction_rate: "39%", returning_students: 19 }]

var result = myData.reduce((accum, { students, books, satisfaction_rate }, idx, arr) => {
    accum["students"] ? (accum["students"] += students) : (accum["students"] = students);
    accum["books"] ? (accum["books"] += books) : (accum["books"] = books);
    accum["satisfaction_rate"] ? (accum["satisfaction_rate"] += parseInt(satisfaction_rate)) : (accum["satisfaction_rate"] = parseInt(satisfaction_rate));
    if (idx === arr.length - 1) {
      accum["satisfaction_rate"] = accum["satisfaction_rate"] / arr.length
    }
return accum;
}, {});

console.log(result);

Comments

0

A more generic solution would be to specify how you want to reduce each key in your keysToConsider object. You could reduce them using SUM, AVG, MIN, MAX, and even more complex ones like standard deviation.

In that case, I would change your keysToConsider to be an object rather than an array:

const myData = [{
    'students': 30,
    'books': 10,
    'satisfaction_rate': "60%",
    'returning_students': 14
}, {
    'students': 25,
    'books': 8,
    'satisfaction_rate': "45%",
    'returning_students': 14
}, {
    'students': 32,
    'books': 14,
    'satisfaction_rate': "39%",
    'returning_students': 19
}];

keysToConsider = {
    'students': sum,
    'books': sum,
    'satisfaction_rate': avg
}

function reduceValuesOfKeys(data) {
    const obj = {};
    let val = 0;
    for (var p in keysToConsider) {
        var reduce = keysToConsider[p];
        obj[p] = reduce(data, p);
    }
    return obj;
}

function sum(data, property) {
    var val = 0;
    data.forEach(element => {
        if (typeof(element[property] === 'string')) {
            val += parseInt(element[property], 10);
        } else {
            val += element[property];
        }
    });
    return val;
}

function avg(data, property) {
    var val = 0;
    data.forEach(element => {
        if (typeof(element[property] === 'string')) {
            val += parseInt(element[property], 10);
        } else {
            val += element[property];
        }
    });
    return val / data.length;
}

console.log(reduceValuesOfKeys(myData));

If you needed to add other reduce functions in the future, it'd be easier.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.