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})是第二个例子。
Jamie Wong

1
感谢您的更正。我在这里遇到了reduce:developer.mozilla.org/en/JavaScript/Reference/Global_Objects/…–
YXD

6
@Jamie Wong它实际上是JavaScript 1.8的一部分
JaredMcAteer 2011年

@OriginalSyn是的-刚刚看到了。有趣,但是由于它没有完整的本机支持,因此在回答此类问题时,实现仍然很重要。
Jamie Wong

3
JavaScript版本只是Firefox解释器的版本,引用它们很混乱。只有ES3和ES5。
雷诺斯2011年

Answers:


279

第一次迭代后,您将返回一个数字,然后尝试获取该数字的属性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回报NaN,并NaN + <anything>始终NaN

澄清:我更喜欢我的方法,而不是该线程中的其他最高答案,因为我不同意这样的想法,即传递一个可选参数以使用幻数来减少以得出数字基元会更干净。这可能导致写入的行数减少,但imo的可读性降低。


7
当然reduce需要一个函数来对数组中的每个元素执行操作。每次它返回一个值,该值用作操作中的下一个“ a”变量。因此,第一次迭代a = {x:1},b = {x:2},然后第二次迭代a = {x:3}(第一次迭代的组合值),b = {x:4}。您的示例在第二次迭代中遇到的问题是尝试添加3.x + bx,3没有名为x的属性,因此它返回undefined并将其添加到bx(4)中返回Not Number
JaredMcAteer 2011年

我理解其中的解释,但仍然看不到{x:...}如何阻止迭代2调用ax?x代表什么?我尝试将这种方法与一种方法结合使用,但似乎无法正常工作
mck 2012年

实际上,只需阅读stackoverflow.com/questions/2118123/…并了解其工作原理..但是,当您拥有一个方法而不是简单的属性时,将如何做呢?
mck 2012年

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
这是解决方案,如果你有一个初始值(减少的第二个参数)
TJ。

谢谢,我一直在寻找带有的第二个参数的提示reduce
nilsole 2014年

与接受的答案相比,这是更少的代码,而且更直接-是否有任何方法可以使接受的答案比这个更好?
jbyrd

1
这帮助我解决了有时数组长度仅为1的情况。
HussienK

1
es6 way var arr = [{x:1},{x:2},{x:4}]; 让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)

带打字稿

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

关键是设置初始值。返回值成为下一次迭代的第一个参数。

最佳答案中使用的技术不是惯用的

接受的答案建议不通过“可选”值。这是错误的,因为惯用的方法是始终包含第二个参数。为什么?三个原因:

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方法有两个参数,

Array.prototype.reduce( callback, initialItem )

callback函数采用以下参数

(accumulator, itemInArray, indexInArray, entireArray) => { /* do stuff */ }

对于第一次迭代,

  • 如果initialItem提供,则reduce函数将传递initialItemaccumulator,将数组的第一项传递为itemInArray

  • 如果initialItem提供时,reduce功能传递数组作为中的第一项initialItem和所述阵列中的第二项itemInArray可以被混淆的行为。

我教导并建议始终设置reduce的初始值。

您可以在以下位置查看文档:

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce

希望这可以帮助!


1
销毁正是我想要的。奇迹般有效。谢谢!
AleksandrH

24

其他人已经回答了这个问题,但我想我会采用另一种方法。您可以直接映射(从ax到x)并减少(添加x),而不是直接求和ax。

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

诚然,它可能会稍微慢一些,但是我认为值得一提。


8

为了使所指出的内容形式化,reducer是一种变形,它采用巧合的方式接受两个可能是相同类型的参数,然后返回与第一个参数匹配的类型。

function reducer (accumulator: X, currentValue: Y): X { }

这意味着减速器的主体需要将转换currentValue并将的当前值转换accumulator为new的值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方法。

实际上,map和filter都可以实现为简化:

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

我还没有介绍的另一种情况是,当期望输入和输出类型专门是动态的时,因为数组元素是函数:

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,我认为从一开始就认为它是一种超凡脱俗可能会让许多人
反感

6

对于第一次迭代,“ a”将是数组中的第一个对象,因此ax + bx将返回1 + 2,即3。现在,在下一次迭代中,返回的3被分配给a,所以a是一个现在n的数字,称为ax将给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})将简单地添加这些数字而不会产生任何麻烦

另一个简单的解决方案是通过将0分配给'a'来提供初始和为零:

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”。
格里芬2014年

2

第一步,它将正常工作,因为will的值a将为1,而bwill 的值为2,但是由于将返回2 + 1,而在下一步中,of的值b将是返回值from step 1 i.e 3,因此b.x将是不确定的。 .and undefined + anyNumber将为NaN,这就是为什么要得到该结果的原因。

相反,您可以尝试通过将初始值设置为零(即

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


请更新此内容,以显示您的方法与其他方法有何不同。
kwelch

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方法的第二个参数。

现在您有了新的const和,其值为30。


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库具有reduce函数,其功能与ES6中的reduce函数完全相同。

这里是链接: 减少源代码


0

返回所有x道具的总和:

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

3
提供有关此答案为什么比其他已接受或赞成的答案更好的上下文,可以在用户寻找问题的解决方案时提供帮助。
安德鲁

0

您可以使用地图并简化功能

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

-1

数组归约函数采用三个参数,即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

同样适用于对象数组(当前的stackoverflow问题):

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

要注意的一件事情是,默认情况下,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(function(a,b){a + bx},0)`

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.