web-dev-qa-db-ja.com

オブジェクトの配列のJavascript削減

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

最初の例で何が間違っていますか?

167
YXD

最初の反復の後、数値を返し、その変数のプロパティ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 = 3b = {x:2}NaNを返します

数値リテラル3には(通常)xというプロパティがないため、undefinedおよびundefined + b.xNaNを返し、NaN + <anything>は常にNaNです

明確化:マジックナンバーを減らすためにオプションのパラメーターを渡すことで数値プリミティブを取得するという考えに同意できないため、このスレッドの他のトップアンサーよりも自分のメソッドの方が好きです。その結果、書き込まれる行は少なくなりますが、読みにくくなります。

217
JaredMcAteer

これを実現するよりクリーンな方法は、初期値を提供することです。

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を返します。

209
Casey Chu

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

お役に立てれば!

35
Babakness

他の人がこの質問に答えましたが、私は別のアプローチに放り込むと思いました。 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;});

確かに、おそらく少し遅くなりますが、オプションとして言及する価値があると思いました。

22
RHSeeger

指摘されたものを形式化するために、レデューサーは、偶然同じタイプである可能性のある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);
7
Norguard

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}; }) 
4
Jamie Wong

最初の反復では、「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)
2
fatimasajjad

最初のステップでは、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);

2
Nitesh Ranjan

削減関数はコレクションを反復処理します

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関数があります。

リンクは次のとおりです。 ソースコードの削減

0
Deen John
    
  
 //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))
0
Rajesh Bose

すべてのx小道具の合計を返すには:

arr.reduce(
(a,b) => (a.x || a) + b.x 
)
0
Black Jack