Python3学习(12)--高阶函数 (二)

上一篇我们讲了Python内置的两个高阶函数map和reduce,本篇我们继续讲高阶函数,一个拥有过滤效果的函数,filter


讲之前我们思考一个问题,上篇我们提到了,map和生成式很相似,map能做的,生成式也可以,那么反过来呢? 下面,我们看一个例子:

有一个1--10的整数列表list(range(1,11)),我们想要留下偶数部分的元素,去除奇数部分,我们怎么做?

A、用生成式很容易办到:

B、我们根据相似点,也用条件走一遍函数,然后map实现:


【even谷歌翻译:偶数】我们发现,is_even(x)函数里面,我们写的没错啊,确实是返回偶数啊,为什么map把函数作用在序列上,就变味了呢?

因为map的机制,是依次把函数作用在序列的每一个元素上,并不是说,元素不符合条件了咱map就放弃它了,就算1不是偶数条件,map也要用None来占个位,也就是序列的长度前后没发生变化,如果你想对map得到的新序列继续操作,得到和生成式一样的效果,我劝你就此打住,太费劲了,我们的Python已经考虑到了这一点,filter函数,登场(鲜花,尖叫,掌声不断):

C、map被PK下去了,没事,我们filter顶上:

我们先来介绍一下filter函数,显然,要想PK,我们的filter必须具备map应有的性质,所以:

(1)第一个参数指向一个函数

(2)第二个参数是一个可迭代的对象--Iterable(当然生成器也是可以迭代的,下面会说到这个参数)

(3)函数具有过滤效果,这就意味着,第一个参数指向的函数需要是条件函数,序列中满足条件的元素将被保留下来

(4)函数返回值,也是一个迭代器 Iterator

根据上面的特性,我们改下上面的demo:


可以吧,filter乐于助人,你map做不到的,我fitler帮你。

利用filter的过滤功能,我们再来实现一个小例子:

假设有个list,里面含有整数,None,字符串以及空串(空格也算),现在我们要对其进行过滤,去掉int、None和空串,保留有意义的str,我们看下demo:

#!/usr/bin/env python3
# encoding:utf-8

def is_str(s):
	if not isinstance(s,int):    #如果s本身是str的话
		return s and s.strip()   #返回有效的字符,过滤掉None和空(空串和空格)
	else:
		return isinstance(s,str) #如果是int类型的话,我们返回false,不是str的不要

L = [1,None,'',' ','A','Hello World',-20]
print('A----------过滤前:')
print(L)
print('B----------过滤后:')
print(list(filter(is_str,L)))

我们看下执行结果:



噢啦,通过以上的讲解,我们对filter的用法已经掌握了,如果你觉得上面的太过简单,那我们就增加点难度,我们知道质数又叫做素数,一个大于1的自然数,除了1和它自身外,不能被其他自然数整除的数叫做质数;否则称为合数。 好的,接下来我们就来求一个1000以内的所有质数,我们先不急,一步步来:

开始之前,我们来介绍一个简洁函数的使用--lambda表达式

我们知道条件语句可以用if...else..来写,但是有一种比较简洁的写法就是三目运算,比如,我们想知道10是否是奇数,如果是,我们显示True,反之则是False,我们可以这样:


明显,答案是False,这个例子不是告诉我们答案有多简单,而是为了引出下面的lambda表达式

既然条件有三目运算,来代替简单的条件语句,试想一下,简单的函数,我们有替代吗?有,Python也提供lambda表达式,我们还是拿本篇学习的filter函数结合简单条件函数和lambada表达式来分别过滤掉偶数部分的数列:

#!/usr/bin/env python3
# encoding:utf-8


def is_odd(n): #是否是奇数
	return n%2 >0

L = [1,2,3,4,5,6,7,8,9,10]
print("A-------我们用一般简单函数可以做到过滤")
print(list(filter(is_odd,L)))
print("B-------我们用lambda表达式也可以做到过滤")
print(list(filter(lambda n:n % 2 > 0,L)))


看下执行结果:


好了,通过上面的例子,我们知道了lambda的基本用法,其实和条件函数一样,但是lambda表达式更加简洁:


回到正题,我们可是要计算质数的啊,根据质数的特点

(1)只能被1和自己整数(2是最小的素数)

(2)不能被第一条外其他的自然数整除 - - 当然,这个是小于自身的自然数,你不可能让8去除以9,来判断它是不是符合素数

我们先用C#来实现以下这个1000内质数的求解:

 for (int x = 2; x <= 1000; x++)
            {
                int a = 1;   //是否是质数的标记 1是0否

                for (int y = 2; y < x; y++)//双重for循环,目的,让当前的数和除自身外的其他小于自身的数整除
                {
                    if (x % y == 0)  //如果存在可以被其他数整除的情况,只要出现一次,就不是质数
                        a = 0;
                }

                if (a == 1)

                    Console.Write("{0}\t", x);//第一层for循环,输出标记等于1的质数 

            }
看下执行效果:


看了C#版的质数求解,是不是很简单,也就是两个for循环,一个进行质数判断,一个进行质数输出。试想一下,我们用Python来写的话,应该怎么来设计呢?首先,我们要有一个1000以内的自然数序列,根据质数特点,我们可以简化下这个序列,因为除2以外的偶数外,都不符合质数要求,因此,我们只要从3开始的奇数,作为我们最开始的基础自然数序列:



A、构造一个3开始的奇数生成器函数(为什么是生成器呢,因为如果直接返回list的话,并不是我们想要的结果,所以,我们需要后续对返回的生成器进行二次过滤)

#!/usr/bin/env python3
# encoding:utf-8

#质数又称素数,一个大于1的自然数,除了1和它自身外,不能被其他自然数整除的数叫做质数;否则称为合数
#偶数肯定不是素数,因为它可以整除2,但2却是例外,因此我们构造从3开始的奇数生成器
def is_odd_3():
	n=1
	while n<=1000:
		n = n + 2
		yield n

print(list(is_odd_3()))

由于,这个生成器转换为list后输出的数比较多,这里就不放图了,下面我们继续:


B、上面提到过,之所以构造一个生成器,是因为这个生成器我们需要二次过滤,而这个过滤条件函数,我们用上面我们学到的lambda表达式来代替,但是我们现在还不知道函数该怎么写,我们利用筛选的思路(filter)提供另一个可以计算出素数的方法:

首先我们A步骤已经列出了所有1000以内的所有奇数(2不考虑,是因为2已经知道是素数了,但是我们还是需要把2写在最前面,作为被除数)

由于1000太大,我们这里只举到15

a)、[2, 3, 5, 7, 9, 11, 13, 15]

我们把2取出(2取出后,剩余的数列是【3,5,7,9,11,13,15】),让它和其余的数列进行除法,凡是不能被整除的留下,筛选后得到新的数列:

[3,5,7,9,11,13,15]  -- > 此时筛选出一个素数 【2】 

b)、我们对新的序列重复a的操作,取出3,和余下数列依次进行整除,我们得到新的数列:

[5,7,11,13]-->很显然,过滤了9和15,我们得到一个质数 -->【3】

c)、继续,对上面的数列进行操作,5取出,和剩余数列依次整除,得到新数列[7,11,13]-->我们又得到一个质数-->【5】

.......是不是看出了规律,每次过滤得到的新序列的第一个元素就是质数(没毛病,老铁们),然后我们重复对新序列进行过滤就行了,因此,这里涉及两个序列交替,一个是新的,一个是旧的,而每次都要弹出新的质数,是不是我们又要构造一个生成器函数(C中会说道),用yield来保存质数;因此,我们要写的条件函数很关键,我们分析一下,首先要有个函数,用来接收取出的数,然后,我们要拿着这个数去和剩余序列中的数进行整除,因为剩余序列是动态的,而取出的第一个数n是固定的,因此,我们用函数里面套lambda表达式的方式来实现我们的条件函数(也就是后续filter的第一个参数,这个参数必须定位准确):

def _not_divisible(n):
	return lambda x: x % n >0 #我们过滤掉能被n整除的部分 

C、接下来,也是最后一步,首先我们初始化一个生成器,用yield来保存质数(同时该质数也是被取出的数),最后我们利用filter函数过滤得到新的序列,然后循环重复操作,我们看下demo怎么实现:

def Prime_Numers():
	yield 2           #首先,我们不要忘了2,否则会直接从3开始输出 G -->Generator
	G = is_odd_3()    #这里,初始化一个生成器(也是迭代器,可以next),1000以内3开头的所有奇数
	while True:       #当生成器next不动的时候,while循环终止
		n = next(G)   #取出第一个元素
		yield n       #根据我们发现的规律,第一个元素必然是质数
		G = filter(_not_divisible(n),G) #构造新的生成器【里面含有的序列的第一个元素必然是一个质数】
		

Finall、我们整合下以上功能,demo如下:

#!/usr/bin/env python3
# encoding:utf-8

#质数又称素数,一个大于1的自然数,除了1和它自身外,不能被其他自然数整除的数叫做质数;否则称为合数
#偶数肯定不是素数,因为它可以整除2,但2却是例外,因此我们构造从3开始的奇数生成器

from __future__ import print_function  #熟悉吧,为了不让print换行

def is_odd_3():
	n=1
	while n<=1000:
		n = n + 2
		yield n


def _not_divisible(n):
	return lambda x: x % n >0 #我们过滤掉能被n整除的部分 


def Prime_Numers():
	yield 2           #首先,我们不要忘了2,否则会直接从3开始输出 G -->Generator
	G = is_odd_3()    #这里,初始化一个生成器(也是迭代器,可以next),1000以内3开头的所有奇数
	while True:       #当生成器next不动的时候,while循环终止
		n = next(G)   #取出第一个元素
		yield n       #根据我们发现的规律,第一个元素必然是质数
		G = filter(_not_divisible(n),G) #构造新的生成器【里面含有的序列的第一个元素必然是一个质数】
		

L= list(Prime_Numers()) #这里需要转换一下,输出序列中的值

for n in L:
	print(n,'\t',end='')


几经周折,我们已迫不及待的想看下执行结果了:



结果正确着,和C#输出的一样,但是我们发现,二者的算法思路完全两种风格,代码理解上,你可能偏于C#版本的算法,但其实,如果你真正读懂了Python,你会发现,Python的编写更加舒服,生成器不是低落到尘埃,filter也不是随心所欲的内建,还有map、reduce、以及我们学过的生成式,这些不管是用起来还是结合着使用,都让编写者觉得很舒服,但是,要是理解他们,不花点时间研究 很难去欣赏它们。

结束语:

         你的对手在看书

         你的仇人在磨刀

         你的闺蜜在减肥

         隔壁老王在练腰

我们必须不断学习,否则我们将被学习者超越



  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值