【Python入门学习】10.高阶函数及用法(map、reduce、filter、sorted)

本节摘要:高阶函数的基本介绍,map( )函数、reduce( )函数、filter( )函数、sorted( )函数的介绍


文章目录

  • 高阶函数
    • map( )函数
    • reduce( )函数
      • 字符串转为整数的函数实现
      • 【练习一】用map()函数规范姓名首字母大写
      • 【练习二】编写prod()函数,利用reduce()求连成积
      • 【练习三】利用map和reduce编写一个str2float函数
    • filter( )函数
      • 【应用】产生素数的函数实现
      • 【练习】筛选回数
    • sorted( )函数
      • 排序算法
      • 【练习】用sorted()对列表分别按名字、成绩高低排序
    • 小记


高阶函数

Higher-order function,即高阶函数。

  • 变量可以指向函数

例如,Python内置的求绝对值的函数abs():

>>> abs(-5)                 # 调用该函数用此代码,abs(-5)是函数调用
5
>>> abs                        # 只写abs,abs是函数本身
<built-in function abs>
>>> x = abs(-5)          # 要获得函数调用结果,可以把结果赋值给变量,x变量指向函数abs()
>>> x
5
>>> x = abs               # 把函数本身赋值给变量
>>> x
<built-in function abs>

结论:函数本身也可以赋值给变量,即:变量可以指向函数。

如果一个变量指向了一个函数,则可通过调用该变量来调用这个函数:

>>> f = abs                  # 变量f现在已经指向了abs函数本身
>>> f(-8)                      # 直接调用abs()函数和调用变量f()完全相同
8
  • 函数名也是变量

函数名也是变量,函数名其实就是指向函数的变量,对于abs()这个函数,完全可以把函数名abs看成变量,它指向一个可以计算绝对值的函数

如果把abs指向其他对象

>>> abs = 10
>>> abs
10
>>> abs(-10)
Traceback (most recent call last):
  File "", line 1, in <module>
TypeError: 'int' object is not callable

abs指向10后,就无法通过abs(-10)调用该函数了!因为abs这个变量已经不指向求绝对值函数而是指向一个整数10

要恢复abs函数,请exit()退出后重启Python交互环境。

注:由于abs函数实际上是定义在import builtins模块中的,所以要让修改abs变量的指向在其它模块也生效,要用import builtins; builtins.abs = 10

  • 传入函数

既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,函数就可以传入函数和返回函数,这种函数就叫高阶函数。

简单高阶函数例如:

def add(x, y, f):
    return f(x) + f(y)

当调用add(2, -5, abs)时,参数x,y和f分别接收2,-5和abs,根据函数定义,可以推导计算过程为:

x = 2
y = -5
f = abs
f(x) + f(y) ==> abs(2) + abs(-5) ==> 7
return 7

代码验证:

>>> def add(x, y, f):
...     return f(x) + f(y)
...
>>> print add(2, -5, abs)
7

编写高阶函数,就是让函数的参数能够接收别的函数。

  • 总结
    把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。

map( )函数

map( ) 是一种高阶函数,它接收两个参数,第一个是函数,第二个是Iterable,即可迭代对象。map( )将函数依次作用于Iterable的每个元素,并将结果以Iterator,即迭代器的形式返回。这种方式类似构造出了数学中的映射过程。

例如,函数 f ( x ) = x 2 f(x)=x^2 f(x)=x2,把这个函数作用在一个list [1, 2, 3, 4, 5, 6, 7, 8, 9]上

>>> def f(x):
...     return x * x
...
>>> r = map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> list(r)
[1, 4, 9, 16, 25, 36, 49, 64, 81]

map()传入的第一个参数是f,即函数对象本身。结果r是一个Iterator,由于Iterator是惰性序列,可以用 list( ) 将其转化为list输出。

该例子也可用循环得出:

>>> L = []
>>> for n in [1, 2, 3, 4, 5, 6, 7, 8, 9]:
...     L.append(f(n))
...
>>> print L
[1, 4, 9, 16, 25, 36, 49, 64, 81]

map()作为高阶函数,还可以计算任意复杂的函数

例如,把list所有数字转为字符串:

>>> list(map(str, [1, 2, 3, 4, 5]))                  #str()可以将整数转化为字符串
['1', '2', '3', '4', '5']

我们还能用列表生成器得到同样的结果,但要用到for语句:

>>> b = [str(x) for x in [1, 2, 3, 4, 5]]
>>> b
['1', '2', '3', '4', '5']

reduce( )函数

reduce( )函数同样接收一个函数和一个Iterable两个参数,不同的是所传入的函数必须要接收两个参数,而reduce是把函数作用的结果继续与序列中下一个元素做累积计算。

其效果就是:

reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

例如,序列求和一般用用Python内建函数sum(),也可以用reduce实现:

>>> from functools import reduce
>>> def add(x, y):
...     return x + y
...
>>> reduce(add, [1, 3, 5, 7, 9])
25

reduce可用于把序列转化为相应的整数显示,例如整数13579

>>> from functools import reduce                  #加入内置函数reduce
>>> def fn(x, y):
...     return x * 10 + y
...
>>> reduce(fn, [1, 2, 3, 4, 5])
12345

字符串转为整数的函数实现

reduce配合map(),可以写出把str转换为int的函数:

>>> def fn(x, y):
...     return x * 10 + y
...
>>> def char2num(s):
...     return {
     '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s]
...
>>> reduce(fn, map(char2num, '13579'))  #map(char2num, ['1', '3', '5', '7', '9'])
13579
>>> from functools import reduce
>>> def fn(x, y):
...     return x * 10 + y
...
>>> def char2num(s):
...     digits = {
     '0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
...     return digits[s]
...
>>> reduce(fn, map(char2num, '13579'))
13579

整理成一个str2int的函数就是:

def str2int(s):
    def fn(x, y):
        return x * 10 + y
    def char2num(s):
        return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s]
    return reduce(fn, map(char2num, s))
from functools import reduce

DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}

def str2int(s):
    def fn(x, y):                             #将一个序列转化为对应的整数
        return x * 10 + y
    def char2num(s):                          #将字符串的每个字符转化为对应的整数,并组成序列
        return DIGITS[s]
    return reduce(fn, map(char2num, s))

还可以用lambda函数进一步简化成:

def char2num(s):
    return {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}[s]

def str2int(s):
    return reduce(lambda x,y: x*10+y, map(char2num, s))
from functools import reduce

DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}

def char2num(s):
    return DIGITS[s]

def str2int(s):
    return reduce(lambda x, y: x * 10 + y, map(char2num, s))

lambda函数的用法在后面介绍

【练习一】用map()函数规范姓名首字母大写

利用map()函数,把用户输入的不规范的英文名字,变为首字母大写,其他小写的规范名字。输入:[‘adam’, ‘LISA’, ‘barT’],输出:[‘Adam’, ‘Lisa’, ‘Bart’]:

# -*- coding: utf-8 -*-
def normalize(name):
    pass

# 测试:
L1 = ['adam', 'LISA', 'barT']
L2 = list(map(normalize, L1))
print(L2)

【交作业】

# -*- coding: utf-8 -*-
def normalize(name):
     return name[0].upper() + name[1:].

L1 = ['adam', 'LISA', 'barT']
L2 = list(map(normalize, L1))
print L2

输出结果:

['Adam', 'Lisa', 'Bart']

【练习二】编写prod()函数,利用reduce()求连成积

Python提供的sum()函数可以接受一个list并求和,请编写一个prod()函数,可以接受一个list并利用reduce()求积:

# -*- coding: utf-8 -*-
from functools import reduce
def prod(L):
    pass

print('3 * 5 * 7 * 9 =', prod([3, 5, 7, 9]))
if prod([3, 5, 7, 9]) == 945:
    print('测试成功!')
else:
    print('测试失败!')

【交作业】

# -*- coding: utf-8 -*-
from functools import reduce

def prod(L):
    def mul(x, y):
        return x * y
    return reduce(mul, L)

print '3 * 5 * 7 * 9 =', prod([3, 5, 7, 9])
if prod([3, 5, 7, 9]) == 945:
     print('测试成功!')
else:
     print('测试失败!')

输出结果:

3 * 5 * 7 * 9 = 945
测试成功!

【练习三】利用map和reduce编写一个str2float函数

利用map和reduce编写一个str2float函数,把字符串’123.456’转换成浮点数123.456:

# -*- coding: utf-8 -*-
from functools import reduce

def str2float(s):

print('str2float(\'123.456\') =', str2float('123.456'))
if abs(str2float('123.456') - 123.456) < 0.00001:
    print('测试成功!')
else:
    print('测试失败!')

【交作业】

# -*- coding: utf-8 -*-
from functools import reduce
def str2float(s):
    DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
    inde = s.find('.')
    leng = len(s)
    s1 = s[:inde]+ s[inde+1 :]
    return reduce(lambda x, y: x * 10 + y, map(lambda x : DIGITS[x], s1))/(10**(leng-inde-1))

filter( )函数

filter,即过滤,filter( )函数的作用是过滤序列。与上面map() reduce()两个函数类似,接收一个函数与一个序列,与之不同的是,filter()在把传入的函数此作用于序列的每个元素之后,根据返回值True或False来保留或移除该元素。用filter()这个高阶函数,关键在于正确实现一个“筛选”函数

例如,如写一个list中,删掉偶数,只保留奇数的函数:

>>> def is_odd(n):
...     return n % 2 == 1
...
>>> list(filter(is_odd, [1, 2, 3, 4, 5, 6, 7]))
[1, 3, 5, 7]

例二,把一个序列中的空字符串删掉:

>>> def not_empty(s):
...     return s and s.strip()   # strip() 方法用于移除字符串头尾指定的字符(默认为空格或换行符)或字符序列
...
>>> list(filter(not_empty, ['A', '', 'B', None, 'C', '   ']))
['A', 'B', 'C']

注意:filter( )函数返回的也是Iterator,惰性序列,需要用list( )来转化才能直接显示计算结果。

【应用】产生素数的函数实现

计算素数的一个方法是埃氏筛法,它的算法理解起来非常简单:
首先,列出从2开始的所有自然数,构造一个序列:
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, …
取序列的第一个数2,它一定是素数,然后用2把序列的2的倍数筛掉:
3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, …
取新序列的第一个数3,它一定是素数,然后用3把序列的3的倍数筛掉:
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, …
取新序列的第一个数5,然后用5把序列的5的倍数筛掉:
7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, …
不断筛下去,就可以得到所有的素数。

用Python来实现这个算法:

def _odd_iter():                             #定义一个初始值为3的奇数初始序列生成器
    n = 1
    while True:
        n = n + 2
        yield n

def _not_divesible(n):                       #定义筛选函数,不是n的倍数则返回True,筛选出不是n的倍数的数
    return lambda x : x % n > 0          #返回lambda函数。%在python中为求模运算。此表达式表示 x被n除后的余数大于0

def primes():                                #定义一个初始值为2,
    yield 2
    it = _odd_iter()                         #it为序列n的生成器,初始序列
    while True:
        n = next(it)                         #返回序列n的第一个值
        yield n
        it = filter(_not_divesible(n), it)    #过滤序列中为n的倍数的值

这个生成器先返回第一个素数2,然后,利用filter()不断产生筛选后的新的序列。

由于primes()也是一个无限序列,所以调用时需要设置一个退出循环的条件:

# 打印1000以内的素数:
for n in primes():                           #for循环取值
    if n < 100:                              #打印100以为的素数
        print(n)
    else:
        break

注意到Iterator是惰性计算的序列,所以我们可以用Python表示“全体自然数”,“全体素数”这样的序列,而代码非常简洁。

总结:

filter()的作用是从一个序列中筛出符合条件的元素。由于filter()使用了惰性计算,所以只有在取filter()结果的时候,才会真正筛选并每次返回下一个筛出的元素。

【练习】筛选回数

回数是指从左向右读和从右向左读都是一样的数,例如12321,909。请利用filter()筛选出回数:

# -*- coding: utf-8 -*-
def is_palindrome(n):
    pass

【交作业】

# -*- coding: utf-8 -*-
def is_palindrome(n):
    if str(n)[0:1] == str(n)[-1:]:        # 美中不足只对3位数字有效,故看答案2
        return n

# 测试:
output = filter(is_palindrome, range(1, 1000))
print('1~1000:', list(output))
if list(filter(is_palindrome, range(1, 200))) == [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99, 101, 111, 121, 131, 141, 151, 161, 171, 181, 191]:
    print('测试成功!')
else:
    print('测试失败!')

输入结果:

('1~1000:', [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99, 101, 111, 121, 131, 141, 151, 161, 171, 181, 191, 202, 212, 222, 232, 242, 252, 262, 272, 282, 292, 303, 313, 323, 333, 343, 353, 363, 373, 383, 393, 404, 414, 424, 434, 444, 454, 464, 474, 484, 494, 505, 515, 525, 535, 545, 555, 565, 575, 585, 595, 606, 616, 626, 636, 646, 656, 666, 676, 686, 696, 707, 717, 727, 737, 747, 757, 767, 777, 787, 797, 808, 818, 828, 838, 848, 858, 868, 878, 888, 898, 909, 919, 929, 939, 949, 959, 969, 979, 989, 999])
测试成功!
def is_palindrome(n):
    return str(n) == str(n)[::-1]            # 将字符串逆序
def is_palindrome(n):
    if str(n)[:]==str(n)[::-1]:
        return int(n)                # 其实n的形式并没有变化,int(n)多此一举
def is_palindrome(n):
    return str(n)[::1]==str(n)[::-1]     # 多此一举的[::1]正序hhh

sorted( )函数

排序算法

sort,分类、排序的意思。在Python中sorted( )函数可以将list排序。

>>> sorted([21, 5, -3, -10, 18])
[-10, -3, 5, 18, 21]

sort( )函数还能接收一个key函数来自定义排序,也就是先把传入的函数作用于list,根据结果进行排序,把传入的初始序列按照结果的顺序进行排序。

例如按绝对值大小排序:

>>> sorted([21, 5, -3, -10, 18], key=abs )
[-3, 5, -10, 18, 21]

key指定的函数将作用于list的每一个元素上,并根据key函数返回的结果进行排序。对比原始的list和经过key=abs处理过的list:

list = [21, 5, -3, -10, 18]
keys = [21, 5, 3, 10, 18]

然后sorted()函数按照keys进行排序,并按照对应关系返回list相应的元素:
keys排序结果 => [3, 5, 10, 18, 21]
| | | | |
最终结果 => [-3, 5, -10, 18, -21]

>>> sorted([21, 5, -3, -10, 18], key=abs )
[-3, 5, -10, 18, 21]

例二,倒序排序:

先自定义一个reversed_cmp函数:

def reversed_cmp(x, y):
    if x > y:
        return -1
    if x < y:
        return 1
    return 0

传入自定义的比较函数reversed_cmp,就可以实现倒序排序:

>>> sorted([36, 5, 12, 9, 21], reversed_cmp)
[36, 21, 12, 9, 5]

例三:字符串排序

>>> sorted(['bob', 'about', 'Zoo', 'Credit'])
['Credit', 'Zoo', 'about', 'bob']

默认情况下,对字符串排序,是按照ASCII的大小比较的,由于’Z’ < ‘a’,结果,大写字母Z会排在小写字母a的前面。

若要忽略大小写,按照字母序排序。要实现这个算法,不必对现有代码大加改动,只要我们能用一个key函数把字符串映射为忽略大小写排序即可。忽略大小写来比较两个字符串,实际上就是先把字符串都变成大写(或者都变成小写),再比较。

给sorted传入key函数,即可实现忽略大小写的排序:

>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower)
['about', 'bob', 'Credit', 'Zoo']

要进行反向排序,不必改动key函数,可以传入第三个参数reverse=True

>>> sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True)
['Zoo', 'Credit', 'bob', 'about']

由此可知,高阶函数的抽象能力是非常强大的,核心代码可以保持得非常简洁。

sorted()也是一个高阶函数。用sorted()排序的关键在于实现一个映射函数。

【练习】用sorted()对列表分别按名字、成绩高低排序

假设我们用一组tuple表示学生名字和成绩:

L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]

请用sorted()对上述列表分别按名字排序:

# -*- coding: utf-8 -*-
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
def by_name(t):
    pass

L2 = sorted(L, key=by_name)
print(L2)

再按成绩从高到低排序:

# -*- coding: utf-8 -*-
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
def by_score(t):
    pass

L2 = sorted(L, key=by_score)
print(L2)

【交作业】

按名字排序:

# -*- coding: utf-8 -*-
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
def by_name(t):
    return t[0]

L2 = sorted(L, key=by_name)
print(L2)

输出结果:

[('Adam', 92), ('Bart', 66), ('Bob', 75), ('Lisa', 88)]

按成绩从高到低排序:

# -*- coding: utf-8 -*-
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
def by_score(t):
    return -t[1]

L2 = sorted(L, key=by_score)
print(L2)

输出结果:

[('Adam', 92), ('Lisa', 88), ('Bob', 75), ('Bart', 66)]
sorted(L, key=lambda x: str.lower(x[0]))
# -*- coding: utf-8 -*-
L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
def by_name(t):
    return t[0].lower()
def by_score(t):
    return -t[1].lower()

小记

1.常用的函数。abs( )取绝对值,max( )取最大值,int( )把其他数据类型转化为整数,hex( )把整数转化为十六进制。

2.在Python交互环境中定义函数时,注意Python会出现…的提示。函数定义结束后需要按两次回车才能重新回到>>>提示符下。

3.pass语句可以用来作为占位符,如果有一定要写但没想好怎么写的内容时,可以先写pass,确保其他语句正常运行。比如:

num = 0
if num < 100:
    pass

这样语句并不会出错。
补充几个函数的用法:
‘字符串’.lower( )函数可以把字符串变成小写。
s.index(‘x’) 返回字符串中字符’x’的索引值;
s.strip( ) 函数去除字符串首尾的空格或其他指定字符;
s[::-1] 把字符串s从尾到头顺序重新排列。


 /) /)
ฅ(• - •)ฅ ~ ~ ฅ’ω’ฅ

你可能感兴趣的:(Python,python)