Javascript减少了对象数组

YXD*_*YXD 190 javascript reduce functional-programming node.js

说我想要a.x为每个元素求和arr.

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

我有理由相信ax在某些时候是不确定的.

以下工作正常

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

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

Jar*_*eer 240

在第一次迭代之后,你将返回一个数字,然后尝试获取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}
Run Code Online (Sandbox Code Playgroud)

从评论中添加的解释:

在下一次迭代中[].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
}
Run Code Online (Sandbox Code Playgroud)

迭代1: ,,a = {x:1} 如在下次迭代b = {x:2}// returns 3a

迭代2 : a = 3, b = {x:2}返回NaN

数字文字3不(通常)具有被称为的属性x,undefined并且 undefined + b.x返回NaN并且NaN + <anything>始终是NaN

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

  • Sure reduce接受一个函数来对数组中的每个元素执行操作.每次返回一个值,该值在操作中用作下一个"a"变量.所以第一次迭代a = {x:1},b = {x:2}然后第二次迭代a = {x:3}(第一次迭代的组合值),b = {x:4}.你试图添加3.x + bx的第二次迭代中的例子的问题,3没有一个名为x的属性,所以它返回undefined并将其添加到bx(4)返回Not a Number (6认同)

Cas*_*Chu 238

更简洁的方法是提供初始值:

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

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

  • 如果您有初始值(reduce 的第二个参数),这就是解决方案 (3认同)
  • 这比接受的答案更少代码,更直接 - 有什么方法可以让接受的答案比这更好? (2认同)
  • es6 方式 var arr = [{x:1}, {x:2}, {x:4}]; 让 a =arr.reduce( (acc, obj) =&gt; acc + obj.x, 0); 控制台日志(一); (2认同)

Bab*_*ess 54

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
Run Code Online (Sandbox Code Playgroud)

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

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

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

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 } ]
Run Code Online (Sandbox Code Playgroud)

但是,如果我们这样做,初始值:

const bar = foo.reduce( badCallback, {})
// foo is still OK
foo // {a:1,b:2,c:3}
Run Code Online (Sandbox Code Playgroud)

为了记录,总是像这样分配Object.assign.将第一个参数设置为空对象Object.assign({}, a, b, c)

2 - 更好的类型推断 - 当使用像Typescript这样的工具或像VS Code这样的编辑器时,你会得到告诉编译器初始化的好处,如果你做错了它可以捕获错误.如果您没有设置初始值,在许多情况下它可能无法猜测,您最终可能会遇到令人毛骨悚然的运行时错误.

3 - 尊重Functors - 当内部功能性孩子被释放时,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 )
Run Code Online (Sandbox Code Playgroud)

callback函数采用以下参数

(accumulator, itemInArray, indexInArray, entireArray) => { /* do stuff */ }
Run Code Online (Sandbox Code Playgroud)

initialItem被提供,它是通过在作为 accumulator进入callback上第一过去功能和itemInArray是数组中的第一项.

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

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

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

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

希望这可以帮助!

  • 解构正是我想要的。奇迹般有效。谢谢你! (3认同)

RHS*_*ger 22

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

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

不可否认,它可能会略微变慢,但我认为值得一提.


Nor*_*ard 7

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

function reducer (accumulator: X, currentValue: Y): X { }
Run Code Online (Sandbox Code Playgroud)

这意味着减速器的主体需要转换currentValue,并将当前值转换accumulator为新的值accumulator.

这在添加时以直接的方式工作,因为累加器和元素值都恰好是相同的类型(但用于不同的目的).

[1, 2, 3].reduce((x, y) => x + y);
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为它们都是数字.

[{ age: 5 }, { age: 2 }, { age: 8 }]
  .reduce((total, thing) => total + thing.age, 0);
Run Code Online (Sandbox Code Playgroud)

现在我们给聚合器一个起始值.在绝大多数情况下,起始值应该是您期望聚合器的类型(您希望作为最终值出现的类型).虽然你没有被迫这样做(而且不应该这样做),但要记住这一点很重要.

一旦你知道了,你可以为其他n:1关系问题编写有意义的减少.

删除重复的单词:

const skipIfAlreadyFound = (words, word) => words.includes(word)
    ? words
    : words.concat(word);

const deduplicatedWords = aBunchOfWords.reduce(skipIfAlreadyFound, []);
Run Code Online (Sandbox Code Playgroud)

提供所有找到的单词的计数:

const incrementWordCount = (counts, word) => {
  counts[word] = (counts[word] || 0) + 1;
  return counts;
};
const wordCounts = words.reduce(incrementWordCount, { });
Run Code Online (Sandbox Code Playgroud)

将数组数组减少为单个平面数组:

const concat = (a, b) => a.concat(b);

const numbers = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
].reduce(concat, []);
Run Code Online (Sandbox Code Playgroud)

无论何时你想要从一系列事物,到一个与1:1不匹配的值,你可以考虑减少.

实际上,map和filter都可以实现为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,
  []
);
Run Code Online (Sandbox Code Playgroud)

我希望这为如何使用提供了一些进一步的背景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);
Run Code Online (Sandbox Code Playgroud)


Jam*_*ong 5

在 reduce 的每一步,你都不会返回一个新{x:???}对象。所以你要么需要做:

arr = [{x:1},{x:2},{x:4}]
arr.reduce(function(a,b){return a + b.x})
Run Code Online (Sandbox Code Playgroud)

或者你需要做

arr = [{x:1},{x:2},{x:4}]
arr.reduce(function(a,b){return {x: a.x + b.x}; }) 
Run Code Online (Sandbox Code Playgroud)

  • 第一个示例需要一个默认值(例如 0),否则在第一次迭代中未定义“a”。 (4认同)

fat*_*jad 5

对于第一次迭代,“ a”将是数组中的第一个对象,因此ax + bx将返回1 + 2,即3。现在,在下一次迭代中,返回的3被分配给a,所以a是一个现在n的数字,称为ax将给NaN。

一个简单的解决方案是首先将数字映射到数组中,然后按如下方式减少它们:

arr.map(a=>a.x).reduce(function(a,b){return a+b})
Run Code Online (Sandbox Code Playgroud)

这里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)
Run Code Online (Sandbox Code Playgroud)


Joã*_*res 5

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

例如:

const myArray = [{ id: 1, value: 10}, { id: 2, value: 20}];
Run Code Online (Sandbox Code Playgroud)

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

const values = myArray.map(obj => obj.value);
Run Code Online (Sandbox Code Playgroud)

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

values = [10, 20];
Run Code Online (Sandbox Code Playgroud)

现在您已准备好执行减少操作:

const sum = values.reduce((accumulator, currentValue) => { return accumulator + currentValue; } , 0);
Run Code Online (Sandbox Code Playgroud)

正如你所看到的,reduce方法多次执行回调函数。每次,它都会获取数组中项目的当前值并与累加器求和。因此,要正确求和,您需要将累加器的初始值设置为 reduce 方法的第二个参数。

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