web-dev-qa-db-ja.com

javascript reduce関数を使用して、特定の条件を満たすアイテムの平均を計算するにはどうすればよいですか?

したがって、次のオブジェクトの配列があると仮定します。

var arr = [
  {"name": "John", "score": "8.8"},
  {"name": "John", "score": "8.6"},
  {"name": "John", "score": "9.0"},
  {"name": "John", "score": "8.3"},
  {"name": "Tom",  "score": "7.9"}
];

var count = 0;
var avgScore = arr.reduce(function (sum,person) {
  if (person.name == "John") {
    count+=1;
    return sum + parseFloat(person.score);
  }
  return sum;
},0)/count);

質問:グローバルcount変数を作成せずに、「John」の平均スコアを計算する方法はありますか。理想的には、カウントはarr.reduceの匿名関数の内部にあります。

9
Randell D

グローバル変数を回避するには、 IIFEs または ブロックスコープ のような標準ソリューションを使用します。ただし、可変カウンターを回避する方法を探していると思います。

最も簡単なのは、他のすべての人を事前にドロップすることです。

var johns = arr.filter(function(person) {
  return person.name == "John";
});
var avgScore = johns.reduce(function (sum, person) {
  return sum + parseFloat(person.score);
}, 0) / johns.length;

ただし、オブジェクトの合計とともに渡されるcountを使用することもできます。

var stats = arr.reduce(function ({count, sum}, person) {
  return (person.name == "John")
    ? {count: count+1, sum: sum + parseFloat(person.score)}
    : {count, sum};
}, {count:0, sum:0})
var avgScore = stats.sum / stats.count);

(ES6オブジェクトプロパティの省略形と破棄を使用)

6
Bergi

これはさらに別のES6バリアントで、(ab)reduceの3番目の引数を一時ストレージとして使用し、合計とカウントから平均を連鎖計算するために再びreduceを呼び出します。

const arr = [
  {"name": "John", "score": "8.8"},
  {"name": "John", "score": "8.6"},
  {"name": "John", "score": "9.0"},
  {"name": "John", "score": "8.3"},
  {"name": "Tom",  "score": "7.9"}
];

const avg = arr.reduce( ([sum, count], {name, score}, i) =>
                        (i = name == 'John', [sum + i * score, count + i]), [0, 0] )
               .reduce( (sum, count) => sum/count );

console.log(avg);
2
trincot

更新を行うすべてのループで計算された、平均を含むオブジェクトを返すことができます。

var arr = [{ name: "John", score: "8.8" }, { name: "John", score: "8.6" }, { name: "John", score: "9.0" }, { name: "John", score: "8.3" }, { name: "Tom", score: "7.9" }],
    avgScore = arr.reduce(function (r, person) {
        if (person.name === "John") {
            r.sum += +person.score;
            r.avg = r.sum / ++r.count;
        }
        return r;
    }, { sum: 0, count: 0, avg: 0 }).avg;

console.log(avgScore);

クロージャと平均の直接リターンを備えたバージョン。

var arr = [{ name: "John", score: "8.8" }, { name: "John", score: "8.6" }, { name: "John", score: "9.0" }, { name: "John", score: "8.3" }, { name: "Tom", score: "7.9" }],
    avgScore = arr.reduce(function (sum, count) {
        return function (avg, person) {
            if (person.name === "John") {
                sum += +person.score;
                return sum / ++count;
            }
            return avg;
        };
    }(0, 0), 0);

console.log(avgScore);

上記のES6

var arr = [{ name: "John", score: "8.8" }, { name: "John", score: "8.6" }, { name: "John", score: "9.0" }, { name: "John", score: "8.3" }, { name: "Tom", score: "7.9" }],
    avgScore = arr.reduce(((sum, count) => (avg, person) => person.name === "John" ? (sum += +person.score) / ++count : avg)(0, 0), 0);

console.log(avgScore);
2
Nina Scholz

この関数は、別のときにフィルターをかけたい場合に、フィルターを引数として取ります。また、filteredPersons.lengthを使用します。カウントの代わりに。

var arr = [
  {"name": "John", "score": "8.8"},
  {"name": "John", "score": "8.6"},
  {"name": "John", "score": "9.0"},
  {"name": "John", "score": "8.3"},
  {"name": "Tom",  "score": "7.9"}
];

function filterJohn(person){
  return person.name === 'John';
};

function calculateAverageScore(persons, filterFunc){
  const filteredPersons = persons.filter(filterFunc);
  return filteredPersons.reduce((sum, person) => { return sum +   parseFloat(person.score); }, 0)/filteredPersons.length;
};

calculateAverageScore(arr, filterJohn);
0
Swoot

カスタムオブジェクトをinitialValueArray.prototype.reduce()のパラメータとして使用するソリューション:

var arr = [{"name": "John", "score": "8.8"},{"name": "John", "score": "8.6"}, {"name": "John", "score": "9.0"}, {"name": "John", "score": "8.3"}, {"name": "Tom",  "score": "7.9"}];

var result = arr.reduce(function (r, o) {
    if (o.name === 'John') ++r.count && (r.sum += Number(o.score));
    return r;
}, {sum: 0, count: 0});

console.log(result.sum/result.count);  // `John's` average score
0
RomanPerekhrest

IIFEを使用して、countをプライベートスコープに限定できます。

var arr = [
  {"name": "John", "score": "8.8"},
  {"name": "John", "score": "8.6"},
  {"name": "John", "score": "9.0"},
  {"name": "John", "score": "8.3"},
  {"name": "Tom",  "score": "7.9"}
];

var avgScore = arr.reduce(
  (function() {
    var count = 0;

    return function (average, person) {
      if (person.name == "John") {
        count += 1;
        return average * (count - 1) / count + parseFloat(person.score) / count;
      }
      return average;
    };
  })(),
  0
);

console.log(avgScore);
0
Jordan Running

2パスは、追加の計算、グローバル、またはラッパーオブジェクトがなくてもうまく機能します。

var arr = [
  {"name": "John", "score": "8.8"},
  {"name": "John", "score": "8.6"},
  {"name": "John", "score": "9.0"},
  {"name": "John", "score": "8.3"},
  {"name": "Tom",  "score": "7.9"}
];


var avgScore = arr.filter(x=>x.name=="John")
   .reduce(function(v, n, c, r) {
        return r.length-1 === c ? 
            (v + +n.score) / r.length : 
             v + +n.score;
    },0);

console.log(avgScore);

複数の異なる形状を実行している場合は、メソッドを再利用できるように、プリミティブの配列で作業する必要があります。

var arr = [
  {"name": "John", "score": "8.8"},
  {"name": "John", "score": "8.6"},
  {"name": "John", "score": "9.0"},
  {"name": "John", "score": "8.3"},
  {"name": "Tom",  "score": "7.9"}
];

// define a few simple helpers
function pluck(o){ return o[this];}    
function avg (v, n, c, r) { // calcs an average w/o a sum
        return r.length-1 === c ? 
            (v + n) / r.length : 
            v + n ;
}

//now use the helpers to write succinct custom code:
var avgScore = arr.filter(x=>x.name=="John")
   .map(pluck, "score")
   .reduce(avg, 0);

console.log(avgScore);

元のアイデアは、ユーザーがカスタムコードを実行せずにパラメーターを渡し、バックエンドでいくつかの計算を実行できるカスタムレポートジェネレーターから生まれました。 avgなどのジェネリックメソッドのlibは、カスタムコールバック関数なしで使用できます。それは違うので、私はそれについて言及します...

0
dandavis