Reduce 和 Transduce 的含义
一、reduce 的用法
reduce是一种数组运算,通常用于将数组的所有成员"累积"为一个值。
var arr = [1, 2, 3, 4]; var sum = (a, b) => a + b; arr.reduce(sum, 0) // 10
上面代码中,reduce对数组arr的每个成员执行sum函数。sum的参数a是累积变量,参数b是当前的数组成员。每次执行时,b会加到a,最后输出a。
累积变量必须有一个初始值,上例是reduce函数的第二个参数0。如果省略该参数,那么初始值默认是数组的第一个成员。
var arr = [1, 2, 3, 4]; var sum = function (a, b) {
console.log(a, b);
return a + b;
}; arr.reduce(sum) // => 10
// 1 2
// 3 3
// 6 4
上面代码中,reduce方法省略了初始值。通过sum函数里面的打印语句,可以看到累积变量每一次的变化。
总之,reduce方法提供了一种遍历手段,对数组所有成员进行"累积"处理。
二、map 是 reduce 的特例
累积变量的初始值也可以是一个数组。
var arr = [1, 2, 3, 4]; var handler = function (newArr, x) {
newArr.push(x + 1);
return newArr;
}; arr.reduce(handler, [])
// [2, 3, 4, 5]
上面代码中,累积变量的初始值是一个空数组,结果reduce就返回了一个新数组,等同于执行map方法,对原数组进行一次"变形"。下面是使用map改写上面的例子。
var arr = [1, 2, 3, 4];
var plusOne = x => x + 1;
arr.map(plusOne) // [2, 3, 4, 5]
事实上,所有的map方法都可以基于reduce实现。
function map(f, arr) {
return arr.reduce(function(result, x) {
result.push(f(x));
return result;
}, []);
}
因此,map只是reduce的一种特例。
三、reduce的本质
本质上,reduce是三种运算的合成。
- 遍历
- 变形
- 累积
还是来看上面的例子。
var arr = [1, 2, 3, 4];
var handler = function (newArr, x) {
newArr.push(x + 1);
return newArr;
}; arr.reduce(handler, [])
// [2, 3, 4, 5]
上面代码中,首先,reduce遍历了原数组,这是它能够取代map方法的根本原因;其次,reduce对原数组的每个成员进行了"变形"(上例是加1);最后,才是把它们累积起来(上例是push方法)。
四、 transduce 的含义
reduce包含了三种运算,因此非常有用。但也带来了一个问题:代码的复用性不高。在reduce里面,变形和累积是耦合的,不太容易拆分。
每次使用reduce,开发者往往都要从头写代码,重复实现很多基本功能,很难复用别人的代码。
var handler = function (newArr, x) {
newArr.push(x + 1);
return newArr;
};
上面的这个处理函数,就很难用在其他场合。
有没有解决方法呢?回答是有的,就是把"变形"和"累积"这两种运算分开。如果reduce允许变形运算和累积运算分开,那么代码的复用性就会大大增加。这就是transduce方法的由来。
transduce这个名字来自 transform(变形)和 reduce 这两个单词的合成。它其实就是reduce方法的一种不那么耦合的写法。
// 变形运算
var plusOne = x => x + 1; // 累积运算
var append = function (newArr, x) {
newArr.push(x);
return newArr;
}; R.transduce(R.map(plusOne), append, [], arr);
// [2, 3, 4, 5]
上面代码中,plusOne是变形操作,append是累积操作。我使用了 Ramda 函数库的transduce实现。可以看到,transduce就是将变形和累积从reduce拆分出来,其他并无不同。
五、transduce 的用法
transduce最大的好处,就是代码复用更容易。
var arr = [1, 2, 3, 4];
var append = function (newArr, x) {
newArr.push(x);
return newArr;
}; // 示例一
var plusOne = x => x + 1;
var square = x => x * x; R.transduce(
R.map(R.pipe(plusOne, square)),
append,
[],
arr
); // [4, 9, 16, 25] // 示例二
var isOdd = x => x % 2 === 1; R.transduce(
R.pipe(R.filter(isOdd), R.map(square)),
append,
[],
arr
); // [1, 9]
上面代码中,示例一是两个变形操作的合成,示例二是过滤操作与变形操作的合成。这两个例子都使用了 Pointfree 风格。
可以看到,transduce非常有利于代码的复用,可以将一系列简单的、可复用的函数合成为复杂操作。作为练习,有兴趣的读者可以试试,使用reduce方法完成上面两个示例。你会发现,代码的复杂度和行数大大增加。
六、Transformer 对象
transduce函数的第一个参数是一个对象,称为 Transformer 对象(变形器)。前面例子中,R.map(plusOne)返回的就是一个 Transformer 对象。
事实上,任何一个对象只要遵守 Transformer 协议,就是 Transformer 对象。
var Map = function(f, xf) {
return {
"@@transducer/init": function() {
return xf["@@transducer/init"]();
},
"@@transducer/result": function(result) {
return xf["@@transducer/result"](result);
},
"@@transducer/step": function(result, input) {
return xf["@@transducer/step"](result, f(input));
}
};
};
上面代码中,Map函数返回的就是一个 Transformer 对象。它必须具有以下三个属性。
- @@transducer/step:执行变形操作
- @@transducer/init:返回初始值
- @@transducer/result:返回变形后的最终值
所有符合这个协议的对象,都可以与其他 Transformer 对象合成,充当transduce函数的第一个参数。
因此,transduce函数的参数类型如下。
transduce(
变形器 : Object,
累积器 : Function,
初始值 : Any,
原始数组 : Array
)
七、into 方法
最后,你也许发现了,前面所有示例使用的都是同一个累积器。
var append = function (newArr, x) {
newArr.push(x);
return newArr;
};
上面代码的append函数是一个常见累积器。因此, Ramda 函数库提供了into方法,将它内置了。也就是说,into方法相当于默认提供append的transduce函数。
R.transduce(R.map(R.add(1)), append, [], [1,2,3,4]);
// 等同于
R.into([], R.map(R.add(1)), [1,2,3,4]);
上面代码中,into方法的第一个参数是初始值,第二个参数是变形器,第三个参数是原始数组,不需要提供累积器。
下面是另外一个例子。
R.into(
[5, 6],
R.pipe(R.take(2), R.map(R.add(1))),
[1, 2, 3, 4]
) // [5, 6, 2, 3]
Reduce 和 Transduce 的含义的更多相关文章
- 阮一峰关于reduce 和transduce的博客
http://www.ruanyifeng.com/blog/2017/03/reduce_transduce.html
- 利用ABAP 740的新关键字REDUCE完成一个实际工作任务
ABAP 740从2013年发布至今已经过去很长的时间了,下面这张图来自SAP社区博客: ABAP News for Release 7.40 – What is ABAP 7.40? 图中的ABAP ...
- Python 函数进阶-高阶函数
高阶函数 什么是高阶函数 高阶函数就是能够把函数当成参数传递的函数就是高阶函数,换句话说如果一个函数的参数是函数,那么这个函数就是一个高阶函数. 高阶函数可以是你使用def关键字自定义的函数,也有Py ...
- MapReduce剖析笔记之七:Child子进程处理Map和Reduce任务的主要流程
在上一节我们分析了TaskTracker如何对JobTracker分配过来的任务进行初始化,并创建各类JVM启动所需的信息,最终创建JVM的整个过程,本节我们继续来看,JVM启动后,执行的是Child ...
- MapReduce剖析笔记之五:Map与Reduce任务分配过程
在上一节分析了TaskTracker和JobTracker之间通过周期的心跳消息获取任务分配结果的过程.中间留了一个问题,就是任务到底是怎么分配的.任务的分配自然是由JobTracker做出来的,具体 ...
- MySQL 各种超时参数的含义
MySQL 各种超时参数的含义 今日在查看锁超时的设置时,看到show variables like '%timeout%';语句输出结果中的十几种超时参数时突然想整理一下,不知道大家有没有想过,这么 ...
- 【转】Hive配置文件中配置项的含义详解(收藏版)
http://www.aboutyun.com/thread-7548-1-1.html 这里面列出了hive几乎所有的配置项,下面问题只是说出了几种配置项目的作用.更多内容,可以查看内容问题导读:1 ...
- MapReduce作业的map task和reduce task调度参数
MapReduce作业可以细分为map task和reduce task,而MRAppMaster又将map task和reduce task分为四种状态: 1.pending:刚启动但尚未向reso ...
- Android内存管理(5)*官方教程:Logcat内存日志各字段含义,查看当前内存快照,跟踪记录内存分配,用adb查看内存情况时各行列的含义,捕获内存快照的3种方法,如何让程序暴漏内存泄漏的方法
Investigating Your RAM Usage In this document Interpreting Log Messages 内存分析日志中各消息的含 ...
随机推荐
- 把图片上的文字转换成word文字?
转换后的文字不是很如意,但是免费方便. 1.打开Office办公软件自带的OneNote工具.随便新建一个笔记页面,以方便我们接下来的操作. 2.插入图片.在菜单栏里点击[插入],选择插入[图片],找 ...
- for 与 for in
在JavaScript中提供了两种方式迭代对象: (1)for 循环: (2)for..in循环: 使用for循环进行迭代数组对象,想必大家都已经司空见惯了.但是,使用for.. in循环时,大家可要 ...
- NumPy IO
NumPy IO Numpy 可以读写磁盘上的文本数据或二进制数据. NumPy 为 ndarray 对象引入了一个简单的文件格式:npy. npy 文件用于存储重建 ndarray 所需的数据.图形 ...
- ActiveMQ之整合spring
ActiveMQ可以轻松的与Spring进行整合,Spring提供了一系列的接口类,非常的好用! 比如异步消息数据.异步发送邮件.异步消息查询等 <dependency> <grou ...
- 微信小程序开发——模板中加载html代码
最新方法可以使用微信小程序提供的 rich-text (富文本)组件直接写解析html,详见 rich-text: <rich-text class='f13 c_9' nodes=" ...
- 【go语言实现服务器接收http请求以及出现泄漏时的解决方案】
一.关于基础的程序的实现 刚开始的时候程序是这样实现的: // Hello package main import ( "database/sql" "fmt" ...
- Wannafly挑战赛13 D.applese的生日(贪心+思维)
题目描述 最可爱的applese生日啦,他准备了许多个质量不同的蛋糕,想请一些同学来参加他的派对为他庆生,为了不让一部分同学感到不爽,他决定把每个蛋糕都分割成几份(也可以不分割),使得最小的蛋糕的质量 ...
- Shell特殊变量:$0 $# $* $@ $? $$和命令行参数
一.特殊变量表 变量 含义 $0 当前脚本的文件名 $n 传递给脚本或函数的参数.n 是一个数字,表示第几个参数.例如,第一个参数是$1,第二个参数是$2. $# 传递给脚本或函数的参数个数. $* ...
- sqlserver中对于特定数据字段定义特定的数据类型
char和varchar:汉字占两个字节,英文.数字或字符占一个 比如: 性别:男 女 可以定义为:char(2)或者是varchar(2) 因为性别是中文,中文占两个字节 nchar和nv ...
- 为ListView的子列表添加不同的响应事件
如何实现当点击不同的listView中的每个子列表出发不同的事件? 当在布局中,设置对应的ListView主布局及对应子布局时,在Activity中向ListView中添加子列表,利用每个子列表的Po ...