函数式编程

来自廖雪峰Python教程后的评论,做一个备份

这里的讨论仅仅涉及将函数作为参数,不涉及函数作为返回值的情况。

使用大家之前讨论的一个例子:

求1到100的和

def sum1(start, end):
  total = 0
  for x in range(start, end + 1):
    total += x
  return total

求1到100每项平方加1的和

def sum2(start, end):
  total = 0
  for x in range(start, end + 1):
    total += x * x + 1
  return total

观察上面的sum1函数和sum2函数,发现非常的相似,都是迭代地加某一个数,然后保存在变量total里,等到函数执行完成后返回该值。唯一不同的地方就是针对被求和的每一项的处理不同:sum1函数中就是每一项x本身,而在sum2函数中,每一项x则是经过x * x + 1或者x ** 2 + 1这样的操作后再参与相加的,而这样的操作可能是多种多样的,比如我要求1到100每个数的立方和,求1到100之间所有素数的和等等,而这个操作应该是独立于我们求和这个概念的:不管我们把每一项变成什么鬼样子,最终的目的就是要把他们全部累积起来。

所以我们可以把针对每一项的具体操作定义为函数,然后将其作为参数传入求和函数sum中

def sum(start, end, handle):
  total = 0
  for x in range(start, end + 1):
    total += handle(x)
  return total

其中的handle几句是处理每一项的具体函数,要计算什么素的和,就直接定义处理该数的handle函数即可

现在我们重新来求解上面两个问题:

求1到100的和

# 定义处理的handle函数
# 函数名你可以随便取名
def identity(n):
  return n

print(sum(1, 100, identity))

求1到100每项平方加1的和

def square_plus_one(n):
  return n ** 2 + 1


print(sum(1, 100, square_plus_one))

如果我们需要计算1到100的立方的和,同理可得

def cube(n):
  return n ** 3

print(sum(1, 100, cube))

如果我们需要计算1到100内所有素数的和呢,也是一样的,只是可能我们的handle函数有些复杂而已

# 处理函数is_prime用于判断一个正整数是否为素数
# 如果是素数的话,则返回该数
# 否则则返回0
def is_prime(n):

  def smallest_divisor(n):
    return find_divisor(n, 2)

  def find_divisor(n, test_divisor):
    if pow(test_divisor, 2) > n:
      return n
    elif n % test_divisor == 0:
      return test_divisor
    else:
      return find_divisor(n, test_divisor + 1)

  if 1 == n:
    retrun 0
  elif smallest_divisor(n) == n:
    return n
  else:
    return 0


print(sum(1, 100, is_prime))

其实,如果我们的脑洞在大一点,或者说抽象思维在强一点,就不难发现,其实阶乘和求和其实是类似的概念,区别仅仅在于:

  1. 将加号更换为乘号
  2. 接收累积的值的变量total的初始值为0变成了1

那么按照上面的sum求和函数,我们可以很容易的定义求积函数product

def product(start, end, handle):
  total = 1
  for x in range(start, end + 1):
    total *= handle(x)
  return total

使用上面定义的product函数,以及上面我们定义的identity函数:

def identity(n):
  return n

我们很容易实现阶乘函数factroial函数:

def factroial(n):
  return product(1, n, identity)

同样的,如果把上面另外两种求和的情况改为求积的情况,也是非常容易的:仅需要将sum函数更换为product函数即可,其他的都不用改变!

其实,如果我们脑洞再够大一点的话,还能抽象出求和和求积表达的其实是一种更一般的概念:累积,简单的理解就是把一堆东西不断堆在一起,越变越大,不断膨胀的一种情况,求和和求积仅仅是两种特殊的累积方式罢了。

如果有了这样的想法,那我们其实是可以将求和和求积抽象为更高级更一般的概念:累积

老方法,我们还是通过观察比较sum函数和product函数的相同点和不同点,看看能得到啥:

sum函数

def sum(start, end, handle):
  total = 0
  for x in range(start, end + 1):
    total += handle(x)
  return total

product函数

def product(start, end, handle):
  total = 1
  for x in range(start, end + 1):
    total *= handle(x)
  return total

通过观察比较,其实也不难发现大部分地方都是相同的,不同的地方也就两个地方:

  1. 变量total的初始值不同
  2. 累积的方式不同,一个是用加号进行累加,另一个是用乘号进行累乘

然后呢,还是套路,就像上面我们在抽象1到100的和与1到100的平方加1的和来定义sum函数一样,将不同之处定义为函数,然后将这些函数作为参数传入即可。

因此我们来定义accmuluate函数来表达累积这个概念:

def accumulate(start, end, handle, init_value, combine):

  def symbol(a, b, combine):
   if '+' == combine:
     return a + b
   elif '*' == combine:
     return a * b
   else:
     pass  # 错误处理:

  total = init_value
  for x in range(start, end + 1):
    total = symbol(total, handle(x), combine)
  return total

有了这样一个更一般的函数,那我们来重新定义sum函数和product函数就轻松了许多:

def sum(start, end, handle):
  return accumulate(start, end, handle, 0, '+')


def product(start, end, handle):
  return accumulate(start, end, handle, 1, '*')

最后将完整的代码贴在这里,并计算1到100的和以及1到6的乘积:

#! /usr/bin/env python3
# -*- coding: utf-8 -*-

def accumulate(start, end, handle, init_value, combine):

  def symbol(a, b, combine):
    if '+' == combine:
      return a + b
    elif '*' == combine:
      return a * b
    else:
      pass  # 错误处理

  total = init_value
  for x in range(start, end + 1):
    total = symbol(total, handle(x), combine)
  return total

def sum(start, end, handle):
  return accumulate(start, end, handle, 0, '+')

def product(start, end, handle):
  return accumulate(start, end, handle, 1, '*')

def identity(n):
  return n

print(sum(1, 100, identity))
print(product(1, 6, identity))

运行结果:

root@kali:~/py3venv/scripts# python accumulate.py
5050
720

这里仅仅是我学习函数式编程时的一点点思考,参考的资料是SICP第一章第三节第一小节的内容,有兴趣的同学可以看看,但是Python可能有更简单更强大的方法来处理此类问题,但是编程思想的学习和探究应该是极其重要的。

简化

通过使用Python强大的map/reduce函数,上述代码将会大大的简化,这里只是定义Sum函数,Product函数以及他们的更一般抽象的Accumulate函数

# 为了不与Python提供的sum内置函数混淆
# 将我们自己定义的求和函数命名为Sum

def Sum(start, end, handle):
  return reduce(lambda x, y: x + y, \
                map(lambda x: handle(x), \
                    [x for x in range(start, end + 1)]))
def Product(start, end, handle):
  return reduce(lambda x, y: x * y, \
                map(lambda x: handle(x), \
                    [x for x in range(start, end + 1)]))

从上述Sum函数与Product函数我们可以抽象出Accumulate函数:

def Accumulate(start, end, handle, combine):

  def symbol(a, b, combine):
   if '+' == combine:
     return a + b
   elif '*' == combine:
     return a * b
   else:
     pass

  return reduce(lambda x, y: symbol(x, y, combine), \
                map(lambda x: handle(x), \
                   [x for x in range(start, end + 1)]))

而通过Accumulate函数,我们又能容易地定义Sum函数和Product函数:

def Sum(start, end, handle):
  return accumulate(start, end, handle, '+')
def Product(start, end, handle):
  return accumulate(start, end, handle, '*')

最后我们来测试一下计算1到100的和以及1到6的乘积来验证上述函数是否正确

# 定义handle函数
def identity(n):
  return n

测试结果:

>>> print(Sum(1, 100, identity))
5050
>>> print(Product(1, 6, identity))
720