Javascript减少了对象数组

时间:2011-04-20 14:31:57

标签: javascript functional-programming node.js reduce

假设我想为a.x中的每个元素求和arr

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

在第一个例子中我做错了什么?

20 个答案:

答案 0 :(得分:215)

在第一次迭代之后,你返回一个数字,然后尝试获取它的属性x以添加到下一个undefined对象,并且涉及undefined的数学结果为{ {1}}。

尝试返回一个包含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的每次迭代的返回值。

迭代1:aa = {x:1}b = {x:2}在迭代2中分配给{x: 3}

迭代2:aa = {x:3}

你的例子的问题是你要返回一个数字文字。

b = {x:4}

迭代1:function (a, b) { return a.x + b.x; // returns number literal } a = {x:1}b = {x:2}在下一次迭代中为// returns 3

迭代2:aa = 3返回b = {x:2}

数字NaN不会(通常)拥有名为3的属性,因此xundefined会返回undefined + b.xNaN总是NaN + <anything>

澄清:我更喜欢我的方法而不是这个帖子中的另一个顶级答案,因为我不同意传递一个可选参数来减少一个幻数来获得一个数字原语的想法更清晰。它可能会导致写入更少的行,但是它的可读性更低。

答案 1 :(得分:206)

实现这一目标的更简洁方法是提供初始值:

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

答案 2 :(得分:34)

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

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

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

接受的答案建议不要传递“可选”值。这是错误的,因为惯用的方式是包含第二个参数 。为什么?原因有三:

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

Array.prototype.reduce( callback, initialItem )

callback函数采用以下参数

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

提供initialItem后,它会在accumulator函数中首先传入callback函数,itemInArray是数组中的第一项。

如果未设置initialItem,则reduce会将数组中的第一项作为initialItem,并将第二项作为itemInArray传递。这可能是令人困惑的行为。

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

有关Array.prototype.reduce的完整文章,请参阅:

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

希望这有帮助!

答案 3 :(得分:22)

其他人已经回答了这个问题,但我认为我会采用另一种方法。您可以将地图(从a.x到x)和reduce(添加x)组合在一起,而不是直接对a.x求和:

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

不可否认,它可能会稍微慢一些,但我认为值得一提。它可以作为一种选择。

答案 4 :(得分:7)

为了形式化已经指出的内容,reducer是一个catamorphism,它接受两个参数,它们可能是巧合的相同类型,并返回一个匹配第一个参数的类型。

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

这意味着缩减器的主体需要将currentValueaccumulator的当前值转换为新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不匹配的单一值时,你可以考虑减少。

事实上,地图和过滤器都可以实现为缩减:

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

答案 5 :(得分:4)

在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}; }) 

答案 6 :(得分:2)

在第一步中,它会正常工作,因为a的值将为1而b的值将为2,但将返回2 + 1,并在下一步中返回值b的返回值为from step 1 i.e 3,因此b.x将是未定义的...且undefined + anyNumber将为NaN,这就是您获得该结果的原因。

相反,您可以通过将初始值设为零来尝试此操作,即

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

答案 7 :(得分:1)

对于第一次迭代,“ a”将是数组中的第一个对象,因此a.x + b.x将返回1 + 2,即3。 现在,在下一次迭代中,返回的3被分配给a,所以a是现在n调用a.x的数字将为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)

答案 8 :(得分:1)

我曾经遇到过这是我的开发工作,我要做的就是将解决方案包装在一个函数中,以使其在我的环境中可重复使用,就像这样:

const sumArrayOfObject =(array, prop)=>array.reduce((sum, n)=>{return sum + n[prop]}, 0)

答案 9 :(得分:0)

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

答案 10 :(得分:0)

返回所有x道具的总和:

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

答案 11 :(得分:0)

如果您有一个包含大量数据的复杂对象(例如对象数组),则可以采用逐步的方法来解决此问题。

例如:

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。

答案 12 :(得分:0)

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

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

答案 13 :(得分:0)

您可以使用reduce方法作为波纹管;如果将0(零)更改为1或其他数字,它将被添加到总数中。例如,此示例给出的总数为31,但是如果将0更改为1,则总数为32。

const batteryBatches = [4, 5, 3, 4, 4, 6, 5];

let totalBatteries= batteryBatches.reduce((acc,val) => acc + val ,0)

答案 14 :(得分:0)

我在ES6中做了一些改进:

arr.reduce((a, b) => ({x: a.x + b.x})).x

返回号码

答案 15 :(得分:0)

reduce函数迭代集合

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

在每个索引回调期间,变量值#34;累加器&#34;是   进入&#34; a&#34;回调函数中的参数。如果我们没有初始化&#34;累加器&#34;,它的值将是未定义的。调用undefined.x会给你错误。

     

要解决此问题,请初始化&#34;累加器&#34;值 0 ,凯西的答案如上所示。

了解&#34;减少&#34;的内容。函数,我建议你看一下这个函数的源代码。 Lodash库具有减少功能,其功能与&#34;减少&#34;完全相同。 ES6中的功能。

这是链接: reduce source code

答案 16 :(得分:0)

function aggregateObjectArrayByProperty(arr, propReader, aggregator, initialValue) {
  const reducer = (a, c) => {
    return aggregator(a, propReader(c));
  };
  return arr.reduce(reducer, initialValue);
}

const data = [{a: 'A', b: 2}, {a: 'A', b: 2}, {a: 'A', b: 3}];

let sum = aggregateObjectArrayByProperty(data, function(x) { return x.b; }, function(x, y) { return x + y; }, 0);
console.log(`Sum = ${sum}`);
console.log(`Average = ${sum / data.length}`);

let product = aggregateObjectArrayByProperty(data, function(x) { return x.b; }, function(x, y) { return x * y; }, 1);
console.log(`Product = ${product}`);

刚刚根据之前给出的解决方案编写了一个通用函数。我是一名 Java 开发人员,对于任何错误或非 JavaScript 标准,我深表歉意:-)

答案 17 :(得分:0)

你可以结合 map 和 reduce:

arr.map((value, index) => value.x).reduce(function(acc, value){ return acc + value; })

答案 18 :(得分:-1)

数组归约函数采用三个参数,即initialValue(默认 它是0),累加器和当前值。 默认情况下,initialValue的值为“ 0”。这是由 累加器

让我们在代码中看到它。

M = [[18, 34, 54, 65],
     [18, 12, 54, 65],
     [21, 43, 55, 78]]
n = len(M)  # i.e. 3
uf = np.frompyfunc(f, 2, 1)
Marray = np.empty(n, dtype='O')
for i in range(n):
    Marray[i] = M[i]
similarities = uf.outer(Marray, Marray).astype('d')  # convert to float instead object type
print(similarities)
# array([[4., 3., 0.],
#        [3., 4., 0.],
#        [0., 0., 4.]])

现在具有相同初始值的示例:

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

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

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

一个要记住的事情是InitialValue,默认情况下为0,并且可以给出我的意思是{},[]和数字

答案 19 :(得分:-1)

您不应将a.x用作累加器,而是可以这样做                      `arr = [{x:1},{x:2},{x:4}]

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