したがって、次のオブジェクトの配列があると仮定します。
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の匿名関数の内部にあります。
グローバル変数を回避するには、 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オブジェクトプロパティの省略形と破棄を使用)
これはさらに別の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);
更新を行うすべてのループで計算された、平均を含むオブジェクトを返すことができます。
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);
この関数は、別のときにフィルターをかけたい場合に、フィルターを引数として取ります。また、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);
カスタムオブジェクトを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
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);
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は、カスタムコールバック関数なしで使用できます。それは違うので、私はそれについて言及します...