如何调用对象数组上的减少和他们的属性?

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

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

我有理由相信a.x在某种程度上是undefined

以下工作正常

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

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

421703 次浏览

在第一次迭代之后,你返回一个数字,然后试图获得它的属性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}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的属性,因此它是undefinedundefined + b.x返回NaN,而NaN + <anything>总是NaN

澄清:在这个线程中,我更喜欢我的方法而不是其他顶部的答案,因为我不同意这样的想法,即通过一个可选参数来减少一个神奇的数字来获得一个数字原语是更干净的。这可能会导致写的行数更少,但在我看来,可读性更差。

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

不可否认,它可能会稍微慢一些,但我认为值得把它作为一个选项提出来。

更简洁的方法是提供一个初始值作为reduce的第二个参数:

var arr = [{x:1}, {x:2}, {x:4}];
var result = arr.reduce(function (acc, obj) { return acc + obj.x; }, 0);
console.log(result);  // 7

第一次调用匿名函数时,使用(0, {x: 1})调用它并返回0 + 1 = 1。下一次,它被(1, {x: 2})调用并返回1 + 2 = 3。然后用(3, {x: 4})调用它,最终返回7

这也处理数组为空的情况,返回0

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数组。当你对数组折叠或应用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 */ }

在第一次迭代中,

  • 如果提供了initialItemreduce函数将initialItem作为accumulator传递,将数组的第一项作为itemInArray传递。

  • 如果initialItem提供了reduce函数将数组中的第一项传递为initialItem,将数组中的第二项传递为itemInArray,这可能是令人困惑的行为。

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

您可以在以下地址查看文档:

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

希望这能有所帮助!

为了将所指出的内容形式化,reducer是一种变形,它接受两个可能碰巧是同一类型的参数,并返回与第一个参数匹配的类型。

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的值时,都可以考虑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);

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

在每次索引回调期间,变量accumulator的值为 在回调函数中传入"a"参数。如果不初始化"accumulator",它的值将是undefined。调用定义。X会给出误差。< / p >

要解决这个问题,请使用0初始化“accumulator”,如Casey的答案所示。

为了理解“reduce”函数的输入输出,我建议你看看这个函数的源代码。 Lodash库有reduce函数,它的工作原理与ES6中的“reduce”函数完全相同

以下是链接: 减少源代码 < / p >

在第一步中,它将正常工作,因为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);

数组缩减函数接受三个参数,即initialValue(默认值) 它是0),累加器和当前值。 默认情况下,initialValue的值为“0”。这是由 蓄电池< / p >

让我们用代码来看看。

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

同样的例子还有initial Value:

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,可以给任何我的意思{},[]和数字


  

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

对于第一次迭代,'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)

你不应该使用a.x作为累加器,相反你可以这样做 ' arr = [{x:1},{x:2},{x:4}]

.

Arr.reduce(函数(a,b){a + b.x},0) '

返回所有x道具的和:

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

如果你有一个包含大量数据的复杂对象,比如一个对象数组,你可以采取一步一步的方法来解决这个问题。

如:

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

首先,你应该将你的数组映射到一个你感兴趣的新数组,在这个例子中它可能是一个新的值数组。

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

这个回调函数将返回一个只包含原始数组中的值的新数组,并将其存储在values const中。现在你的values const是一个这样的数组:

values = [10, 20];

现在你已经准备好执行reduce了:

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

如您所见,reduce方法多次执行回调函数。对于每一次,它都取数组中该项的当前值,并与累加器相加。为了正确地求和,你需要将累加器的初始值设置为reduce方法的第二个参数。

现在你有了新的const sum值为30。

您可以使用减少方法如下所示;如果您将0(零)更改为1或其他数字,它会将其添加到总数中。例如,这个例子给出的总数是31,但是如果我们把0改为1,总数将是32。

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


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

我在ES6中做了一点改进:

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

返回数

我曾经遇到过这种情况,我所做的是将我的解决方案包装在一个函数中,使其在我的环境中可重用,就像这样:

const sumArrayOfObject =(array, prop)=>array.reduce((sum, n)=>{return sum + n[prop]}, 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标准道歉:-)

只是我对用object literal设置默认值的看法。

  let arr = [{
duration: 1
}, {
duration: 3
}, {
duration: 5
}, {
duration: 6
}];
    

const out = arr.reduce((a, b) => {
return {
duration: a.duration + b.duration
};
}, {
duration: 0
});
    

console.log(out);

let temp =[{x:1},
{x:2},
{x:3},
{x:4}];
let sum = temp.map(element => element.x).reduce((a, b) => a+ b , 0)
console.log(sum);

我们可以用这种方法求x的和

输出:10

泛型typescript函数:

const sum = <T>(array: T[], predicate: (value: T, index: number, array: T[]) => number) => {
return array.reduce((acc, value, index, array) => {
return acc + predicate(value, index, array);
}, 0);
};

例子:

const s = sum(arr, (e) => e.x);
var arr = [{x:1}, {x:2}, {x:3}];
arr.map(function(a) {return a.x;})
.reduce(function(a, b) {return a + b});
console.log(arr);
//I tried using the following code and the result is the data array
//result = [{x:1}, {x:2}, {x:3}];
var arr2 = [{x:1}, {x:2}, {x:3}]
.reduce((total, thing) => total + thing.x, 0);
console.log(arr2);
// and I changed the code to like this and it worked.
// result = 6

我们可以使用数组减少方法来创建新的对象,我们可以使用这个选项来求和或过滤

const FRUITS = ["apple", "orange"]
const fruitBasket = {banana: {qty: 10, kg:3}, apple: {qty: 30, kg:10}, orange: {qty: 1, kg:3}}


const newFruitBasket = FRUITS.reduce((acc, fruit) => ({ ...acc, [fruit]: fruitBasket[fruit]}), {})


console.log(newFruitBasket)