arr
の各要素についてa.x
を合計したいとします。
arr = [{x:1},{x:2},{x:4}]
arr.reduce(function(a,b){return a.x + b.x})
>> NaN
ある時点でa.xが未定義であると信じる理由があります。
以下は正常に動作します
arr = [1,2,4]
arr.reduce(function(a,b){return a + b})
>> 7
最初の例で何が間違っていますか?
最初の反復の後、数値を返し、その変数のプロパティx
を取得して、undefined
である次のオブジェクトに追加しようとすると、undefined
を含む数学がNaN
になります。
パラメータのxプロパティの合計を含むx
プロパティを含むオブジェクトを返します。
var arr = [{x:1},{x:2},{x:4}];
arr.reduce(function (a, b) {
return {x: a.x + b.x}; // returns object with property x
})
// ES6
arr.reduce((a, b) => ({x: a.x + b.x}));
// -> {x: 7}
コメントから説明を追加:
次の繰り返しでa
変数として使用される[].reduce
の各繰り返しの戻り値。
反復1:a = {x:1}
、b = {x:2}
、{x: 3}
が反復2のa
に割り当てられました
反復2:a = {x:3}
、b = {x:4}
。
例の問題は、数値リテラルを返すことです。
function (a, b) {
return a.x + b.x; // returns number literal
}
反復1:a = {x:1}
、b = {x:2}
、// returns 3
を次の反復でa
として
反復2:a = 3
、b = {x:2}
はNaN
を返します
数値リテラル3
には(通常)x
というプロパティがないため、undefined
およびundefined + b.x
はNaN
を返し、NaN + <anything>
は常にNaN
です
明確化:マジックナンバーを減らすためにオプションのパラメーターを渡すことで数値プリミティブを取得するという考えに同意できないため、このスレッドの他のトップアンサーよりも自分のメソッドの方が好きです。その結果、書き込まれる行は少なくなりますが、読みにくくなります。
これを実現するよりクリーンな方法は、初期値を提供することです。
var arr = [{x:1}, {x:2}, {x:4}];
arr.reduce(function (acc, obj) { return acc + obj.x; }, 0); // 7
console.log(arr);
無名関数が初めて呼び出されると、(0, {x: 1})
で呼び出され、0 + 1 = 1
を返します。次回、(1, {x: 2})
で呼び出され、1 + 2 = 3
を返します。その後、(3, {x: 4})
で呼び出され、最終的に7
を返します。
TL; DR、初期値を設定
destructuring の使用
arr.reduce( ( sum, { x } ) => sum + x , 0)
破壊せずに
arr.reduce( ( sum , cur ) => sum + cur.x , 0)
TypeScriptを使用
arr.reduce( ( sum, { x } : { x: number } ) => sum + x , 0)
破壊方法を試してみましょう:
const arr = [ { x: 1 }, { x: 2 }, { x: 4 } ]
const result = arr.reduce( ( sum, { x } ) => sum + x , 0)
console.log( result ) // 7
これの鍵は、初期値を設定することです。戻り値は、次の反復の最初のパラメーターになります。
受け入れられた答えは、「オプションの」値を渡さないことを提案しています。慣用的な方法は、2番目のパラメーターalwaysを含めることであるため、これは間違っています。どうして? 3つの理由:
1。 Dangerous-初期値を渡さないことは危険であり、コールバック関数が不注意だと副作用や突然変異を引き起こす可能性があります。
見よ
const badCallback = (a,i) => Object.assign(a,i)
const foo = [ { a: 1 }, { b: 2 }, { c: 3 } ]
const bar = foo.reduce( badCallback ) // bad use of Object.assign
// Look, we've tempered with the original array
foo // [ { a: 1, b: 2, c: 3 }, { b: 2 }, { c: 3 } ]
ただし、初期値を使用してこの方法で行った場合:
const bar = foo.reduce( badCallback, {})
// foo is still OK
foo // {a:1,b:2,c:3}
レコードについては、常にこのObject.assign({}, a, b, c)
のように割り当てます。最初のパラメーターを空のオブジェクトに設定します{}
2-優れた型推論--TypeScriptのようなツールまたはVS Codeのようなエディターを使用する場合、コンパイラーに初期値とその間違っているとエラーをキャッチできます。初期値を設定しないと、多くの状況で推測できず、不気味なランタイムエラーが発生する可能性があります。
3-Functorsを尊重する-JavaScriptは、その内部の機能的な子が解き放たれたときに最も良く輝きます。関数の世界では、配列の「折り畳み」またはreduce
の方法に関する標準があります。配列に catamorphism を折りたたみまたは適用すると、その配列の値を取得して新しい型を構築します。最終的な型が配列、別の配列、または他の型の値である場合でも、結果の型を伝える必要があります。
別の方法で考えてみましょう。 JavaScriptでは、関数をデータのように渡すことができます。これがコールバックの仕組みです。次のコードの結果は何ですか?
[1,2,3].reduce(callback)
数字を返しますか?オブジェクト?これにより、より明確になります
[1,2,3].reduce(callback,0)
関数型プログラミングの仕様については、こちらをご覧ください。 https://github.com/fantasyland/fantasy-land#foldable
reduce
メソッドは2つのパラメーターを取ります。
Array.prototype.reduce( callback, initialItem )
callback
関数は、次のパラメーターを取ります
(accumulator, itemInArray, indexInArray, entireArray) => { /* do stuff */ }
initialItem
が指定されると、最初の過去にaccumulator
としてcallback
関数に渡され、itemInArray
が配列の最初の項目になります。
initialItem
が設定されていない場合、reduce
は配列の最初の項目をinitialItem
として受け取り、2番目の項目をitemInArray
として渡します。これは混乱を招く可能性があります。
Reduceの初期値を常に設定することをお勧めします。
Array.prototype.reduce
に関する完全な記事については、以下を参照してください。
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
お役に立てれば!
他の人がこの質問に答えましたが、私は別のアプローチに放り込むと思いました。 a.xの合計に直接移動するのではなく、マップ(a.xからx)を組み合わせて縮小(xを追加)することができます。
arr = [{x:1},{x:2},{x:4}]
arr.map(function(a) {return a.x;})
.reduce(function(a,b) {return a + b;});
確かに、おそらく少し遅くなりますが、オプションとして言及する価値があると思いました。
指摘されたものを形式化するために、レデューサーは、偶然同じタイプである可能性のある2つの引数を取り、最初の引数に一致するタイプを返すカタモルフィズムです。
function reducer (accumulator: X, currentValue: Y): X { }
つまり、リデューサーの本体は、currentValue
およびaccumulator
の現在の値を新しいaccumulator
の値に変換する必要があります。
これは、アキュムレータと要素の値の両方がたまたま同じ型であるため(ただし、目的が異なるため)、追加するときに簡単に機能します。
[1, 2, 3].reduce((x, y) => x + y);
それらはすべて数字なので、これは機能します。
[{ age: 5 }, { age: 2 }, { age: 8 }]
.reduce((total, thing) => total + thing.age, 0);
現在、アグリゲーターに開始値を与えています。ほとんどの場合、開始値はアグリゲーターが期待するタイプ(最終値として出てくると予想されるタイプ)でなければなりません。これを強制されることはありません(そうすべきではありません)が、留意することが重要です。
それがわかれば、他のn:1関係の問題について意味のある削減を書くことができます。
繰り返される単語の削除:
const skipIfAlreadyFound = (words, Word) => words.includes(Word)
? words
: words.concat(Word);
const deduplicatedWords = aBunchOfWords.reduce(skipIfAlreadyFound, []);
見つかったすべての単語の数を提供する:
const incrementWordCount = (counts, Word) => {
counts[Word] = (counts[Word] || 0) + 1;
return counts;
};
const wordCounts = words.reduce(incrementWordCount, { });
配列の配列を単一のフラット配列に縮小する:
const concat = (a, b) => a.concat(b);
const numbers = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
].reduce(concat, []);
さまざまなものから、1:1に一致しない単一の値に移行する場合は、reduceを検討する必要があります。
実際、マップとフィルターは両方ともリダクションとして実装できます。
const map = (transform, array) =>
array.reduce((list, el) => list.concat(transform(el)), []);
const filter = (predicate, array) => array.reduce(
(list, el) => predicate(el) ? list.concat(el) : list,
[]
);
これにより、reduce
の使用方法に関するコンテキストがさらに提供されることを願っています。
これにまだ1つ追加しているのは、配列要素が関数であるため、入力および出力タイプが特に動的であることを期待している場合です。
const compose = (...fns) => x =>
fns.reduceRight((x, f) => f(x), x);
const hgfx = h(g(f(x)));
const hgf = compose(h, g, f);
const hgfy = hgf(y);
const hgfz = hgf(z);
Reduceの各ステップで、新しい{x:???}
オブジェクトを返していません。したがって、次のいずれかを行う必要があります。
arr = [{x:1},{x:2},{x:4}]
arr.reduce(function(a,b){return a + b.x})
またはあなたがする必要があります
arr = [{x:1},{x:2},{x:4}]
arr.reduce(function(a,b){return {x: a.x + b.x}; })
最初の反復では、「a」が配列の最初のオブジェクトになるため、ax + bxは1 + 2つまり3を返します。次の反復では、返された3がaに割り当てられます。 NaNを提供します。
簡単な解決策は、最初に配列の数値をマッピングし、次に以下のようにそれらを減らすことです。
arr.map(a=>a.x).reduce(function(a,b){return a+b})
ここでarr.map(a=>a.x)
は数字の配列[1,2,4]を提供します。現在.reduce(function(a,b){return a+b})
を使用すると、これらの数字を簡単に追加できます。
別の簡単な解決策は、次のように「a」に0を割り当てることにより、初期合計をゼロとして提供することです。
arr.reduce(function(a,b){return a + b.x},0)
最初のステップでは、a
の値は1で、b
の値は2ですが、2 + 1が返され、次のステップではb
の値が戻り値from step 1 i.e 3
になり、b.x
になります。未定義になり...未定義+ anyNumberはNaNになり、その結果が得られます。
arr.reduce(function(a,b){return a + b.x},0);
削減関数はコレクションを反復処理します
arr = [{x:1},{x:2},{x:4}] // is a collection
arr.reduce(function(a,b){return a.x + b.x})
に翻訳する:
arr.reduce(
//for each index in the collection, this callback function is called
function (
a, //a = accumulator ,during each callback , value of accumulator is
passed inside the variable "a"
b, //currentValue , for ex currentValue is {x:1} in 1st callback
currentIndex,
array
) {
return a.x + b.x;
},
accumulator // this is returned at the end of arr.reduce call
//accumulator = returned value i.e return a.x + b.x in each callback.
);
各インデックスコールバック中に、変数「accumulator」の値がコールバック関数の「a」パラメーターに渡されます。 「アキュムレータ」を初期化しない場合、その値は未定義になります。 undefined.xを呼び出すとエラーが発生します。
これを解決するには、ケーシーの答えが上に示したように、値で「アキュムレーター」を初期化します。
「reduce」関数の詳細を理解するには、この関数のソースコードを参照することをお勧めします。 Lodashライブラリには、ES6の「reduce」関数とまったく同じように機能するreduce関数があります。
リンクは次のとおりです。 ソースコードの削減
//fill creates array with n element
//reduce requires 2 parameter , 3rd parameter as a length
var fibonacci = (n) => Array(n).fill().reduce((a, b, c) => {
return a.concat(c < 2 ? c : a[c - 1] + a[c - 2])
}, [])
console.log(fibonacci(8))
すべてのx
小道具の合計を返すには:
arr.reduce(
(a,b) => (a.x || a) + b.x
)