Nis*_*xit 48 javascript reduce dictionary
我使用了这两种方法,但我对这两种方法的使用感到很困惑.
有什么map可以做但不能做reduce,反之亦然?
注意:我知道如何使用这两种方法我正在质疑这些方法之间的主要区别以及何时需要使用.
Ion*_*zău 129
双方map并reduce具有作为输入数组,你定义一个函数.它们在某种程度上是互补的:map不能为多个元素的数组返回一个单独的元素,而reduce总是会返回最终更改的累加器.
map使用map迭代元素,并为每个元素返回所需的元素.
例如,如果您有一组数字并想要获得它们的方块,则可以执行以下操作:
// A function which calculates the square
const square = x => x * x
// Use `map` to get the square of each number
console.log([1, 2, 3, 4, 5].map(square))Run Code Online (Sandbox Code Playgroud)
reduce使用数组作为输入,您可以根据获取accumulator和current_element参数的回调函数(第一个参数)获取一个单独的元素(比如一个Object,或一个Number,或另一个数组):
const numbers = [1, 2, 3, 4, 5]
// Calculate the sum
console.log(numbers.reduce(function (acc, current) {
return acc + current
}, 0)) // < Start with 0
// Calculate the product
console.log(numbers.reduce(function (acc, current) {
return acc * current
}, 1)) // < Start with 1Run Code Online (Sandbox Code Playgroud)
当你可以用两者做同样的事情时,你应该选择哪一个?试着想象一下代码的外观.对于提供的示例,您可以使用以下方法计算正方形数组reduce:
// Using reduce
[1, 2, 3, 4, 5].reduce(function (acc, current) {
acc.push(current*current);
return acc;
}, [])
// Using map
[1, 2, 3, 4, 5].map(x => x * x)
Run Code Online (Sandbox Code Playgroud)
现在,看看这些,显然第二个实现看起来更好,而且更短.通常你会选择更清洁的解决方案,在这种情况下map.当然,你可以做到这一点reduce,但简而言之,想想哪个更短,最终会更好.
geo*_*rey 11
我认为这个问题是一个非常好的问题,我不能不同意答案,但我感觉我们完全没有抓住重点。
\nmap更抽象地思考reduce可以为我们提供很多非常好的见解。
这个答案分为3部分:
\nmap并reduce以有意义且一致的方式在各种不一定是集合的对象上实现。
它们返回一个对周围算法有用的值,并且它们只关心这个值。
\n它们的主要作用是传达有关结构改造或保存的意图。
\n我所说的“结构”是指一组表征抽象对象的概念属性,例如无序列表或二维矩阵,以及它们在数据结构中的具体化。
\n请注意,两者之间可能存在脱节:
\n\n\n\n
map是严格的结构保持变换。
在其他类型的对象上实现它以掌握其语义价值是有用的:
\nclass A {\n constructor (value) {\n this.value = value\n }\n\n map (f) { \n return new A(f(this.value))\n }\n}\n\nnew A(5).map(x => x * 2); // A { value: 10 }\nRun Code Online (Sandbox Code Playgroud)\n实现的对象map可以具有各种行为,但它们总是返回与您在使用提供的回调转换值时开始使用的相同类型的对象。
Array.map返回与原始数组具有相同长度和相同顺序的数组。
因为它保留了结构,所以map被视为安全操作,但并非每个回调都是相同的。
使用一元回调:map(x => f(x)),数组的每个值与其他值的存在完全无关。
另一方面,使用其他两个参数会引入耦合,这可能不符合原始结构。
\n想象一下删除或重新排序下面数组中的第二项:在地图之前或之后执行此操作不会产生相同的结果。
\n与数组大小耦合:
\n[6, 3, 12].map((x, _, a) => x/a.length);\n// [2, 1, 4]\nRun Code Online (Sandbox Code Playgroud)\n与订购耦合:
\n[\'foo\', \'bar\', \'baz\'].map((x, i) => [i, x]);\n// [[0, \'foo\'], [1, \'bar\'], [2, \'baz\']]\nRun Code Online (Sandbox Code Playgroud)\n与一个特定值耦合:
\n[1, 5, 3].map((x, _, a) => x/Math.max(...a));\n//[ 0.2, 1, 0.6]\nRun Code Online (Sandbox Code Playgroud)\n与邻居的耦合:
\nconst smooth = (x, i, a) => {\n const prev = a[i - 1] ?? x;\n const next = a[i + 1] ?? x;\n const average = (prev + x + next) / 3;\n return Math.round((x + average) / 2);\n};\n\n[1, 10, 50, 35, 40, 1].map(smoothh);\n// [ 3, 15, 41, 38, 33, 8 ]\xe2\x80\x88\nRun Code Online (Sandbox Code Playgroud)\n我建议在调用站点上明确说明是否使用这些参数。
\nconst transfrom = (x, i) => x * i;\n\n\xe2\x9d\x8c array.map(transfrom);\n\xe2\xad\x95 array.map((x, i) => transfrom(x, i));\nRun Code Online (Sandbox Code Playgroud)\n当您将可变参数函数与map.
\xe2\x9d\x8c ["1", "2", "3"].map(parseInt);\n // [1, NaN, NaN]\n\xe2\xad\x95 ["1", "2", "3"].map(x => parseInt(x));\n // [1, 2, 3]\nRun Code Online (Sandbox Code Playgroud)\n\n\n\n
reduce设置一个不受其周围结构影响的值。
再次,让我们在一个更简单的对象上实现它:
\nclass A {\n constructor (value) {\n this.value = value\n }\n\n reduce (f, init) { \n return init !== undefined\n ? f(init, this.value)\n : this.value\n }\n}\n\nnew A(5).reduce(); // 5\n\nconst concat = (a, b) => a.concat(b);\nnew A(5).reduce(concat, []); // [ 5 ]\nRun Code Online (Sandbox Code Playgroud)\n无论您保留该值还是将其放回到其他东西中, 的输出都reduce可以是任何形状。它实际上是相反的map。
数组可以包含多个值或零值,这会产生两个有时相互冲突的要求。
\n需要结合\n\n\n我们如何返回多个没有结构的值?
\n
是不可能的。为了只返回一个值,我们有两种选择:
\n现在不是更有意义了吗?
\n需要初始化\n\n\n如果没有返回值怎么办?
\n
如果reduce返回一个假值,则无法知道源数组是否为空或者是否包含该假值,因此除非我们提供初始值,否则reduce必须抛出异常。
您应该能够f在以下代码片段中猜出减速器的作用:
[a].reduce(f);\n[].reduce(f, a);\nRun Code Online (Sandbox Code Playgroud)\n没有什么。它不被称为。
\n这是一个简单的情况:a是我们想要返回的单个值,因此f不需要。
顺便说一句,这就是我们之前没有在类中强制使用减速器的原因A:因为它只包含一个值。它对于数组是强制性的,因为数组可以包含多个值。
由于只有当您有 2 个或更多值时才会调用减速器,因此说它的唯一目的是将它们组合起来只是一箭之遥。
\n在可变长度的数组上,期望减速器转换值是危险的,因为正如我们发现的那样,它可能不会被调用。
\n当你需要转变价值观和改变形态时,我鼓励你map先这样做。reduce
无论如何,为了可读性,将这两个问题分开是个好主意。
\n因为reduce这是用于实现结构转换的通用工具,所以当您想要返回数组时,如果存在另一种更集中的方法可以完成您想要的操作,我建议您避免使用它。
具体来说,如果您在使用 a 中的嵌套数组时遇到困难,请在使用 之前先map考虑flatMap或。flatreduce
在数组上实现reduce引入了这个反馈循环,其中减速器的第一个参数是前一次迭代的返回值。
不用说,它看起来一点也不像map\ 的回调。
我们可以像这样递归地实现Array.reduce:
const reduce = (f, acc, [current, ...rest]) => \n rest.length == 0\n ? f(acc, current)\n : reduce(f, f(acc, current), rest)\nRun Code Online (Sandbox Code Playgroud)\n这突出了减速器的二进制性质以及它的返回值如何在下一次迭代中f成为新值。acc
我让你说服自己以下内容是正确的:
\nreduce(f, a, [b, c, d])\n\n// is equivalent to\nf(f(f(a, b), c), d)\n\n// or if you squint a little \n((a \xe2\x9d\x8b b) \xe2\x9d\x8b c) \xe2\x9d\x8b d\nRun Code Online (Sandbox Code Playgroud)\n这看起来应该很熟悉:您知道算术运算遵循“结合性”或“交换性”等规则。我在这里想表达的是,同样的规则也适用。
\nreduce可能会去掉周围的结构,但在转换时值仍然以代数结构绑定在一起。
代数结构超出了本答案的范围,因此我只会讨论它们的相关性。
\n((a \xe2\x9d\x8b b) \xe2\x9d\x8b c) \xe2\x9d\x8b d\nRun Code Online (Sandbox Code Playgroud)\n看看上面的表达式,不言而喻的是,有一个约束将所有值联系在一起:\xe2\x9d\x8b必须知道如何以相同的方式组合它们+必须知道如何组合1 + 2并且同样重要(1 + 2) + 3。
确保这一点的一种方法是强制这些值属于同一集合,在该集合上,减速器是“内部”或“封闭”二元运算,也就是说:将该集合中的任意两个值与减速器相结合会产生一个值属于同一集合。
\n在抽象代数中,这被称为岩浆。您还可以查找更多讨论的半群,并且与结合性相同(不需要大括号),尽管reduce并不关心。
生活在岩浆中并不是绝对必要的:我们可以想象一种情况,其中\xe2\x9d\x8b可以结合a和b但不能c结合 和b。
函数组合就是一个例子。以下函数之一返回一个字符串,该字符串限制了组合它们的顺序:
\nconst a = x => x * 2;\nconst b = x => x ** 2;\nconst c = x => x + \' !\';\n\n// (a \xe2\x88\x98 b) \xe2\x88\x98 c\nconst abc = x => c(b(a(x)));\nabc(5); // "100 !"\n\n// (a \xe2\x88\x98 c) \xe2\x88\x98 b\nconst acb = x => b(c(a(x)));\nacb(5); // NaN\nRun Code Online (Sandbox Code Playgroud)\n与许多二元运算一样,函数组合可以用作化简器。
\n了解我们是否处于重新排序或从数组中删除元素可能会造成reduce破坏的情况是很有价值的。
所以,岩浆:不是绝对必要的,但非常重要。
\n假设我们想通过引入一个初始值来防止数组为空时引发异常:
\narray.reduce(f, init)\n\n// which is really the same as doing\n[init, ...array].reduce(f)\n\n// or\n((init \xe2\x9d\x8b a) \xe2\x9d\x8b b) \xe2\x9d\x8b c...\nRun Code Online (Sandbox Code Playgroud)\n我们现在有了额外的价值。没问题。
\n“没问题”!?我们说reducer的目的是组合数组值,但init不是真正的值:它是我们自己强制引入的,它不应该影响 的结果reduce。
问题是:
\n\n\n\n
init我们应该选择什么f(init, a)才能init \xe2\x9d\x8b a返回a?
我们想要一个初始值,就像它不存在一样。我们想要一个中立的元素(或“身份”)。
\n您可以查找单位岩浆或幺半群(与结合性相同),它们是配备中性元素的岩浆的脏话。
\n你已经知道了一堆中性元素
\nnumbers.reduce((a, b) => a + b, 0)\n\nnumbers.reduce((a, b) => a * b, 1)\n\nbooleans.reduce((a, b) => a && b, true)\n\nstrings.reduce((a, b) => a.concat(b), "")\n\narrays.reduce((a, b) => a.concat(b), [])\n\nvec2s.reduce(([u,v], [x,y]) => [u+x,v+y], [0,0])\n\nmat2s.reduce(dot, [[1,0],[0,1]])\nRun Code Online (Sandbox Code Playgroud)\n您可以对多种抽象重复此模式。请注意,中性元素和计算不需要这么简单(极端的例子)。
\n\n\n我们必须接受这样一个事实,即某些减少仅适用于非空数组,并且添加不良初始化程序并不能解决问题。
\n
一些减少错误的例子:
\n仅部分中立\nnumbers.reduce((a, b) => b - a, 0)\n\n// does not work\nnumbers.reduce((a, b) => a - b, 0)\nRun Code Online (Sandbox Code Playgroud)\n减去0formb返回b,但减去bfrom0返回-b。\n我们说只有“右恒等式”才是正确的。
并非每个非交换运算都缺乏对称中性元素,但这是一个好兆头。
\n超出范围\nconst min = (a, b) => a < b ? a : b;\n\n// Do you really want to return Infinity?\nnumbers.reduce(min, Infinity)\nRun Code Online (Sandbox Code Playgroud)\nInfinity是唯一一个不会改变reduce非空数组输出的初始值,但我们不太可能希望它实际出现在我们的程序中。
中性元素并不是我们为了方便而添加的小丑值。它必须是一个允许的值,否则它不会完成任何事情。
\n无意义\n下面的减少依赖于位置,但是添加初始化程序自然会将第一个元素移动到第二个位置,这需要弄乱减速器中的索引来维持行为。
\nconst first = (a, b, i) => !i ? b : a;\nthings.reduce(first, null);\nRun Code Online (Sandbox Code Playgroud)\nconst camelCase = (a, b, i) => a + (\n !i ? b : b[0].toUpperCase() + b.slice(1)\n);\nwords.reduce(camelCase, \'\');\nRun Code Online (Sandbox Code Playgroud)\n接受数组不能为空的事实并简化化简器的定义会更清晰。
\n此外,初始值是退化的:
\nnull不是空数组的第一个元素。
空字符串绝不是有效的标识符。
\n没有办法用初始值来保存“第一性”的概念。
\n代数结构可以帮助我们以更系统的方式思考我们的程序。知道我们正在处理的是哪一个可以准确预测我们可以期待什么reduce,所以我只能建议你查一下它们。
我们已经看到map和reduce在结构上如此不同,但这并不是说它们是两个孤立的事物。
我们可以map用 来表达reduce,因为总是可以重建我们开始时使用的相同结构。
const map = f => (acc, x) => \n acc.concat(f(x))\n;\n\nconst double = x => x * 2;\n[1, 2, 3].reduce(map(double), []) // [2, 4, 6]\nRun Code Online (Sandbox Code Playgroud)\n进一步推动它已经产生了诸如传感器之类的巧妙技巧。
\n我不会详细介绍它们,但我希望您注意一些与我们之前所说的相呼应的事情。
\n首先让我们看看我们要解决什么问题
\n[1, 2, 3, 4].filter(x => x % 2 == 0)\n .map(x => x ** 2)\n .reduce((a, b) => a + b)\n// 20\nRun Code Online (Sandbox Code Playgroud)\n我们迭代 3 次并创建 2 个中间数据结构。这段代码是声明性的,但效率不高。传感器试图协调两者。
\n首先是使用 来编写函数的一些实用工具reduce,因为我们不会使用方法链:
const composition = (f, g) => x => f(g(x));\nconst identity = x => x;\n\nconst compose = (...functions) => \n functions.reduce(composition, identity)\n;\n\n// compose(a, b, c) is the same as x => a(b(c(x)))\nRun Code Online (Sandbox Code Playgroud)\n现在注意下面的map执行filter。我们传入这个reducer函数而不是直接连接。
const map = f => reducer => (acc, x) => \n reducer(acc, f(x))\n;\n\nconst filter = f => reducer => (acc, x) => \n f(x) ? reducer(acc, x) : acc\n;\nRun Code Online (Sandbox Code Playgroud)\n更具体地看一下:
\n reducer => (acc, x) => [...]
\n应用回调函数后f,我们剩下一个函数,该函数接受一个reducer作为输入并返回一个reducer。
这些对称函数是我们传递给的compose:
const pipeline = compose(\n filter(x => x % 2 == 0), \n map(x => x ** 2)\n);\nRun Code Online (Sandbox Code Playgroud)\n记住compose是用 实现的reduce:我们composition之前定义的函数结合了我们的对称函数。
此操作的输出是相同形状的函数:需要一个减速器并返回一个减速器,这意味着
\nreduce如果你需要说服力,我可以让你扩展整个事情。如果这样做,您会注意到变换将方便地从左到右应用,这与 的方向相反compose。
好吧,让我们使用这个奇怪的东西:
\nconst add = (a, b) => a + b;\n\nconst reducer = pipeline(add);\n\nconst identity = 0;\n\n[1, 2, 3, 4].reduce(reducer, identity); // 20\nRun Code Online (Sandbox Code Playgroud)\nmap我们将、filter和等多种操作组合reduce成一个单一的reduce,仅迭代一次,没有中间数据结构。
这是一个不小的成就!map而且这并不是一个仅仅reduce根据语法的简洁性就可以想出的方案。
另请注意,我们可以完全控制初始值和最终的减速器。我们使用了0and add,但我们可以使用[]and concat(更实际的push性能方面)或任何其他可以实现类似连接操作的数据结构。
tad*_*man 10
通常,“映射”是指将一系列输入转换为等长的一系列输出,而“减少”是指将一系列输入转换为更少量的输出。
人们所说的“ map-reduce”通常被理解为“变换,可能并行,串行组合”。
当“地图”,你写一个函数变换x与f(x)进入一些新的价值x1。当您“减少”时,您正在编写一些g(y)接受array y并发出array的函数y1。他们处理不同类型的数据并产生不同的结果。
该map()函数通过在输入数组中的每个元素上传递一个函数来返回一个新数组。
这与以reduce()相同方式接受数组和函数的方式不同,但该函数接受2输入 - 累加器和当前值。
所以reduce()可以像map()如果你总是.concat在累加器上使用函数的下一个输出。然而,它更常用于减少数组的维数,因此要么取一维并返回单个值,要么展平二维数组等。
让我们一一看看这两个。
地图
Map 接受一个回调并对数组中的每个元素运行它,但它的独特之处在于它根据您现有的数组生成一个新数组。
var arr = [1, 2, 3];
var mapped = arr.map(function(elem) {
return elem * 10;
})
console.log(mapped); // it genrate new arrayRun Code Online (Sandbox Code Playgroud)
降低
数组对象的 Reduce 方法用于将数组缩减为一个值。
var arr = [1, 2, 3];
var sum = arr.reduce(function(sum, elem){
return sum + elem;
})
console.log(sum) // reduce the array to one single valueRun Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
13511 次 |
| 最近记录: |