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


225

a.x各要素を合計したいとしarrます。

arr = [{x:1},{x:2},{x:4}]
arr.reduce(function(a,b){return a.x + b.x})
>> NaN

ある時点で斧が未定義であると信じる原因があります。

以下は正常に動作します

arr = [1,2,4]
arr.reduce(function(a,b){return a + b})
>> 7

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


1
また、私はあなたarr.reduce(function(a,b){return a + b})が2番目の例で意味すると思います。
Jamie Wong

1
訂正ありがとうございます。私はここに削減出会っ:developer.mozilla.org/en/JavaScript/Reference/Global_Objects/...
YXD

6
@Jamie Wongは実際にはJavaScript 1.8の一部です
JaredMcAteer

@OriginalSynええ-ちょうどそれを見た。興味深いですが、完全にネイティブサポートされていないため、このような質問に答える場合でも実装は重要です。
Jamie Wong

3
JavaScriptのバージョンは、Firefoxインタープリターの単なるバージョンであり、それらを参照するのは混乱します。ES3とES5のみです。
Raynos、2011

回答:


279

最初の繰り返しの後の数を返すと、そのプロパティを取得しようとしているyour're 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}

コメントから追加された説明:

各反復の戻り値は、次の反復で変数[].reduceとして使用されaます。

反復1: 、、a = {x:1} に割り当てられた反復で2b = {x:2}{x: 3}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 3a

イテレーション2: a = 3b = {x:2}リターンNaN

数値リテラルは3(通常)と呼ばれる性質を持っていないx、それはですので、undefinedundefined + b.xリターンNaNNaN + <anything>常にありますNaN

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


7
確かにreduceは、配列内の各要素に対して操作を実行する関数を受け取ります。操作の次の「a」変数として使用される値を返すたびに。したがって、最初の反復a = {x:1}、b = {x:2}、2番目の反復a = {x:3}(最初の反復の結合値)、b = {x:4}。3.x + bxを追加しようとした2回目の反復の例の問題、3にはxというプロパティがないため、未定義を返し、それをbx(4)に追加するとNot a Number
JaredMcAteer

説明は理解しましたが、{x:...}が反復2によるaxの呼び出しをどのように妨げているのかわかりません。そのxは何を意味していますか?この方法をメソッドで使用してみましたが、機能していないようです
mck

実際には、stackoverflow.com / questions / 2118123 /…を読んで、それがどのように機能するかを理解するだけです。
mck

278

これを行うためのより明確な方法は、初期値を提供することです。

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ます。


1
これは、初期値(reduceの2番目のパラメーター)
TJ

ありがとう、私はの2番目の引数のヒントを探していましたreduce
nilsole

これは受け入れられた回答よりもコードが少なく、より直接的です-受け入れられた回答がこれより優れている方法はありますか?
jbyrd 2016

1
これは、配列の長さがたった1である場合の事例を私が手助けするのに役立ちました。
HussienK 2016

1
es6 way var arr = [{x:1}、{x:2}、{x:4}]; let a = arr.reduce((acc、obj)=> acc + obj.x、0); console.log(a);
amar ghodke

76

TL; DR、初期値を設定

使い方な破壊を

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番目のパラメーターが常に含まれるからです。どうして?3つの理由:

1.危険 -初期値を渡さないことは危険であり、コールバック関数が不注意な場合、副作用や変異を引き起こす可能性があります。

見よ

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 tampered 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空のオブジェクトに設定します。このように:Object.assign({}, a, b, c)

2-型推論の 改善-TypescriptのようなツールやVS Codeのようなエディターを使用すると、コンパイラーにイニシャルを伝えるメリットが得られ、間違っているとエラーをキャッチできます。初期値を設定しないと、多くの状況でそれを推測できず、不気味なランタイムエラーが発生する可能性があります。

3-ファンクタを尊重する -JavaScriptは、その内部の機能的な子が解放されたときに最もよく輝きます。機能的な世界では、「折りたたみ」またはreduce配列の方法に関する標準があります。配列にカタモフィズムをフォールドまたは適用すると、その配列の値を取得して新しいタイプを構築します。結果の型を伝達する必要があります。これは、最終的な型が配列、別の配列、またはその他の型の値の型であっても行う必要があります。

別の方法で考えてみましょう。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 */ }

最初の反復では、

  • が指定されている場合initialItemreduce関数はをinitialItemとして渡しaccumulator、配列の最初の項目をとして渡しitemInArrayます。

  • initialItemが指定されていない場合、reduce関数は配列の最初の項目をとして渡し、配列initialItemの2番目の項目をitemInArray混乱させる可能性がある配列として渡します。

私は常に、reduceの初期値を設定することをお勧めします。

次のURLでドキュメントを確認できます。

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce

お役に立てれば!


1
解体はまさに私が欲しかったものです。魅力のように機能します。ありがとうございました!
AleksandrH

24

他の人がこの質問に答えましたが、私は別のアプローチを投げると思いました。直接axを合計するのではなく、マップ(axからx)とリデュース(xを追加)を組み合わせることができます。

arr = [{x:1},{x:2},{x:4}]
arr.map(function(a) {return a.x;})
   .reduce(function(a,b) {return a + b;});

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


8

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

1
型システムに関して明確に説明するための+1。OTOH、私はそれがカタモルフィズムであるという考えで開くことによって多くの人に不快であるかもしれないと思います...
Periata Breatta

6

最初の反復では、「a」は配列の最初のオブジェクトになるため、ax + bxは1 + 2、つまり3を返します。次の反復では、返された3がaに割り当てられるため、aはaxを呼び出すnの数になります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)

5

リデュースの各ステップでは、新しい{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}; }) 

3
最初の例ではデフォルト値(0など)が必要です。それ以外の場合、最初の反復では "a"は未定義です。
グリフィン

2

最初のステップでは、の値はa1であり、の値は2であるため正常に機能しますbが、2 + 1が返され、次のステップではの値がb戻り値from step 1 i.e 3になるためb.x、未定義になります。 .and undefined + anyNumberはNaNになるため、その結果が得られます。

代わりに、ゼロとして初期値を与えることでこれを試すことができます

arr.reduce(function(a,b){return a + b.x},0);


これを更新して、あなたのアプローチが他のアプローチとどう違うかを示してください。
kwelch '12

2

オブジェクトの配列のように、大量のデータを含む複雑なオブジェクトがある場合、これを解決するために段階的なアプローチを取ることができます。

たとえば:

const myArray = [{ id: 1, value: 10}, { id: 2, value: 20}];

まず、配列を目的の新しい配列にマップする必要があります。この例では、値の新しい配列である可能性があります。

const values = myArray.map(obj => obj.value);

このコールバック関数は、元の配列の値のみを含む新しい配列を返し、値constに格納します。これで、値constは次のような配列になります。

values = [10, 20];

これで、削減を実行する準備ができました。

const sum = values.reduce((accumulator, currentValue) => { return accumulator + currentValue; } , 0);

ご覧のとおり、reduceメソッドはコールバック関数を複数回実行します。毎回、配列内のアイテムの現在の値を取り、アキュムレータと合計します。したがって、適切に合計するには、reduceメソッドの2番目の引数としてアキュムレータの初期値を設定する必要があります。

これで、値が30の新しいconst sumが得られました。


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. 
);

各インデックスコールバック中に、変数「アキュムレータ」の値がコールバック関数の「a」パラメータに渡されます。「アキュムレータ」を初期化しない場合、その値は未定義になります。undefined.xを呼び出すと、エラーが発生します。

これを解決するには、上に示したCaseyの回答のように、「アキュムレータ」を値0で初期化します。

「reduce」関数の入出力を理解するには、この関数のソースコードを確認することをお勧めします。Lodashライブラリには、ES6の「reduce」関数とまったく同じように機能するreduce関数があります。

ここにリンクがあります: ソースコードを減らします


0

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

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

3
この回答が他の受け入れられたまたは賛成されたものよりも優れている理由に関するコンテキストを提供すると、ユーザーが質問の解決策を探すときに役立つ場合があります。
Andrew

0

マップを使用して関数を減らすことができます

const arr = [{x:1},{x:2},{x:4}];
const sum = arr.map(n => n.x).reduce((a, b) => a + b, 0);

-1

配列削減関数は、3つのパラメーター、つまり、initialValue(デフォルトは0)、アキュムレーター、および現在の値を取ります。デフォルトでは、initialValueの値は "0"になります。これはアキュムレータによって取得されます

これをコードで見てみましょう。

var arr =[1,2,4] ;
arr.reduce((acc,currVal) => acc + currVal ) ; 
// (remember Initialvalue is 0 by default )

//first iteration** : 0 +1 => Now accumulator =1;
//second iteration** : 1 +2 => Now accumulator =3;
//third iteration** : 3 + 4 => Now accumulator = 7;
No more array properties now the loop breaks .
// solution = 7

ここで、初期値を持つ同じ例:

var initialValue = 10;
var arr =[1,2,4] ;
arr.reduce((acc,currVal) => acc + currVal,initialValue ) ; 
/
// (remember Initialvalue is 0 by default but now it's 10 )

//first iteration** : 10 +1 => Now accumulator =11;
//second iteration** : 11 +2 => Now accumulator =13;
//third iteration** : 13 + 4 => Now accumulator = 17;
No more array properties now the loop breaks .
//solution=17

同じことがオブジェクト配列にも適用されます(現在のスタックオーバーフローの質問):

var arr = [{x:1},{x:2},{x:4}]
arr.reduce(function(acc,currVal){return acc + currVal.x}) 
// destructing {x:1} = currVal;
Now currVal is object which have all the object properties .So now 
currVal.x=>1 
//first iteration** : 0 +1 => Now accumulator =1;
//second iteration** : 1 +2 => Now accumulator =3;
//third iteration** : 3 + 4 => Now accumulator = 7;
No more array properties now the loop breaks 
//solution=7

心に留めておくべきことの1つは、デフォルトでInitialValueであり、0であり、{}、[]および数値を意味するものは何でも指定できます。


-1

    
  
 //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))


-2

アキュムレータにはaxを使用しないでください。代わりに、次のようにできます `arr = [{x:1}、{x:2}、{x:4}]

arr.reduce(関数(a、b){a + bx}、0) `

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.