关于python:Python函数式编程系列010惰性列表之动手实现List

34次阅读

共计 2724 个字符,预计需要花费 7 分钟才能阅读完成。

这篇文章,咱们要入手实现一个 List,不过和个别的文章不同,咱们这里不必类来实现,而是用根本的数据结构,二元元组(a, b) 和空元组 () 来实现。这两个都能够通过 lambda 间接定义进去,具体方法能够参考上一篇的内容。

咱们考虑一下,List(也叫链表),最要害的是创立一个模式,能够无穷开展本人,保留一个值和下一个数据的,例如 [1, 2, 3, 4] 咱们能够用 (1, (2, (3, (4, ()))))。咱们必须指定一个结尾,这个就是() 在其中的作用,()同时代表空列表和列表结尾的含意。很容易地,咱们能够将列表定义如下(我这里包了个函数,只是为了将数据隔离,避免咱们应用自带的比拟来实现一些性能):

def cons(head, tail):
    def helper():
        return (head, tail)
    return helper

而后咱们定义两个函数,来获取外面的数据,相似上一篇接口中的firstsecond

head = lambda cons_list: cons_list()[0]
tail = lambda cons_list: cons_list()[1]

咱们能够定义一个函数示意空的变量 empty_list_base,这之后,为了不便计算,咱们能够写一个生成cons 的的不便的办法(当然这个实现用了 *arg 的概念,咱们默认应用这个语法糖个性):

def cons_apply(*args):
    if len(args) == 0:
        return empty_list_base
    else:
        return cons(args[0], cons_apply(*args[1:]))

这样咱们就能够很不便地实现新建 List 了:

>>> cons_apply(1, 2, 3) # 返回 cons(1, cos(2, cos(3, ())))

为了不便比拟,咱们也能够定义一个判断列表是否相等的函数:

def equal_cons(this: ListBase[S], that: ) -> bool:
    if this == empty_list_base and that != empty_list_base:
        return False
    elif this != empty_list_base and that == empty_list_base:
        return False
    elif this == empty_list_base and that == empty_list_base:
        return True
    else:
        return head(this) == head(that) and equal_cons(tail(this), tail(that))

当初咱们就能够很不便地做一些验证了。

>>> assert equal_cons(cons_apply(1, 2, 3), cons(1, cons(2, cons(3, ()))))

当初咱们须要就是要实现一些不须要循环实现的列表运算,就是上一篇说的 mapfold_leftfilter

map的作用是将函数 f 带入到列表的每一个值,即咱们带入 f 到列表的头之后,再把 map 利用到 tail 中,即:

def map_cons(f, cons_list):
    if cons_list == ():
        return empty_list_base
    else:
        return cons(f(head(cons_list)), map_cons(f, tail(cons_list)))

同理,咱们能够实现 filterfold_left:

def filter_cons(f, cons_list):
    if cons_list == ():
        return empty_list_base
    else:
        hd, tl = head(cons_list), tail(cons_list)
        if f(hd):
            return cons(hd, filter_cons(f, tl))
        else:
            return tl

def fold_left_cons(f, init, cons_list):
    if cons_list == ():
        return init
    else:
        return fold_left_cons(f, f(init, head(cons_list)), tail(cons_list))

这样,咱们就能够实现一些基本功能了,比方将 [1, 2, 3, 4, 5] 每个元素加一,筛选偶数求和,就能够写成:

>>> res = fold_left_cons(lambda x, y: x + y, 0,
>>>    filter_cons(lambda x: x % 2 == 0, 
>>>        map_cons(lambda x: x + 1,
>>>            cons_apply(1, 2, 3, 4, 5)
>>>    )))
>>> res == 12

当然,这种格调的代码,嵌套的可读性很差,这里咱们就想到了之前咱们实现的 and_thencompose函数,能够组合这些水管结构的货色。不过,咱们将这些函数改成科里化会更不便的写。这样就能够用函数组合的格调了:

map_cons_curry = lambda f: lambda cons_list: map_cons(f, cons_list)
filter_cons_curry = lambda f: lambda cons_list: filter_cons(f, cons_list)
fold_left_cons_curry = lambda f: lambda init: lambda cons_list: fold_left_cons(f, init, cons_list)

具体的调用就是上面的办法了:

>>> f = and_then(>>>    map_cons_curry(lambda x: x + 1),
>>>    filter_cons_curry(lambda x: x % 2 == 0),
>>>    fold_left_cons_curry(lambda x, y: x + y)(0),
>>> )
>>>
>>> assert f(cons_apply(1, 2, 3, 4, 5)) == 12

如果你应用了我保护的这个 fppy(点击这里进入)的例子的话,你也能够应用一个F_ 的润饰器轮子,这样就能够实现另一种基于类的链式写法:

from fppy.base import F_, I

F_(I)\
    .and_then(map_cons_curry(lambda x: x + 1))\
    .and_then(filter_cons_curry(lambda x: x % 2 == 0))\
    .and_then(fold_left_cons_curry(lambda x, y: x + y)(0))\
    .apply(cons_apply(1, 2, 3, 4, 5)) # 返回 12

这篇之中,咱们简略仅用二元元组、相等、函数的概念,保护了一个列表的后果,并能通过一些列表函数对齐进行遍历计算、筛选。下一篇之中,咱们讲开始粗略地探讨类、类型这些概念,这将不便咱们当前的探讨。

正文完
 0