##缘起 定制edx的过程中,需要自己去筛选处理不少数据,大多是直接从mongo中挖出。 大部分的工作都可以抽象描述成按条件筛选出一个数据集的子集,数据集包括courses(课程对象集合),commentThread(论坛帖子),comment(帖子评论),courseInfo(课程公告),这些数据的结构都极其相似,都是类列表数据(跟操作list基本一样), 恰好前段时间一直在关注函数式,也在学习Scheme,所以自然想到用函数式风格处理list是最方便的了。

一试,果不其然。

高阶函数提高了程序的模块性。比如使用一个高阶函数来实现排序,可以使得我们使用不同的条件(函数)来排序,这就将排序条件和排序过程清楚地划分开来。很容易写函数来实现插拔。

具体使用中,比如筛选过程使用高阶函数(def course_filter)来写,之后的筛选条件都写成filter_by_xx形式:filter_by_name/filter_by_subject,清晰明了,提高代码复用性,也导致了良好的一致性,进而使可读性提高。

使用函数风格来写函数,具体的 理念/原则 可以通过搜索函数式编程找到,其中一些比较关键的地方:

  • 函数是"一等公民",使用高阶函数
  • 尽量不要产生"副作用"
  • 函数只是返回新的值,不修改变量.
  • 引用透明(确定性),任何时候只要参数相同,引用函数所得到的返回值总是相同的。
  • 函数应该像管道
  • 把函数当成变量来用,关注于描述问题而不是怎么实现,这样可以让代码更易读

##函数式编程的几个技术 其实在python中以下几个东西基本也可以用for(列表推导)来做,不过列表推导不是模块化的,倾向于一次性使用、

###map

1
2
3
4
#列表长度
name_len = map(len, ["hao", "chen", "coolshell"])
print name_len
# 输出 [3, 4, 9]

这样的代码是在描述要干什么,而不是怎么干

1
2
3
4
5
6
#首字母大写
def toUpper(item):
      return item.upper()
 
upper_name = map(toUpper, ["hao", "chen", "coolshell"])
# 输出 ['HAO', 'CHEN', 'COOLSHELL']
1
2
3
4
#求一组数据的平方数
squares = map(lambda x: x * x, range(9))
print squares
# 输出 [0, 1, 4, 9, 16, 25, 36, 49, 64]

###reduce

1
2
3
#求和 相当于:((((1+2)+3)+4)+5) )
print reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])
# 输出 15
1
2
3
4
5
6
#数组中正数的平均值
num =[2, -5, 9, 7, -2, 5, 3, 1, 0, -3, 8]
positive_num = filter(lambda x: x>0, num)
average = reduce(lambda x,y: x+y, positive_num) / len( positive_num )
#避免了变量倒来倒去逻辑
#代码变成了在描述你要干什么,而不是怎么去干。

###map函数的实现(js)

1
2
3
4
5
6
7
8
:::text
var map = function (mappingFunction, list) {
  var result = [];
  forEach(list, function (item) {
    result.push(mappingFunction(item));
  });
  return result;
};

###filter

1
2
3
nums=[1,2,3,4,5,6,7]
b=filter(lambda x:x>5, nums)
#[6,7]
1
2
3
4
5
6
7
#是否是成年人
def is_adult(age):
    if age>=18:
        return True
age_list=[15,16,17,18,19]
b=filter(age, age_list)
#[18,19]

可以作为高阶函数的参数,模块化,可插拔

##zip zip()是Python的一个内建函数,它接受一系列可迭代的对象作为参数,将对象中对应的元素打包成一个个tuple(元组),然后返回由这些tuples组成的list(列表)

若传入参数的长度不等,则返回list的长度和参数中长度最短的对象相同。利用*号操作符,可以将list unzip(解压)

1
2
3
4
5
6
7
8
9
a = [1,2,3]
b = [4,5,6]
c = [4,5,6,7,8]
zipped = zip(a,b)
#[(1, 4), (2, 5), (3, 6)]
zip(a,c)
#[(1, 4), (2, 5), (3, 6)]
zip(*zipped)
#[(1, 2, 3), (4, 5, 6)]

###sorted和groupby ####sorted sorted([…],key = fuc)

1
2
3
4
5
6
a=[1,3,2,6,-5,-4]
sorted(a, key=lambda a:abs(a), reverse = False )
#[1,2,3,4,5,6]

#reverse:注明升序还是降序,True--降序,False--升序(默认)
#fuc 经常使用lambda

####groupby

1
2
3
4
5
6
7
8
9
for i,j in groupby("abbcdddef", lambda x:x*2):
     print i,[x for x in j]
    
aa ['a']
bb ['b', 'b']
cc ['c']
dd ['d', 'd', 'd']
ee ['e']
ff ['f']

注意先排序(sorted),groupby(“abbca”)中前后两个a不会连在一起。

groupby(list,func)的返回值(i,j),i是func的计算结果;j是一个迭代器,j的内容是经过func计算值相同的lst中的元素;

本质是mapreduce 所以记不住全局,只有一个交换空间,所以必须相邻

###pipeline pipeline 管道借鉴于Unix Shell的管道操作——把若干个命令串起来(他的设哲学就是KISS – 让每个功能就做一件事,并把这件事做到极致),前面命令的输出成为后面命令的输入,如此完成一个流式计算。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#1)找出偶数。
#2)乘以3
#3)转成字符串返回


def even_filter(nums):
    return filter(lambda x: x%2==0, nums)
 
def multiply_by_three(nums):
    return map(lambda x: x*3, nums)
 
def convert_to_string(nums):
    return map(lambda x: 'The Number: %s' % x,  nums)
 
nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
fns = [even_filter,multiply_by_three,convert_to_string]
def pipeline_func(data, fns):
    return reduce(lambda a, x: x(a),
                  fns,
                  data)
#reduce有两个列表时怎么处理?

###高阶函数 python中,高阶函数常常写作装饰器(Decorator),可以参考之前的一篇文章

如陈浩在博客中说的:我们不用太纠结是不是我们的程序就是OO,就是functional的,我们重要的品味其中的味道

  • 具体的使用 map filter groupby sorted (信息隐藏,对外True/False)

##参考资料