简体   繁体   中英

most efficient way to find average using lodash

I have an array of objects, the number of objects is variable -

var people = [{
  name: john,
  job: manager,
  salary: 2000
},
  {
  name: sam,
  job: manager,
  salary: 6000
},
  {
  name: frodo,
  job: janitor
}];

Whats the most elegant way to find the average of the salaries of all managers using lodash? ( I assume we have to check if an object is manager, as well as if the object has a salary property)

I was thinking in the below lines -

_(people).filter(function(name) {
    return name.occupation === "manager" && _(name).has("salary");}).pluck("salary").reduce(function(sum,num) { return sum+num });

But I am not sure if this is the right approach.

Why all people gets over-complicated here?

 const people = [ { name: 'Alejandro', budget: 56 }, { name: 'Juan', budget: 86 }, { name: 'Pedro', budget: 99 }, ]; const average = _.meanBy(people, (p) => p.budget); console.log(average);
 <script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>

As per the docs: https://lodash.com/docs/#meanBy

"efficient" is very ambiguous term. Saying "efficient" you can think about performance , or readability , or conciseness and so on. I think the most readable and concise solution is:

_(people).filter({ job: 'manager'}).filter('salary').reduce(function(a,m,i,p) {
    return a + m.salary/p.length;
},0);

The most fast solution is do not use loadash, nor any library, nor any filter , reduce methods at all. Use for loop instead:

var sum    = 0;
var count  = 0;
for (var i = 0, ii = people.length; i < ii; ++i) {
    var man = people[i];

    if (typeof man.salary !== 'undefined') {
        sum += man.salary;
        ++count;
    }
}
var avg = sum/count;

I think for the client side development readability is more important than performance in most cases , so I think first variant is most "efficient".

lodash v3:

_.sum(people, 'salary') / people.length ( people mustn't be empty)

lodash v4:

_.meanBy(people, 'salary')

I don't know about lowdash, but maybe a plain JS solution will help you get there:

console.log(people.reduce(function(values, obj) {
              if (obj.hasOwnProperty('salary')) {
                values.sum += obj.salary;
                values.count++;
                values.average = values.sum / values.count;
              }
              return values;
            }, {sum:0, count:0, average: void 0}).average
); // 4000

This passes an object to reduce as the accumulator that has three properties: the sum of salaries, the count of salaries, and the average so far. It iterates over all the objects, summing the salaries, counting how many there are and calculating the average on each iteration. Eventually it returns that object (the accumulator ) and the average property is read.

Calling a single built–in method should be faster (ie more efficient) than calling 4 native functions. "Elegant" is in the eye of the beholder. ;-)

BTW, there are errors in the object literal, it should be:

var people = [{
  name: 'john',
  job: 'manager',
  salary: 2000
},
  {
  name: 'sam',
  job: 'manager',
  salary: 6000
},
  {
  name: 'frodo',
  job: 'janitor'
}];
function average(acc, ele, index) {
    return (acc + ele) / (index + 1);
}

var result = _.chain(people)
    .filter('job', 'manager')
    .map('salary')
    .reduce( average )
    .value();

With the more functional lodash version ( lodash-fp ) and es2015 you can to use arrow functions and auto curry to get a more flexible and functional flavored solution.

You can put it in an ugly one liner:

const result = _.flow(_.filter(['job', 'manager']), 
    e => _.sumBy('salary', e) / _.countBy(_.has('salary'), e).true)(people);

Or you can create a tidy DSL:

const hasSalary = _.has('salary');
const countWhenHasSalary = _.countBy(hasSalary);
const salarySum = _.sumBy('salary');
const salaryAvg = a => salarySum(a) / countWhenHasSalary(a).true;
const filterByJob = job => _.filter(['job', job]);
const salaryAvgByJob = job => _.flow(filterByJob(job), salaryAvg);

const result = salaryAvgByJob('manager')(people);

Most clean (elegant) way I could think of was:

var salariesOfManagers = _(people).filter({job: 'manager'}).filter('salary').pluck('salary');
var averageSalary = salariesOfManagers.sum() / salariesOfManagers.value().length;

It takes sum of items and divides its with number of items, which is pretty much the definition of average.

Too bad that if you would like to make that to neat one-liner, the code will get less clear to read.

Using lodash/fp and ES2016/ES6 it can be done in a more functional way

const avg = flow(
    filter({job: 'manager'}), 
    map('salary'),
    mean
)

console.log(avg(people))

What you do is 1. Get all objects 'manager' type 2. Extract 'salary' property/field from them 3. Find average using mean function

Here is a full version of code for your convenience that runs on nodejs.

'use strict'
const _ = require('lodash/fp');

const {
    flow,
    filter,
    map,
    mean
} = _

const people = [{
    name: 'john',
    job: 'manager',
    salary: 2000
}, {
    name: 'sam',
    job: 'manager',
    salary: 6000
}, {
    name: 'frodo',
    job: 'janitor'
}];

const avg = flow(
    filter({job: 'manager'}), 
    map('salary'),
    mean
)

console.log(avg(people))

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