python函数式编程
off999 2024-11-18 15:39 25 浏览 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求和代码
- 下一篇:python经典案例:求1到100之和
相关推荐
- Kubernetes 核心概念全景图:Pod、Node、Cluster、Control Plane 等
-
想真正读懂Kubernetes的底层运作,你必须理解它的“权力架构”。Pod是什么?Node是什么?ControlPlane又是做什么的?它们之间有什么关系?怎么协同工作?本篇带你构建一个...
- Helm 实战:用 Helm 部署一个 Nginx 应用
-
这一篇,我们将动手实战:用Helm从零部署一个Nginx应用,并掌握HelmChart的结构和参数化技巧。一、准备环境在开始之前,你需要确保环境中具备以下工具:已部署的Kubernet...
- 从零开始:如何在 Linux 上搭建 Nginx + Node.js 高性能 Web 服务
-
在现代互联网服务架构中,Nginx+Node.js已成为轻量级、高性能网站的首选组合。本文将带你从零开始,一步步搭建一个高并发、高可用的Web服务平台,让新手也能轻松掌握生产级部署思路。一、...
- NetBox 最新版 4.4.1 完整安装指南
-
NetBox最新版4.4.1完整安装指南(修正版)by大牛蛙1.系统准备#关闭SELinux和防火墙(仅测试环境)systemctldisable--nowfirewalldse...
- Termux 安装 linux 宝塔面板,搭建 Nginx+PHP+Mysql web 网站环境
-
Termux安装linux宝塔面板,搭建Nginx+PHP+Mysqlweb服务环境,解决启动故障奶妈级教程1.到宝塔面板官网:https://www.bt.cn/new/download...
- OpenEuler系统安装Nginx安装配置_openwrt安装nginx
-
NginxWEB安装时可以指定很多的模块,默认需要安装Rewrite模块,也即是需要系统有PCRE库,安装Pcre支持Rewrite功能。如下为安装NginxWEB服务器方法:源码的路径,而不是编...
- 多级缓存架构实战:从OpenResty到Redis,打造毫秒级响应系统
-
在传统的Web架构中,当用户发起请求时,应用通常会直接查询数据库。这种模式在低并发场景下尚可工作,但当流量激增时,数据库很容易成为性能瓶颈。多级缓存通过在数据路径的不同层级设置缓存,可以显著降低数据库...
- 如何使用 Nginx 缓存提高网站性能 ?
-
快速加载的站点提供了更好的用户体验并且可以拥有更高的搜索引擎排名。通过Nginx缓存提高你的网站性能是一个有效的方法。Nginx是一个流行的开源web服务器,也可以作为web服务器反向代...
- 如何构建企业级Docker Registry Server
-
很多人问我,虚拟机镜像和docker镜像的区别是什么?其实区别非常明显,我们可以通过阅读Dockerfile文件就可以知道这个镜像都做了哪些操作,能提供什么服务;但通过虚拟机镜像,你能一眼看出来虚拟机...
- 如何解决局域网SSL证书问题?使用mkcert证书生成工具轻松搞定
-
“局域网里弹出‘不安全’红锁,老板就在身后盯着演示,那一刻只想原地消失。”别笑,九成前端都经历过。自签证书被Chrome标红,客户以为网站被黑,其实只是缺一张被信任的证。mkcert把这事从半小时缩到...
- Docker 安全与权限控制:别让你的容器变成“漏洞盒子”
-
在享受容器带来的轻量与灵活的同时,我们也必须面对一个现实问题:安全隐患。容器并不是天然安全,错误配置甚至可能让攻击者“越狱”入侵主机!本篇将带你从多个层面强化Docker的安全防护,构建真正可放心...
- Kubernetes生产级管理指南(2025版)
-
在云原生技术持续演进的2025年,Kubernetes已成为企业数字化转型的核心引擎。然而,生产环境中的集群管理仍面临基础设施配置、安全漏洞、运维复杂度攀升等挑战。本文将结合最新行业实践,从基础设施即...
- 云原生工程师日常使用最多的工具和100条高频命令
-
在云原生时代,工程师不仅要熟悉容器化、编排和服务网格,还要掌握大量工具和命令来进行日常运维与开发。本文将从工具篇和命令篇两个角度,详细介绍云原生工程师每天都会用到的核心技能。一、云原生工程师常...
- 用 Jenkins 实现自动化 CI/CD_jenkins api自动执行
-
场景设定(可替换为你的技术栈)语言:Node.js(示例简单,任何语言思路一致)制品:Docker镜像(推送到DockerHub/Harbor)运行环境:Kubernetes(staging...
- 5款好用开源云笔记虚拟主机部署项目推荐
-
在个人数据管理与协同办公场景中,开源云笔记项目凭借可自主部署、数据可控的优势,成为众多用户的首选。以下推荐5款适配虚拟主机部署、功能完善的开源项目,附核心特性与部署要点,助力快速搭建专属云笔记系统。...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- Kubernetes 核心概念全景图:Pod、Node、Cluster、Control Plane 等
- Helm 实战:用 Helm 部署一个 Nginx 应用
- 从零开始:如何在 Linux 上搭建 Nginx + Node.js 高性能 Web 服务
- NetBox 最新版 4.4.1 完整安装指南
- Termux 安装 linux 宝塔面板,搭建 Nginx+PHP+Mysql web 网站环境
- OpenEuler系统安装Nginx安装配置_openwrt安装nginx
- 多级缓存架构实战:从OpenResty到Redis,打造毫秒级响应系统
- 如何使用 Nginx 缓存提高网站性能 ?
- 如何构建企业级Docker Registry Server
- 如何解决局域网SSL证书问题?使用mkcert证书生成工具轻松搞定
- 标签列表
-
- python计时 (73)
- python安装路径 (56)
- python类型转换 (93)
- python进度条 (67)
- python吧 (67)
- python的for循环 (65)
- python格式化字符串 (61)
- python静态方法 (57)
- python列表切片 (59)
- python面向对象编程 (60)
- python 代码加密 (65)
- python串口编程 (77)
- python封装 (57)
- python写入txt (66)
- python读取文件夹下所有文件 (59)
- python操作mysql数据库 (66)
- python获取列表的长度 (64)
- python接口 (63)
- python调用函数 (57)
- python多态 (60)
- python匿名函数 (59)
- python打印九九乘法表 (65)
- python赋值 (62)
- python异常 (69)
- python元祖 (57)