// 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))
const a = x => x * 2;
const b = x => x ** 2;
const c = x => x + ' !';
// (a ∘ b) ∘ c
const abc = x => c(b(a(x)));
abc(5); // "100 !"
// (a ∘ c) ∘ b
const acb = x => b(c(a(x)));
acb(5); // NaN
numbers.reduce((a, b) => a + b, 0)
numbers.reduce((a, b) => a * b, 1)
booleans.reduce((a, b) => a && b, true)
strings.reduce((a, b) => a.concat(b), "")
arrays.reduce((a, b) => a.concat(b), [])
vec2s.reduce(([u,v], [x,y]) => [u+x,v+y], [0,0])
mat2s.reduce(dot, [[1,0],[0,1]])
const composition = (f, g) => x => f(g(x));
const identity = x => x;
const compose = (...functions) =>
functions.reduce(composition, identity)
;
// compose(a, b, c) is the same as x => a(b(c(x)))
9条答案
按热度按时间5cnsuln71#
Source
map
和reduce
的输入都是您定义的数组和函数。它们在某种程度上是互补的:map
不能为多个元素的数组返回单个元素,而reduce
将始终返回最终更改的累加器。一米四分一秒
使用
map
迭代元素,并为每个元素返回所需的元素。例如,如果您有一个数字数组,并希望获得它们的平方,您可以执行以下操作:
∮ ∮ ∮ ∮
reduce
使用数组作为输入,您可以根据回调函数(第一个参数)获取一个元素(比如Object、Number或另一个Array),回调函数获取
accumulator
和current_element
参数:当你可以同时使用两个函数时,你应该选择哪一个呢?试着想象一下代码的样子。对于提供的例子,你可以使用
reduce
来计算你提到的squares数组:现在,看看这些,显然第二个实现看起来更好,也更短。通常你会选择更简洁的解决方案,在这个例子中是
map
。当然,你可以用reduce
来做,但简单地说,想想哪个会更短,最终会更好。oyjwcjzk2#
我想这张图可以回答你关于高阶函数之间的区别
628mspwn3#
通常,“Map”意味着将一系列输入转换成一系列 * 等长 * 的输出,而“减少”意味着将一系列输入转换成一系列 * 更少 * 的输出。
人们所指的“Map缩减”通常被解释为“转换,可能是并行的,串行的合并”。
当你“map”的时候,你写的是一个函数,它把
x
和f(x)
转换成一个新的值x1
;当你“reduce”的时候,你写的是一个函数g(y)
,它接受数组y
并发出数组y1
。它们在数据结构方面产生不同的结果。
l7mqbcuq4#
map()
函数通过对输入数组中的每个元素传递函数来返回新数组。这与
reduce()
不同,reduce()
以相同的方式接受数组和函数,但函数接受2
输入-累加器和当前值。所以
reduce()
可以像map()
一样使用,如果你总是把.concat
放到函数的下一个输出的累加器上,但是它更常用于减少数组的维数,比如取一个一维数组并返回一个值,或者扁平化一个二维数组等等。xqk2d5yq5#
下面我们就来逐一了解一下这两位。
Map
Map接受一个回调函数,并对数组中的每个元素运行它,但它的独特之处在于它***基于现有数组生成一个新数组***。
减少
数组对象的Reduce方法用于***将数组缩减为一个值***。
nwo49xxi6#
我认为这是一个非常好的问题,我不能不同意答案,但我有一种感觉,我们完全没有抓住要点。
更抽象地思考
map
和reduce
可以为我们提供很多非常好的见解。本答案分为3部分:
Map或减少
共同特征
map
和reduce
以有意义和一致的方式在不一定是集合的广泛对象上实现。它们返回一个对周围的算法有用的值,并且它们只关心这个值。
它们的主要作用是传达关于结构的转换或保存的意图。
结构
“结构”指的是一组概念属性,这些属性描述了抽象对象的特征,如无序列表或二维矩阵,以及它们在数据结构中的具体化。
请注意,两者之间可能存在断开:
Map
map
是严格的结构保持变换。在其他类型的对象上实现它以掌握其语义值是有用的:
实现
map
的对象可以有各种各样的行为,但是它们总是返回与您在使用提供的回调函数转换值时开始时相同类型的对象。Array.map
返回一个长度和顺序与原始值相同的数组。在回调arity上
因为
map
保留了结构,所以它被视为安全的操作,但不是每个回调都是一样的。使用一元回调:
map(x => f(x))
,则数组的每个值与其他值的存在完全无关。另一方面,使用其他两个参数会引入耦合,这可能不符合原始结构。
假设删除或重新排序以下数组中的第二项:在Map之前或之后执行该操作将不会产生相同的结果。
与阵列大小耦合:
与订购耦合:
与一个特定值耦合:
与邻居耦合:
我建议在调用点明确说明是否使用这些参数。
在
map
中使用变元函数时,这还有其他好处。减少
reduce
设置一个不受其周围结构影响的值。同样,让我们在一个更简单的对象上实现它:
不管你是保留这个值,还是把它放回其他值中,
reduce
的输出可以是任何形状,它实际上与map
相反。对阵列的影响
数组可以包含多个值或零个值,这会产生两个有时相互冲突的要求。
需要合并
我们如何返回多个没有结构的值?
这是不可能的。为了只返回一个值,我们有两种选择:
现在不是更有意义了吗?
需要初始化
如果没有要返回的值怎么办?
如果
reduce
返回了一个伪值,就无法知道源数组是空的还是包含了这个伪值,所以除非我们提供一个初始值,否则reduce
必须抛出。减速器的真正用途
在下面的代码片段中,您应该能够猜到reducer
f
的作用:没有.它没有被调用.
这是一个微不足道的例子:
a
是我们要返回的单个值,因此不需要f
。顺便说一下,这就是为什么我们之前没有在
A
类中强制使用reducer的原因:因为它只包含一个值。它在数组中是强制的,因为数组可以包含多个值。因为只有当你有两个或更多的值时才会调用reducer,所以说它的唯一目的是合并它们只是一个小问题。
关于转换值
在可变长度的数组上,期望reducer转换值是危险的,因为正如我们发现的,它可能不会被调用。
当您需要转换值和改变形状时,我建议您在使用
reduce
之前使用map
。无论如何,为了可读性,将这两个关注点分开是一个好主意。
何时不使用减少
因为
reduce
是实现结构转换的通用工具,所以我建议您在需要返回数组时避免使用它,如果存在另一个更集中的方法来完成您想要的工作。具体来说,如果您在
map
中处理嵌套数组时遇到困难,请在考虑reduce
之前先考虑flatMap
或flat
。在reduce的核心
递归二元运算
在数组上实现
reduce
引入了这个反馈循环,其中reducer的第一个参数是前一次迭代的返回值。不用说,它看起来一点也不像
map
的回调。我们可以递归地实现
Array.reduce
,如下所示:这突出了reducer
f
的二进制特性,以及它的返回值如何在下一次迭代中变为新的acc
。我让你说服自己以下是真的:
这看起来很熟悉:你知道算术运算遵循诸如"结合性"或"交换性"之类的规则。2我想在这里传达的是同样的规则也适用。
reduce
可以剥离周围的结构,但是对于变换时,值仍然在代数结构中绑定在一起。约化器的代数
代数结构超出了这个答案的范围,所以我将只涉及它们是如何相关的。
查看上面的表达式,不言而喻,存在将所有值联系在一起的约束:
❋
必须知道如何组合它们,就像+
必须知道如何组合1 + 2
一样,同样重要的是(1 + 2) + 3
。最弱的安全结构
确保这一点的一种方法是强制这些值属于同一个集合,在该集合上reducer是"内部"或"闭合"二元运算,也就是说:用缩减器组合来自该组的任何两个值产生属于同一组的值。
在抽象代数中,这被称为magma,你也可以查找半群,它被更多地讨论,与结合性是一样的(不需要花括号),尽管
reduce
不关心。不太安全
生活在岩浆中并非绝对必要:我们可以想象
❋
可以组合a
和b
但不能组合c
和b
的情况。函数组合就是这样一个例子。以下函数之一返回一个字符串,该字符串限制了组合它们的顺序:
像许多二元运算一样,函数合成可以用作约简器。
了解我们是否处于重新排序或从数组中删除元素可能使
reduce
中断的情况是很有价值的。所以,岩浆:不是绝对必要的,但是非常重要。
初始值如何
假设我们想通过引入一个初始值来防止在数组为空时抛出异常:
我们现在有了一个附加值。没问题。
"没问题"!?我们说过reducer的目的是合并数组值,但
init
不是一个 * true * 值:这是我们自己强行引入的,应该不会影响reduce
的结果。问题是:
我们应该选择什么样的
init
才能使f(init, a)
或init ❋ a
返回a
?我们需要一个初始值,它的行为就像它不存在一样,我们需要一个中性元素(或"恒等式")。
你可以查unital magmas或么半群(与结合律相同),它们是对带有中性元素的岩浆的咒骂。
一些中性元素
你已经知道很多中性元素
你可以对很多种抽象重复这个模式,注意中性元素和计算不需要这么琐碎(extreme example)。
中性元素的艰辛
我们不得不接受这样的事实,即一些约简只可能用于非空数组,并且添加不好的初始化器并不能解决问题。
减少错误的一些例子:
从
b
减去0
得到b
,而从0
减去b
得到-b
,我们说只有"右恒等式"是真的。并非所有非交换运算都缺少对称中性元素,但这是个好兆头。
对于非空数组,
Infinity
是唯一不改变reduce
输出的初始值,但我们不太可能希望它实际出现在我们的程序中。中性元素不是我们为了方便而添加的Joker值,它必须是一个允许的值,否则它什么也做不了。
下面的归约依赖于位置,但是添加初始化器自然会将第一个元素移到第二个位置,这需要在归约器中修改索引以保持该行为。
一个17块一个18块一个
如果接受数组不能为空的事实并简化归约器的定义,那就更简洁了。
此外,初始值是退化的:
null
不是空数组的第一个元素。没有办法用初始值来保持"第一性"的概念。
结论
代数结构可以帮助我们以更系统的方式思考我们的程序,知道我们正在处理的是哪一个可以准确地预测我们可以从
reduce
中期待什么,所以我只能建议你去查一查。∮再向前一步∮
我们已经看到
map
和reduce
在结构上是如何如此不同,但这并不是说它们是两个孤立的事物。我们可以用
reduce
来表示map
,因为总是可以重新构建我们开始时使用的相同结构。再往前推一点,就出现了诸如传感器之类的巧妙技术。
我不想详细谈论这些问题,但我希望你们注意到一些与我们以前所说的相呼应的事情。
探头
首先让我们看看我们要解决的是什么问题
我们迭代了3次,创建了2个中间数据结构。这段代码是声明性的,但效率不高。转换器尝试协调这两个数据结构。
首先介绍一下使用
reduce
组合函数的实用程序,因为我们不打算使用方法链接:现在注意下面的
map
和filter
的实现。我们传入这个reducer
函数,而不是直接连接。更具体地看这个问题:
reducer => (acc, x) => [...]
在应用回调函数
f
之后,剩下的函数将reducer作为输入并返回reducer。这些对称函数就是我们传递给
compose
的函数:请记住,
compose
是使用reduce
实现的:前面定义的composition
函数组合了对称函数。此操作的输出是相同形状的函数:它需要一个reducer并返回一个reducer,这意味着
reduce
的reducer如果你需要说服力的话,我让你扩展整个事情,如果你这样做,你会注意到变换将方便地从左到右应用,这是
compose
的相反方向。好吧,让我们用这个怪人:
我们已经将
map
、filter
和reduce
等不同的操作组合到一个reduce
中,只迭代一次,没有中间数据结构。这是一个不小的成就!而且这不是一个你仅仅根据语法的简洁性就可以在
map
和reduce
之间做出决定的方案。还需要注意的是,我们可以完全控制初始值和最终的reducer,我们使用了
0
和add
,但是我们也可以使用[]
和concat
(更现实的是push
的性能)或者其他任何可以实现类似concat操作的数据结构。c7rzv4ha7#
要了解map、filter和reduce之间的区别,请记住:
1.这三种方法都适用于数组所以无论何时你想对一个数组进行任何操作,你都要使用这些方法。
1.这三种方法都遵循函数方法,因此原始数组保持不变。原始数组不变,而是返回一个新的数组/值。
Map
返回一个新数组,其元素数与原始数组中的元素数相等。因此,如果原始数组有5个元素,则返回的数组也将有5个元素。每当我们想对数组中的每个元素进行更改时,都会使用此方法。您可以记住,ann数组中的每个元素都被Map到输出数组中的某个新值。因此名称map
例如,Filter
返回一个新数组,其元素数等于或少于原始数组。它返回数组中通过某些条件的元素。当我们要对原始数组应用过滤器时,使用此方法,因此名称为filter
。例如,Reduce
返回单个值,与map/filter不同。因此,每当我们想对数组的所有元素运行操作,但希望使用所有元素得到单个输出时,我们使用reduce
。您可以记住,数组的输出减少为单个值,因此命名为reduce
。例如,1rhkuytd8#
map函数在每个元素上执行一个给定的函数,而reduce函数则将数组中的元素简化为一个值,下面我将给出两个函数的例子:
a5g8bdjr9#
reduce
确实可以将数组简化为单个值,但是由于我们可以将对象作为initialValue
传递,因此我们可以在此基础上进行构建,并最终得到一个比开始时更复杂的对象。比如这个例子,我们根据一些标准对项目进行分组。因此,术语“reduce”对于reduce
的能力可能会有轻微的误导,并且认为它必然减少信息可能是错误的,因为它也可能增加信息。