| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> Python知识库 -> python函数式编程 -> 正文阅读 |
|
[Python知识库]python函数式编程 |
python列表推导式列表推导式的语法格式: 例如,将列表中每个元素加1
当然也可以加 一般语法格式为:
lambda表达式lambda表达式是根据丘奇的lambda演算定义的,它是最纯粹的函数式编程。 语法格式为: 可以看下下面的例子🌰 :
当然可以接收任意的参数:
如果要进行逻辑判断呢?当然也是可以的,不过有一点是需要注意的,那就是不能使用
如果想到一个列表进行筛选,那岂不是也需要判断条件吗?让我们再来看一个🌰 : 加入我们想筛选出一个列表中的偶数,返回一个符合条件的所有元素组成的新列表,可以这样做
看到里面使用到了 filter
看一个接收具名函数的例子🌰 :
像这种简单的函数,也许使用匿名函数会更简洁,也不影响代码的可读性。但需要注意的是, map通过 同样的,它接收一个单参数函数,但是这个函数不需要像 看下面一个例子🌰 :
同样我们可以将具名函数
我们也可以传入内置单参函数:
reduce假如我们要将一个列表中的所有元素相加求和,应该怎么做?嗯,可以这样子:
但,我相信你知道我不是这个意思。我想说的是,如果我们使用
发现什么不同了么?对比前面的 它的工作过程大概是这样的: mapReduce
我们可以直接封装为
递归递归和函数调用递归就是这样,让人以为清楚又不清楚,在解释什么是递归之前,先说说什么不是递归。
函数 让我们再来看一个递归的经典例子:斐波那契数列。 从斐波那契数列说开来斐波那契数列:
所以,递归就是一个函数调用另一个函数,只是恰好被调用的函数与调用函数的名称相同而已。 但是我们如果对一个问题进行递归算法的设计呢? 我们想一想,一般的函数调用,函数A调用函数B,调用完之后就结束了。就算是有多层嵌套,那么函数A调用函数B,函数B调用函数C,函数C再调用函数D,调用到最后还是会结束,然后逐一向外返回结果。而递归这种自己调用自身的呢?在没有任何限制的情况下,会无限的进行嵌套调用,不会停止,导致爆栈。可是应该何时判断递归调用结束,避免爆栈呢?当然是在函数自身内部进行判断,也是到达所谓的 我们来分析一下这个斐波那契数列递归算法的基本情况: 到哪里停止?像我们上面的例子来说,输入5,递归调用自身,需要f(4),参数 认真说说打家劫舍问题小偷去偷东西,他所带的包只能放下42个单位的东西,他可以偷到的东西重量分别是5、10、18、23、30、45,在这种情况下,小偷所能够偷到的货物重要最多是多少呢? 我们直观来看,如果他选择18和23,那么总重量为41,是目前最好的组合。但是他所能偷到的东西增加了一个2单位的物品,情况就不一样了,这时候他选择2、10、30三种物品,总重量刚好就是42。 在这个例子里,就存在两种基本情况,一个是小偷所能带走的容量,另一个是他所选择物品的总重量。我们来设计这样一个函数,他接收两个容量和总重量两个参数,返回能够选择的物品之间的最大重量:
如何设计这样一个递归函数呢?我们从考虑基本情况说起。先考虑容量,容量最小是什么情况呢? 容量最小肯定只能是0,虽然现实中肯定不可能小于0,但是作为参数判断,肯定要考虑小于0的情况,因此当输入的容量 <= 0 的时候,直接返回0,容量为0根本没法偷。 因此:
这时候我们来考虑第二种情况,如果能偷的物品列表为空呢?也是没得偷的情况,这种情况的话能偷的情况肯定也是0,因为没得偷:
这两种情况肯定是并列的,所以我们可以放在一起:
这就是基础的情况,接下来我们来考虑如何设计递归呢? 我们这样来考虑,面对一个一个的物品(因为列表是有顺序)的,所以对于当前物品,我们面临两种选择,偷或者不偷。首先有一种特殊的情况是需要考虑的,那就是如果第一个物品的重量就是大于容量的话,我们的选择是不偷。
如果第一个物品的重量是等于容量的话,我们的选择是直接偷:
如果第一个物品重量小于剩余容量的话,则我们要考虑偷或者不偷,但是事实上却没有这么简单。 因为我们的目标是偷到的物品总重量最大,因此可能存在这两种不同的情况: 1.假如剩余容量capacity为10,物品重量列表为items[8,4,6],那么这种情况下应该不偷第一个物品,我们选择偷后两种物品,这样能够偷到的总重量为10。 2.但是如果同样容量为10的情况下,物品重量列表为[4,8,6],这种情况下我们应该取第一个和第三个物品,这样子偷的物品重量最大。 所以不能只看当前物品重量是否小于容量来决定是不是要偷,而是要 你或许会有这样一个疑问,那我岂不是手动要比较每一次?那不是需要全部比较完才知道结果?事实上递归是这样运作的:本次在A偷与不偷,我们全部走一次,偷的情况下,偷了以后再看剩余的容量和剩余的物品列表,再继续看当前物品偷不偷,然后这时候又是两种情况都进行,偷的情况下走了一个分支,继续下一次偷与不偷的判断,然后不偷的情况走一个分支,继续下一次偷与不偷的判断。到了最后基本的情况开始一层一层的返回,返回每一次偷的结果和不偷的结果,对比得到偷与不偷,这个结果再继续返回,在对比上一层偷与不偷的结果,只到返回到最外层,判断最初的那个物品的选择,偷与不偷的情况。 是不是有点烧脑🤯 ?递归就是这样,你以为你理解了,但细细考虑,你发现你还是没考虑情况,总是把自己陷进去,这可能是由于计算机是用栈来记录递归调用情况的,而我们的大脑并不是的原因吧。 让我们用代码来描述这个过程:
由于每一次都有两种情况,所以如果物品列表items中有n个物品,那么最多可以产生2的n次方次调用。 这里最难理解的,可能是每一次的两种选择,需要再假定当前取或不取情况下两种选择分别的情况,然后这个过程持续进行,可能脑中很难去演义这整个过程。我觉得好的方式是,不在大脑中去演示这种一直递归的情况,因为我们的脑子没有一个栈来保存调用记录,而是理解清楚每一个选择都需要简化的一个选择,直到最基本的情况。 让我们来看下全部的代码:
我们来验证一下:
我们来总结一下: 首先递归就是一个函数调用另一个函数,只不过是函数调用的恰好是它自身。我们在设计递归函数时,使用的策略是解决较小版本的问题让我们更接近解决原来版本的问题,这个较小版本的问题更加接近基本情况。我们需要清楚的就是: 典型的递归函数应该有两个主要部分: (1)基本情况:针对函数的“最简单”参数返回的值,这种情况下已经没有自相似性的存在。 (2)递归情况:这是较小版本的问题的解。利用更小或者更简单的参数来调用该函数,然后以某种方式利用较小问题的解来解决原始问题。 另外递归的模式可以总结为: (1)将输入分解为first和rest,或者类似的东西,即让问题简化 (2)对rest部分进行递归 (3)按照特定于问题的方式,将递归调用的结果和rest组合起来 (4)记住并推理基本情况,有时候可能需要几种基本情况。 那么递归难道仅仅只是如此吗?我的意思是说,难道所有的递归函数都只需要考虑两种情况吗?当然不是这样,接下来我们就看一个问题。
举个例子,假如给定的两个字符串分别是 我们来分析这三种情况: 如果在头部插入 因此每次我们有三种选择,相比上面的例子相对复杂了一些。 这时候我们来看下这个问题所需要考虑的基本问题: 我们所接收的参数有两个,分别是字符串A和字符串B,那么最基本的情况是字符串为空。假如A为空,B不为空,那么A变成B所需要的步骤即为B的长度,将B中每一个字符都依次插入到A;如果A不为空,B为空,那么A变成B所需要的步骤即为A的长度,将A中每一个字符都删除,直至为空;那么如果A和B同时为空的话,那么不需要任何操作,步骤为0。 我们来写下代码:
考虑完基本的情况,我们来考虑递归的情况: 我们要逐个对比两个字符串的每个字符是否相同,如果相同,那么直接删除当前字符,然后将问题简化为去掉当前字符的情况。
如果字符串当前所比较的两个字符不相同呢?我们可以有三种操作,意味着我们有三种选择,这时候可以像上面的例子一样,我们全部尝试,看哪一个步骤最少。但是我们是要递归进行,在递归时候我们要考虑的就是参数如何变化,每一次的结果是怎样的。上面已经分析过了,我们再来回顾一下: (1) (2) (3) 转化为代码:
我们看下完整的代码:
我们来验证一下:
讲了这么多的递归,我们何不来练习一把?就像上学时候,总是要做大量的练习来巩固知识的。 刻意练习 —— 从leetcode上的递归问题开始叭两数相加【题目】给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。 请你将两个数相加,并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外,这两个数都不会以 0 开头。 【解题】我们考虑用递归来解决这个问题。首先考虑基本情况,给定的输入是两个链表,要做的事情是把链表节点上的值相加,那么边界条件便是链表的某一个节点是不是存在呢?那既然有两个链表,可能的情况有三种了:两个链表的节点都不存在;链表A的节点存在,链表B的节点不存在;链表A的节点不存在,链表B的节点存在。如果是第一种情况,就没有什么好说的了,应该直接返回 我们来将以上的分析转化为代码:
那么接下来便可以考虑递归的情况了: 递归进行的便是两个节点的值相加,这样子就会存在 我们来看下👀代码:
进位是处理完了,可是递归呢?当然我们刚才写的就是递归的内容,但是递归调用呢?递归调用当然是调用自身了,也就是 这个时候再想想题目里要求我们返回的是什么?是一个链表!那我们每一个去初始化一个链表返回不就可以了吗?那这个链表的值是什么,以及下一个节点该指向什么呢?首先说值,应该是两个链表对应节点值的和,如果需要进位那就将进位加到下一个节点上去,而本身的值应该是 那我们要返回的链表的下一个指向呢?我们目前还是没有进行递归调用,我们可以在这里进行递归调用,那便是:
是不是感觉会有一些惊奇😂 但其实如果仔细想想,我们这个函数的目标就是生成一个新的链表,返回值当然应该是一个链表了。 我们来看一下整体的代码:
|
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/15 23:54:51- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |