其实早就想说说这个有意思的话题,直到最近看了spark,又再次陷入这个有意思的事情,决定上来说说这个。从哪里开始说起呢?就从我这几个月学js的心得说起吧。javascript,一门神奇的语言,印象最深的特点之一是,函数经常接受另一个函数作为参数传入,官方称这叫回调,当然了,又有同步回调,异步回调,弄的人懵懵的。这是它的特色,函数也是同string array啥的类似的对象。暂且不讨论不太容易理解的回调精髓,就说说我常用的几个用函数作为参数的方法吧。

1. map()方法

语法:

var new_array = arr.map(callback[, thisArg])

咦,发现了callback吧!那map()究竟能实现怎样的功能呢?场景是这样的:比如我有个数组,我想对数组中的每一个元素都进行同样一个操作,如对每一个数字做平方,传统的思维方式是写循环遍历整个数组,但是这个时候我们有map()了,这个循环就不用写了。

  • Example
// for loop
var arr = [1, 3, 5, 7, 9];
var new_array = new Array(arr.length);
for(var i = 0; i < arr.length; i++) {
	new_array[i] = arr[i] * arr[i];
}
// map()
var arr = [1, 3, 5, 7, 9];
var new_array = arr.map(function(ele) {
	return ele * ele;
})

打开node试试吧!看是不是很有趣!

2017-03-26 10 43 57

2. filter()方法

只能说,紧随map()之后的必须是filter()大法啊!

语法:

var new_array = arr.filter(callback[, thisArg])

同样地,它回调的是什么样的函数呢?字面意思过滤,所以它会返回一个满足这个回调函数条件的新数组。本来也是要用循环遍历所有元素找出来满足条件的,这个时候有了filter()也就不用了。

  • Example
var arr = [1, 2, 1, 3, 4, 1, 5, 1];
var new_array = arr.filter(function(ele) {
	return ele !== 1;
})

2017-03-26 10 58 48

3. every()方法

filter()类似,只不过every()返回的是一个布尔值,用来判断是否数组中所有元素都满足回调函数的条件。

语法:

arr.every(callback[, thisArg])
  • Example
var arr = [1, 2, 3, 4, 5, 6, 7];
arr.every(x => x >=5);

2017-03-26 11 11 26

我check了一下是否数组内的所有元素都大于5,结果是false。这里还用到了非常有意思的箭头函数。简单来说,类似于匿名函数,因为它没有名字,并且非常适合map(), filter()等等这类方法。

4. reduce()方法

语法:

arr.reduce(callback, [initialValue])

reduce()接受的回调函数是用来把数组所有元素减少到一个值,听起来有点懵,看两个例子好了。

  • 栗子一
var arr = [1, 2, 3, 4];
var sum = arr.reduce(function(acc, val) {
	return acc * val;
})
// 24

它做了什么呢?它把所有的元素都乘起来了。

  • 栗子二
var str = ['a', 'b', 'c', 'd', 'e'];
var new_str = str.reduce(function(acc, val) {
	return acc.concat(val);
})
// 'abcde'

一个连接字符串的小例子。

  • 栗子三
var arr = [[1, 2], [3, 4, 5], [6, 7, 8, 9]];
var new_arr = arr.reduce(function(acc, val) {
	acc.concat(val);
})
// [1, 2, 3, 4, 5, 6, 7, 8, 9]

这个是传说中的拍扁你!之后R里面你也会见到。

类似的函数还有多少呢?我没数过,的确特别特别多,这是函数式编程思想,很有趣。链接收好不谢。

underscore.js lodash lazy.js

话说回来,你以为我是学js知道的上面这仨链接的么?还真不是。作为一个资历尚浅但是由衷热爱R的代码狗,怎么可能没听说过purrr这个包呢!初识purrr,就觉得这不是把js里的函数式编程思想copy进来了嘛!R本来就是函数式编程的,但这个包似乎让这个思想更加深刻了~

purrr包经典函数:map()家族

# .x is list or vector, .f is function or formular
map(.x, .f, ...)

同js里面的map()方法,这里的map()同样是对向量的每一个元素做同样一个操作,如何定义这个操作呢?可以是函数,也可以是公式。两个小例子:

  • 栗子一
1:10 %>%
	map(function(x) x ^ 2)
  • 栗子二
1:10 %>%
	map(~ .x + 1)

返回的是和向量等长的list,很烦人是不是?来跟我拍扁它!这里拍扁可以用map_dbl()(双精度)这个家伙。家族有:map_lgl()map_chr()map_int()分别用来拍扁不同的数据类型。

1:10 %>%
	map_dbl(function(x) x ^ 2)

另一个拍扁方法是flatten()家族。其中flatten()一定返回list,家族有:flatten_lgl() flatten_dbl() flatten_chr() flatten_int() flatten_df()

为了体会其功效,专门写一个复杂的list套list的情况:

2017-04-01 8 27 37

flatten()一次只能拍扁一下,啥意思呢?就是它只能每次把最外层list拍扁一下,拍完了还是list

list1 <- list(list(c(1, 2, 3), c(2, 3, 4)), c(3, 4, 5))
flatten(list1)

2017-04-01 8 30 45

咦,是不是list1[2]给拍扁了,拍扁之后的长度也从2变成了5,但可能这并不是你想要的。那我们还得再拍扁一下。

flatten(list1) %>%
	flatten()

2017-04-01 9 46 14

这次终于彻底拍扁了,flatten()的使命彻底结束。但这依旧不是你想要的吧!我知道你想要向量。

flatten(list1) %>%
	flatten() %>%
		flatten_dbl()

终极目标达成!不知道你看懂没有。前提是你要对R里面的数据类型及数据结构有很好的理解,如果你说不清楚list1[1]list1[[1]]的区别,就得去搞搞清楚了。

同样,我认为更简单的做法就是unlist(),因为它有个recursive的选项,你可以选择递归下去,也可以做一次就结束。unlist(list1)直接一步到位。

reduce()家族

成员:reduce() reduce_right() reduce2() reduce2_right()

药效一致:accumulate() accumulate_right()

同js里面的reduce()方法,它会把list或者vector以某种方法浓缩成一个,这个方法就是你定义的函数。

a <- 1:7 %>%
	reduce(`-`)
# -26

reduce_right()是从右向左来递归。accumulate()会把中间结果给出来。

every()

同js里的every()方法,给出的结果也是True or False。比如:

a <- 1:10
a %>%
	every(function(x) {
		x < 11
	})
# TRUE

剩下的我就不多说了,类似这样在js和R里成对的函数还有哪些呢?

javascript R
map() map()家族
reduce() reduceRight() reduce()家族
every() every()
some() some()
indexOf() lastIndexOf() detect_index()
find() detect()
invoke() invoke()家族
flatten() flatten()家族
negate() negate()
... ...

不同语言之间的相互借鉴非常之有趣吧~

但我开篇提到了spark,其实是闲来无聊看了看spark发现了几个对RDD的转化操作才决定把这篇写掉的。不过在spark中用到类似函数一点都不奇怪啦~毕竟人家生来就是折腾大数据的。RDD是什么我就不过多解释,就把它想成一个大大的数据集。对于分布式计算呢,它会被分成多个分区,然后每个分区单独操作。在转化操作中,map() flatMap() filter() reduce() foreach() fold()比较常用。以python和scala中为例(java太残暴),你或许能看懂什么了。这说明,python和scala里面都有类似函数。以及python里面的lambda,js里面的=>,Scala里的=>非常适合配上这类函数一起用。

nums = sc.parallelize([1, 2, 3, 4])
squared = nums.map(lambda x: x * x).collect()
val input = sc.parallelize(List(1, 2, 3, 4))
val result = input.map(x => x * x)

大部分时候我都喜欢采用这种省时省力的方式,把很多东西联想到一起,一次性搞定不同语言不同框架中类似的想法,然后自己再感慨感慨世间万物互联互通,有相似的内在哲理,不同的计算机语言当然也是这样,最重要的是要搞清楚什么场景下用什么语言和工具更合适,学习新东西的成本倒不高~