百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

python函数式编程

off999 2024-11-18 15:39 16 浏览 0 评论

python列表推导式

列表推导式的语法格式:[f(x) for x in L],其中 f(x) 表示一个函数,作用于列表 L 的每一个元素。所以整体来看,就是将函数f映射到列表 L 中的每个元素上。其返回值是一个新的列表

例如,将列表中每个元素加1

[x+1 for x in [10,20,30]] 
1

L 也可以是一个字符串

[x for x in 'string'] 
1

当然也可以加if 的判断条件,用于对元素进行条件判断。

一般语法格式为:[f(x) for x in L if ...],返回值是哪些符合 if 条件的元素所组成的新列表。


[str for str in ['java','javascript', 'python'] if len(str) > 4] 
12

lambda表达式

lambda表达式是根据丘奇的lambda演算定义的,它是最纯粹的函数式编程。

语法格式为:(lambda arg : state)(arg)。其中 lambda 是一个关键字,: 前的 arg 是参数,而 state 是要执行的语句,第二个括号里的 arg 是外部传入的参数。

可以看下下面的例子 :

(lambda x:x+1)(10) 
1

当然可以接收任意的参数:

(lambda x,y : x+y)(10,20) 
1

如果要进行逻辑判断呢?当然也是可以的,不过有一点是需要注意的,那就是不能使用 if 语句,因为它并不是一个表达式。纯粹的函数式编程是不能有 if 存在的。

 (lambda x : x % 2 == 0)(20) 
1

如果想到一个列表进行筛选,那岂不是也需要判断条件吗?让我们再来看一个 :

加入我们想筛选出一个列表中的偶数,返回一个符合条件的所有元素组成的新列表,可以这样做

list(filter(lambda x : x % 2 == 0,[10,20,1,2,3,8])) 
1

看到里面使用到了filter函数,我们就顺便说一下filter。

filter

filter 是用来进行筛选的函数,接收一个函数和一个列表,在上面的例子中,filter(func, list),我们传入的是一个 lambda表达式,它是一个匿名函数,[10,20,1,2,3,8]即是这个lambda要处理的元素集合,而filter 将函数映射到了列表中的每一个元素上,最后我们将返回的结果转化为一个list列表。

看一个接收具名函数的例子 :

def isOdd(x):
    return x%2 == 0
    
 list(filter(isOdd,[1,2,3,4,5,6])) 
1234

像这种简单的函数,也许使用匿名函数会更简洁,也不影响代码的可读性。但需要注意的是,filter 所接收的函数只能接收一个参数,且需要返回一个布尔值,这个函数本身被称为谓词,我认为是因为它执行一个动作,所以称为谓词。

map

通过 filter 很容易想到,如果我们不是做函数,而是做一个集合的完全映射呢?即把一个集合通过一个函数完全映射为了另外一个函数,这样的说法似乎很熟悉,是不是很像中学时候学到的函数,f(x) -> y,python的内置函数map可以帮我们做到这一点。

同样的,它接收一个单参数函数,但是这个函数不需要像filter那样返回布尔值,事实上我们完全可以用map替代filter,但是filter本身值得单独存在。

看下面一个例子 :

def add(x):
    return x + 1
    
list(map(add, [1,2,3])) 
1234

同样我们可以将具名函数 add 替换为匿名函数,我们使用lambda来做到这一点:

list(map(lambda x : x+1, [1,2,3])) 
1

我们也可以传入内置单参函数:

list(map(len, ['java', 'javascript', 'python'])) 
1

reduce

假如我们要将一个列表中的所有元素相加求和,应该怎么做?嗯,可以这样子:

sum([1,2,3,4,5]) 
1

但,我相信你知道我不是这个意思。我想说的是,如果我们使用reduce帮忙我实现呢?

reduce(lambda x,y : x+y, [1,2,3,4,5]) 
1

发现什么不同了么?对比前面的filter和map来说,它接收的是两个参数,是的,只能接收两个参数的函数。为什么这样呢?因为它的功能是做规约,即将列表中所有的元素规约为一个值。

它的工作过程大概是这样的:reduce 从列表中获取前两个元素,并使用传入的函数将其规约为单个值;然后这一过程继续进行,将得到的单个值与列表中的下一个元素结合,再次规约为单个值,这个过程一直持续,一直到仅剩单个值。

mapReduce

map 和 reduce 组合在一起会组成强大的功能,先使用 map 转换列表中的值,然后使用 reducce 将结果合并为单个值。

reduce(reduceFunction, map(mapFunction, yourList))
1
def add(x, y):
    return x + y
    
reduce(add, map(lambda x : x + 1, [1,2,3,4,5])) 
1234

我们可以直接封装为 mapReduce 函数:

def mapReduce(mapFunction, reduceFunction, yourList):
    return reduce(reduceFunction, mapFunction(yourList))
12

递归

递归和函数调用

递归就是这样,让人以为清楚又不清楚,在解释什么是递归之前,先说说什么不是递归。

def func1(x):
    result = 2*x
    return result

def func2(x):
    y = x + 1
    z = func1(y)
    return x + y + z

print(func2(5))
12345678910

函数 func2 里调用了 func1,请问这是递归吗?这不是,那函数调用和递归有关系吗?答案是有关系。

让我们再来看一个递归的经典例子:斐波那契数列。

从斐波那契数列说开来

斐波那契数列:

def factorial(n):
    if n <= 1:
        return 1

    return n * factorial(n-1)

factorial(5)
1234567

所以,递归就是一个函数调用另一个函数,只是恰好被调用的函数与调用函数的名称相同而已。

但是我们如果对一个问题进行递归算法的设计呢?

我们想一想,一般的函数调用,函数A调用函数B,调用完之后就结束了。就算是有多层嵌套,那么函数A调用函数B,函数B调用函数C,函数C再调用函数D,调用到最后还是会结束,然后逐一向外返回结果。而递归这种自己调用自身的呢?在没有任何限制的情况下,会无限的进行嵌套调用,不会停止,导致爆栈。可是应该何时判断递归调用结束,避免爆栈呢?当然是在函数自身内部进行判断,也是到达所谓的基本情况。因此使用递归最基本的,也是非常重要的地方,就是找到基本情况。

我们来分析一下这个斐波那契数列递归算法的基本情况:

到哪里停止?像我们上面的例子来说,输入5,递归调用自身,需要f(4),参数 n 的限制是什么?到了这个限制的边界上,应该如何处理?我们看到它的基本情况是 n=1,只有这样一个基本情况。然后递归函数只有一个基本情况吗?针对斐波那契来说是这样的,我们来看另外一个例子,也是常说的打家劫舍问题。

认真说说打家劫舍问题

小偷去偷东西,他所带的包只能放下42个单位的东西,他可以偷到的东西重量分别是5、10、18、23、30、45,在这种情况下,小偷所能够偷到的货物重要最多是多少呢?

我们直观来看,如果他选择18和23,那么总重量为41,是目前最好的组合。但是他所能偷到的东西增加了一个2单位的物品,情况就不一样了,这时候他选择2、10、30三种物品,总重量刚好就是42。

在这个例子里,就存在两种基本情况,一个是小偷所能带走的容量,另一个是他所选择物品的总重量。我们来设计这样一个函数,他接收两个容量和总重量两个参数,返回能够选择的物品之间的最大重量:

subset(42,[5,10,18、23,30,45]) // 41

subset(42,[2,5,10,18,23,30,45]) // 42
123

如何设计这样一个递归函数呢?我们从考虑基本情况说起。先考虑容量,容量最小是什么情况呢?

容量最小肯定只能是0,虽然现实中肯定不可能小于0,但是作为参数判断,肯定要考虑小于0的情况,因此当输入的容量 <= 0 的时候,直接返回0,容量为0根本没法偷。

因此:

if capacity <= 0:
    return 0
12

这时候我们来考虑第二种情况,如果能偷的物品列表为空呢?也是没得偷的情况,这种情况的话能偷的情况肯定也是0,因为没得偷:

if items == []:
    return 0
12

这两种情况肯定是并列的,所以我们可以放在一起:

if capacity <=0 or items == []:
    return 0
12

这就是基础的情况,接下来我们来考虑如何设计递归呢?

我们这样来考虑,面对一个一个的物品(因为列表是有顺序)的,所以对于当前物品,我们面临两种选择,偷或者不偷。首先有一种特殊的情况是需要考虑的,那就是如果第一个物品的重量就是大于容量的话,我们的选择是不偷。

if capacity <=0 or items == []:
    return 0
    
first = items[0] 
rest = items[1:] 

if first > capacity:
    return subset(capacity, rest)
12345678

如果第一个物品的重量是等于容量的话,我们的选择是直接偷:

if capacity <=0 or items == []:
    return 0
    
first = items[0] 
rest = items[1:] 

if first > capacity:
    return subset(capacity, rest)

if first == capacity:
    return first
1234567891011

如果第一个物品重量小于剩余容量的话,则我们要考虑偷或者不偷,但是事实上却没有这么简单。

因为我们的目标是偷到的物品总重量最大,因此可能存在这两种不同的情况:

1.假如剩余容量capacity为10,物品重量列表为items[8,4,6],那么这种情况下应该不偷第一个物品,我们选择偷后两种物品,这样能够偷到的总重量为10。

2.但是如果同样容量为10的情况下,物品重量列表为[4,8,6],这种情况下我们应该取第一个和第三个物品,这样子偷的物品重量最大。

所以不能只看当前物品重量是否小于容量来决定是不是要偷,而是要比较不同的情况下偷到的总重量。

你或许会有这样一个疑问,那我岂不是手动要比较每一次?那不是需要全部比较完才知道结果?事实上递归是这样运作的:本次在A偷与不偷,我们全部走一次,偷的情况下,偷了以后再看剩余的容量和剩余的物品列表,再继续看当前物品偷不偷,然后这时候又是两种情况都进行,偷的情况下走了一个分支,继续下一次偷与不偷的判断,然后不偷的情况走一个分支,继续下一次偷与不偷的判断。到了最后基本的情况开始一层一层的返回,返回每一次偷的结果和不偷的结果,对比得到偷与不偷,这个结果再继续返回,在对比上一层偷与不偷的结果,只到返回到最外层,判断最初的那个物品的选择,偷与不偷的情况。

是不是有点烧脑 ?递归就是这样,你以为你理解了,但细细考虑,你发现你还是没考虑情况,总是把自己陷进去,这可能是由于计算机是用栈来记录递归调用情况的,而我们的大脑并不是的原因吧。

让我们用代码来描述这个过程:

if first < capacity:
    
    use = first + subset(capacity - first, rest)
    
    lose = subset(capacity, rest)
    
    return max(use, lose)
1234567

由于每一次都有两种情况,所以如果物品列表items中有n个物品,那么最多可以产生2的n次方次调用。

这里最难理解的,可能是每一次的两种选择,需要再假定当前取或不取情况下两种选择分别的情况,然后这个过程持续进行,可能脑中很难去演义这整个过程。我觉得好的方式是,不在大脑中去演示这种一直递归的情况,因为我们的脑子没有一个栈来保存调用记录,而是理解清楚每一个选择都需要简化的一个选择,直到最基本的情况。

让我们来看下全部的代码:

def subset(capacity, items):
    if capacity <=0 or items == []:
     return 0

    first = items[0] 
    rest = items[1:] 
    
    if first > capacity:
     return subset(capacity, rest)

    if first == capacity:
     return first

    if first < capacity:
        
        use = first + subset(capacity - first, rest)
        
        lose = subset(capacity, rest)
        
        return max(use, lose)
1234567891011121314151617181920

我们来验证一下:

maxVal = subset(10,[3,4,2,9,10])
print(maxVal) 
12

我们来总结一下:

首先递归就是一个函数调用另一个函数,只不过是函数调用的恰好是它自身。我们在设计递归函数时,使用的策略是解决较小版本的问题让我们更接近解决原来版本的问题,这个较小版本的问题更加接近基本情况。我们需要清楚的就是:这个问题必须处理的最简单或者说最基本的输入是什么?

典型的递归函数应该有两个主要部分:

(1)基本情况:针对函数的“最简单”参数返回的值,这种情况下已经没有自相似性的存在。

(2)递归情况:这是较小版本的问题的解。利用更小或者更简单的参数来调用该函数,然后以某种方式利用较小问题的解来解决原始问题。

另外递归的模式可以总结为:

(1)将输入分解为first和rest,或者类似的东西,即让问题简化

(2)对rest部分进行递归

(3)按照特定于问题的方式,将递归调用的结果和rest组合起来

(4)记住并推理基本情况,有时候可能需要几种基本情况。

那么递归难道仅仅只是如此吗?我的意思是说,难道所有的递归函数都只需要考虑两种情况吗?当然不是这样,接下来我们就看一个问题。

字符串转化的最小距离:给定两个字符串,计算将第一个字符串转换成第二个字符串所需要的最少操作。可以采用三种操作:替换、插入和删除。

举个例子,假如给定的两个字符串分别是 alice 和 cmice,我们要将 alice 转换成 cmice,对比两个字符串,对于alice的第一个字符a,我们可以考虑将在头部插入一个c,也可以直接将a替换成c,也可以将a删除。

我们来分析这三种情况:

如果在头部插入c,那么第一个字符串(以下称为字符串A)变为calice,那么就将问题转化为了将字符串calice变为第二个字符串cmice(以下称为字符串B)。如果将a替换为c,那问题就转化为了将字符串clice变为字符串cmice。如果将a删除,那么问题就转化为了将字符串lice变为字符串cmice。

因此每次我们有三种选择,相比上面的例子相对复杂了一些。

这时候我们来看下这个问题所需要考虑的基本问题:

我们所接收的参数有两个,分别是字符串A和字符串B,那么最基本的情况是字符串为空。假如A为空,B不为空,那么A变成B所需要的步骤即为B的长度,将B中每一个字符都依次插入到A;如果A不为空,B为空,那么A变成B所需要的步骤即为A的长度,将A中每一个字符都删除,直至为空;那么如果A和B同时为空的话,那么不需要任何操作,步骤为0。

我们来写下代码:

def translate(s1,s2):
    if len(s1) == 0:
        return len(s2)
  
    if len(s2) == 0:
        return len(s1)
123456

考虑完基本的情况,我们来考虑递归的情况:

我们要逐个对比两个字符串的每个字符是否相同,如果相同,那么直接删除当前字符,然后将问题简化为去掉当前字符的情况。

def translate(s1,s2):
    if len(s1) == 0:
        return len(s2)
    elif len(s2) == 0:
        return len(s1)
    elif s1[0] == s2[0]:
        return translate(s1[1:],s2[1:])
1234567

如果字符串当前所比较的两个字符不相同呢?我们可以有三种操作,意味着我们有三种选择,这时候可以像上面的例子一样,我们全部尝试,看哪一个步骤最少。但是我们是要递归进行,在递归时候我们要考虑的就是参数如何变化,每一次的结果是怎样的。上面已经分析过了,我们再来回顾一下:

(1)如果在头部插入c,那么第一个字符串(以下称为字符串A)变为calice,那么就将问题转化为了将字符串calice变为第二个字符串cmice(以下称为字符串B)。当然可以不体现这一步,B去掉首字符,相当于插入之后A和B都去除首字符了,即tranlate(s1,s2[1:]),操作增加了一次(插入操作)。

(2)如果将a替换为c,那问题就转化为了将字符串clice变为字符串cmice。这时候字符串A和B都可以将当前字符(首字符)删除,即tranlate(s1[1:],s2[1:])`,操作增加了一次(修改操作)。

(3)如果将a删除,那么问题就转化为了将字符串lice变为字符串cmice。这时候A字符串都能将当前字符(首字符)剔除,而B字符串不变,即tranlate(s1[1:],s2),操作增加了一次(删除操作)。

转化为代码:

insert = 1 + translate(s1,s2[1:])
replace = 1 + translate(s1[1:], s2[1:])
delete = 1 + translate(s1[1:],s2)
return min(insert,replace,delete)
1234

我们看下完整的代码:

def translate(s1,s2):
    if len(s1) == 0:
        return len(s2)
    elif len(s2) == 0:
        return len(s1)
    elif s1[0] == s2[0]:
        return translate(s1[1:],s2[1:])
    else:
        insert = 1 + translate(s1,s2[1:])
        replace = 1 + translate(s1[1:], s2[1:])
        delete = 1 + translate(s1[1:],s2)
        return min(insert,replace,delete)
123456789101112

我们来验证一下:

print(translate('alcei','clsgt')) 
1

相关推荐

Python钩子函数实现事件驱动系统(created钩子函数)

钩子函数(HookFunction)是现代软件开发中一个重要的设计模式,它允许开发者在特定事件发生时自动执行预定义的代码。在Python生态系统中,钩子函数广泛应用于框架开发、插件系统、事件处理和中...

Python函数(python函数题库及答案)

定义和基本内容def函数名(传入参数):函数体return返回值注意:参数、返回值如果不需要,可以省略。函数必须先定义后使用。参数之间使用逗号进行分割,传入的时候,按照顺序传入...

Python技能:Pathlib面向对象操作路径,比os.path更现代!

在Python编程中,文件和目录的操作是日常中不可或缺的一部分。虽然,这么久以来,钢铁老豆也还是习惯性地使用os、shutil模块的函数式API,这两个模块虽然功能强大,但在某些情况下还是显得笨重,不...

使用Python实现智能物流系统优化与路径规划

阅读文章前辛苦您点下“关注”,方便讨论和分享,为了回馈您的支持,我将每日更新优质内容。在现代物流系统中,优化运输路径和提高配送效率是至关重要的。本文将介绍如何使用Python实现智能物流系统的优化与路...

Python if 语句的系统化学习路径(python里的if语句案例)

以下是针对Pythonif语句的系统化学习路径,从零基础到灵活应用分为4个阶段,包含具体练习项目和避坑指南:一、基础认知阶段(1-2天)目标:理解条件判断的逻辑本质核心语法结构if条件:...

[Python] FastAPI基础:Path路径参数用法解析与实例

查询query参数(上一篇)路径path参数(本篇)请求体body参数(下一篇)请求头header参数本篇项目目录结构:1.路径参数路径参数是URL地址的一部分,是必填的。路径参...

Python小案例55- os模块执行文件路径

在Python中,我们可以使用os模块来执行文件路径操作。os模块提供了许多函数,用于处理文件和目录路径。获取当前工作目录(CurrentWorkingDirectory,CWD):使用os....

python:os.path - 常用路径操作模块

应该是所有程序都需要用到的路径操作,不废话,直接开始以下是常用总结,当你想做路径相关时,首先应该想到的是这个模块,并知道这个模块有哪些主要功能,获取、分割、拼接、判断、获取文件属性。1、路径获取2、路...

原来如此:Python居然有6种模块路径搜索方式

点赞、收藏、加关注,下次找我不迷路当我们使用import语句导入模块时,Python是怎么找到这些模块的呢?今天我就带大家深入了解Python的6种模块路径搜索方式。一、Python模块...

每天10分钟,python进阶(25)(python进阶视频)

首先明确学习目标,今天的目标是继续python中实例开发项目--飞机大战今天任务进行面向对象版的飞机大战开发--游戏代码整编目标:完善整串代码,提供完整游戏代码历时25天,首先要看成品,坚持才有收获i...

python 打地鼠小游戏(打地鼠python程序设计说明)

给大家分享一段AI自动生成的代码(在这个游戏中,玩家需要在有限时间内打中尽可能多的出现在地图上的地鼠),由于我现在用的这个电脑没有安装sublime或pycharm等工具,所以还没有测试,有兴趣的朋友...

python线程之十:线程 threading 最终总结

小伙伴们,到今天threading模块彻底讲完。现在全面总结threading模块1、threading模块有自己的方法详细点击【threading模块的方法】threading模块:较低级...

Python信号处理实战:使用signal模块响应系统事件

信号是操作系统用来通知进程发生了某个事件的一种异步通信方式。在Python中,标准库的signal模块提供了处理这些系统信号的机制。信号通常由外部事件触发,例如用户按下Ctrl+C、子进程终止或系统资...

Python多线程:让程序 “多线作战” 的秘密武器

一、什么是多线程?在日常生活中,我们可以一边听音乐一边浏览新闻,这就是“多任务处理”。在Python编程里,多线程同样允许程序同时执行多个任务,从而提升程序的执行效率和响应速度。不过,Python...

用python写游戏之200行代码写个数字华容道

今天来分析一个益智游戏,数字华容道。当初对这个游戏颇有印象还是在最强大脑节目上面,何猷君以几十秒就完成了这个游戏。前几天写2048的时候,又想起了这个游戏,想着来研究一下。游戏玩法用尽量少的步数,尽量...

取消回复欢迎 发表评论: