python学习笔记|函数式编程——map,filter,reduce

开始陆续学习python相关知识,这一篇介绍函数式编程相关知识。在文章中会介绍三个主要的高阶函数,从基本例子入手了解函数式编程。

map()

map 意思是映射,在数学的学习中,映射即为x与y存在的关系,即x通过一定的方法称为y。首先来看一个例子:将数组[1,2,3,4,5,6,7,8]中的元素均变为原来的2倍。

乍一看很简单,通过一个简单的循环便可以解决。那么我们首先用这个方法解决:

def num2(list):
    newList = []
    for x in list:
        x *= 2
        newList.append(x)
    return newList

执行方法:

list = [1,2,3,4,5,6,7,8]
num2(list)

输出结果为:

[1,4,9,16,25,36,49,64]

上述方法为整形,若将其扩展为字符或字符串呢,我们再来看一个例子——大小写转换。

为了简单,我们只将['a','b','c','d']转换为['A','B','C','D']

def upperNum(list):
    newList = []
    for x in list:
        newList.append(x[:].upper())
    return newList

执行函数:

list = ['a','b','c','d']
print upperNum(list)

输出结果为:

['A', 'B', 'C', 'D']

通过上述两个例子,你是否发现了相同点。均是一个数组中元素通过某种方法进行变换,唯一不同的是传入数组的类型与方法不同。那么,我们能否定义出这个相同点函数,将数组和方法作为参数传递进去。

幸运的是,泛型在python中非常简单,这让我们无需考虑数组中变量的类型。关于泛型的讨论,在下面补充中会介绍,如果你了解其他编程语言相信一定不会陌生。

而如何将函数作为参数传递进去,这便是函数式编程

函数式编程就是一种抽象程度很高的编程范式,纯粹的函数式编程语言编写的函数没有变量,因此,任意一个函数,只要输入是确定的,输出就是确定的,这种纯函数我们称之为没有副作用。而允许使用变量的程序设计语言,由于函数内部的变量状态不确定,同样的输入,可能得到不同的输出,因此,这种函数是有副作用的。

python对函数式编程提供部分支持,既可以作为参数传递,又能返回一个函数。由于python允许使用变量,因此,python不是纯函数式编程语言

我们来定义一个map函数,其参数为function方法和sequence数组,而通过研究发现其返回值依然是是一个数组。

def map(function,sequence):
    newList = []
    for x in sequence:
        newList.append(function(x))
    return newList

上述两个通过map方法便能合二为一:

def num2(x):
  return x * x

def upperNum(x):
    return x[:].upper()

map(num2,list)
map(upperNum,list)

执行结果仍然为:

[1,4,9,16,25,36,49,64]
['A', 'B', 'C', 'D']

注,在python2中是能够直接出来结果的,但是在python3中这个会返回一个对象。要想用到结果就必须的在前面加上list来转化一下,比如:

在系统库中的map函数定义如下,有一个可选参数

map(function, sequence, *sequence_1)

如果给定多个序列,则函数被调用,其中包含相应的参数列表每个序列的项;当不是全部时,用None代替缺失值使得序列的长度相同。如果函数没有,返回一个列表序列的项(或一个数组的列表,如果不止一个序列)。

例如:

 l1 = [ 0, 1, 2, 3, 4, 5, 6 ]  
 l2 = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ]  
 map(f, l1, l2)  

结果为:

[(0, 'Sun'), (1, 'Mon'), (2, 'Tue'), (3, 'Wed'), (4, 'Thu'), (5, 'Fri'), (6, 'Sat')]  

补充中提到一个匿名函数,对map方法仍能进行进一步的简化。

filter()

filter 意思是过滤器,即为筛选出数组或元祖中符合条件的元素。首先来看一个例子,选出1-10中的奇数:

def is_Odd(list):
    newList = []
    for x in list:
        if x%2 :
            newList.append(x)
        else:
            pass
    return newList

执行函数:

list = [1,2,3,4,5,6,7,8,9]
is_Odd(list)

输出结果为:

[1, 3, 5, 7, 9]

再来看另一个例子,假设有一个标记文件路径的数组字符串数组exampleFiles赋值如下:

exampleFiles = ["README.md", "HelloWorld.py","HelloSwift.swift", "HelloPython.py"]

现在我们想从数组中取出.py数组,使用一个循环便可得到:

def getPyFile(fileNames):
  newFileNames = []
  for fileName in fileNames:
      if ".py" in fileName:
         newFileNames.append(fileName)
  return newFileNames

执行这个函数:

fileNames =  ["README.md", "HelloWorld.py","HelloSwift.swift", "HelloPython.py"]
getPyFile(fileNames)

结果为:

['HelloWorld.py', 'HelloPython.py']

从上述两个例子中可以得出两个方法的共同点,即返回那些函数(项)为真的序列项。如果函数是None,返回True的项,并返回一个列表。(如果序列是一个元组或者字符串,返回相同的类型)

因此,有了上述map函数的基础,我们可以定义一个过滤器方法,将上述方法合二为一:

def filter(function,sequence):
    list = []
    for x in sequence:
        if function(x):
            list.append(x)
    return list

以筛选文件为例,执行这个方法:

def getPyFile2(fileName):
  if ".py" in fileName:
       return fileName
   else:
       pass

filter(getPyFile2,fileNames)

执行这个函数,结果为:

['HelloWorld.py', 'HelloPython.py']

与map相同,在python3中filter函数返回的是一个对象,需要加list转换成数组

reduce()

reduce意思是聚合,有了map和filter函数的研究基础,我们同样先来讨论一个简单的函数,定义一个函数计算数组中所有整数的和。

python函数库中虽然自带有sum()函数,但我们仍自定义函数解决

def sum(list):
    result = 0
    for x in list:
        result += x
    return result

执行函数:

list = [1, 2, 3, 4, 5, 6, 7, 8, 9]
sum(list)

结果为:

45

再来看另外一个例子:数以一组单词,将其拼成一个句子。例如将数组["I","am","a","good","boy"]拼接成一个字符串"I am a good boy"

定义函数如下:

def append(list):
    result = ""
    for x in list:
        result = result + x+" "
    return result

执行函数:

list = ["I","am","a","good","boy"]
append(list)

结果为:

"I am a good boy "

分析上面两个函数相同的地方,它们都用一些值初始化了一个变量result,它们进行处理的时候都遍历了整个数组list,并通过某种算法更新result。要定义实现这种通用算法的一个通用函数,有两处需要进行抽象:result的初始值以及在每个循环中用于更新result值的函数。

因此我们可以定义一个函数函数满足上述需求,注意reduce函数最终返回的是一个value值而非数组:

def reduce(function,sequence):
    result = None
    for x in sequence:
        result = function(result,x)
    return result

其中function为需要操作的函数,sequence为数组,将resultsequence中元素操作后值赋予result,进行循环操作。

执行上述函数操作便得以简化:

def sum(a,b):
    return  a + b

def append(a,b):
    return  a + " " + b

reduce(sum,list)
reduce(append,list)

这我们得出所需要的reduce函数。在系统中的reduce定义为 reduce(function,sequence,initial=None),由于初值并非只有0或空字符串,可以为任意,因此需要赋初值,在这里给予一个初值变量并默认初值。将上述reduce函数改编为:

def reduce(function,sequence,initial=None):
    result = initial
    for x in sequence:
        result = function(result,x)
    return result

补充

1.匿名函数

在Python中,对匿名函数提供了有限支持。还是以map()函数为例,计算f(x)=x2时,需定义一个f(x)的函数,然后使用map函数,如:

def f(x):
  return x * x

list(map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9]))

输出结果为:

[1, 4, 9, 16, 25, 36, 49, 64, 81]

使用匿名函数,便能用一行解决:

list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))

输出结果同样为:

[1, 4, 9, 16, 25, 36, 49, 64, 81]

从上述例子可以看到,匿名函数关键字为lambda,冒号前面的x表示函数参数。匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。

用匿名函数有个好处,因为函数没有名字,不必担心函数名冲突。此外,匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数,例如:

f = lambda x: x * x
f(5)

结果为:

25

因此,上述几段程序使用匿名函数便能简化很多:

filter(lambda fileName:".py" in fileName,fileNames)
reduce(lambda a, b: a + b,list)
reduce(lambda a, b: a + " " +b,list)

2.sorted函数

将函数作为参数可不仅仅上述几个函数,比如sorted函数,顾名思义为排序,使用起来也很简单。

sorted([36, 5, -12, 9, -21])
[-21, -12, 5, 9, 36]

在系统中,sort函数的定义为:

def sorted(iterable, cmp=None, key=None, reverse=False)

其高阶在于后面的三个参数。reverse参数是一个bool变量,是否倒序,默认值为False(正序)。key是关键字,是用于排序的对象。在上个例子中为数组本身,当然也可以对数组的个位数进行排序,如:

sorted([36, 5, 12, 9, 21],key=lambda x:x%10)
[21, 12, 5, 36, 9]

cmp参数是比较的方法,加入我们要实现倒序而不是用reverse,可以这样写:

sorted([36, 5, -12, 9, -21],cmp=lambda x,y:cmp(x,y))
[-21, -12, 5, 9, 36]

练习

作为本文的结束,给出一个小例子供大家理解三个函数。(题目来源于 objc.io出版的《函数式编程》)

有一组城市和人口数据如下:

name: "Paris", population: 2243
name: "Madrid", population: 3216
name: "Amsterdam", population: 811
name: "Berlin", population: 3397

假如我们想要找出至少有1百万人口的城市并打印出它们的城市名和人口,输出结果为:

City: Population 
Paris : 2243000 
Madrid : 3216000 
Berlin : 3397000

首先我们过滤掉小于100万人口的城市。然后使用map函数进行影射,将城市人口的单位进行转换。最后,我们使用reduce通过城市名和人口的列表计算得出一个字符串。这里我们使用了标准库的map,filterreduce函数。结果,我们可以像链条一样将这些函数串起来。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容