红魔咖啡馆

头发越掉越多,头发越掉越少

0%

Efficiency

Memorization

使用记忆化优化斐波那契数组
memo函数用来记录已经计算过的数,当有相同计算需求时直接赋值
count函数记录调用函数次数(包括递归)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def fib(n):
    if n==0 or n==1:
        return n
    else:
        return fib(n-2)+fib(n-1)
def count(f):
    def counted(n):
        counted.call_count +=1
        return f(n)
    counted.call_count = 0
    return counted
def memo(f):
    cache={}
    def memoized(n):
        if n not in cache:
            cache[n] = f(n)
        return cache[n]
    return memoized
fib = count(fib)
counted_fib =fib
fib = memo(fib)
fib = count(fib)
print(fib(30))
print("origin:", fib.call_count, "memorized:", counted_fib.call_count)
832040
origin: 59 memorized: 31

Exponentiation

优化指数运算至\(o(\log n)\)
以下显示了进行优化后的算法在不同数据量下的运行时间图,呈现对数曲线趋势

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
%matplotlib inline
import matplotlib.pyplot as plt
plt.style.use('ggplot')
plt.rc('font', size = 16)

from timeit import repeat
from numpy import median, percentile

def plot_times(name, xs, n=15):
    f = lambda x: name+'('+str(x)+')'
    g = globals()

    samples = []
    for _ in range(n):
        times = lambda x: repeat(f(x), globals = g, number = 1, repeat = n)
        samples.append([median(times(x)) for x in xs])
    ys = [10e3*median(sample) for sample in zip(*samples)]

    plt.figure(figsize=(8, 8))
    plt.plot(xs, ys)
    plt.xlabel('n')
    plt.ylabel('ms')
1
2
3
4
5
6
7
8
9
10
def exp_fast(b,n):
    if n==0:
        return 1
    elif n%2==0:
        return square(exp_fast(b,n//2))
    else:
        return b*exp_fast(b, n-1)

def square(x):
    return x*x
1
2
exp_2_fast = lambda n: exp_fast(2.0, n)
plot_times('exp_2_fast', range(20, 1600, 10))
output_5_0

Order of Growth Notation

时间复杂度常用大O与大\(\Theta\)表示法表示
- 大O描述了运行时间上限 - 大\(\Theta\)则对同时表示了上限与下限 常见的时间复杂度表示(大O与大\(\Theta\)自行替换符号): - 指数增长:\(o(b^n)\) - 二次增长:\(o(b^2)\) - 线性增长:\(o(n)\) - 对数增长:\(o(\log n)\) - 常数增长:\(o(1)\)

Space

Active environment: - 正在被调用的函数的环境 - 函数的父函数在活动环境中(即嵌套函数内层调用时) python会自动检测非活动环境并将其回收

Composition

Linked List

用Python实现链表 - 每个节点由firstrest组成,前者表示值,后者表示链接的剩余链表 - 将每个节点当作一堆二元组看待 - 最后一个节点指向空链表Link.empty,使用类属性表示 - 构造方式:Link(3, Link(4, Link(5, Link.empty))) ### 创建链表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Link:
    # 使用空元组实现空节点,类属性
    empty = ()
    def __init__(self, first, rest=empty):
        # 判断rest是否是空节点或是一个Link类
        assert rest is Link.empty or isinstance(rest, Link)
        self.first = first
        self.rest = rest


s = Link(3, Link(4, Link(5)))
print(s.first)
print(s.rest.first)
print(s.rest.rest.first)
print(s.rest.rest.rest is Link.empty)

操作列表

实现如下行为:range, map, filter 我们使用递归实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Link:
    # 使用空元组实现空节点,类属性
    empty = ()
    def __init__(self, first, rest=empty):
        # 判断rest是否是空节点或是一个Link类
        assert rest is Link.empty or isinstance(rest, Link)
        self.first = first
        self.rest = rest
def range_link(start, end):
    if start>=end:
        return Link.empty
    else:
        return Link(start, range_link(start+1, end))

def map_link(f, s):
    if s is Link.empty:
        return s
    else:
        return Link(f(s.first), map_link(f, s.rest))

def filter_link(f, s):
    if s is Link.empty:
        return s
    filter_rest = filter(f, s.rest)
    if f(s.first):
        return Link(s.first, filter_rest)
    else:
        return filter_rest

链表的改变

可以通过对属性赋值来改变链表的firstrest属性

1
2
3
4
5
6
7
8
>>> s = Link(1, Link(2, Link(3)))
>>> s.first = 5
>>> t = s.rest
>>> t.rest = s;
>>> s.first
5
>>> s.rest.rest.rest.rest.rest.first
2

此赋值将s的后段与前段相接,创建了一个循环链表

插入节点

向一个有序链表中添加节点,保证链表仍有序

1
2
3
4
5
6
7
8
9
def add(s, v):
    assert s is not Link.empty
    if s.first > v:
        s.first, s.rest = v, Link(s.first, s.rest)
    elif s.first < v and s.rest is Link.empty:
        s.rest = Link(v)
    elif s.first < v:
        add(s.rest, v)
    return s

Tree Class

使用类实现树 ### 创建树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Tree:
    def __init__(self, label, branches = []):
        self.label = label
        for branch in branches:
            assert isinstance(branch, Tree)
        self.branches = list(branches)
    def __repr__(self):
        if self.branches:
            branches_str = ', '+ repr(self.branches)
        else:
            branch_str = ''
        return 'Tree({0}{1})'.format(repr(self.label), branch_str)
    def __str__(self):
        return '\n'.join(self.indented())

    def indented(self):
        lines = []
        for b in self.branches:
            for line in b.indented():
                lines.append('    '+line)
        return [str(self.label)] + lines
    def is_leaf(self):
        return not self.branches

实现一些操作

如下代码实现了创建斐波那契树以及输出叶子节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def fib_tree(n):
    if n==0 or n==1:
        return Tree(n)
    else:
        left = fib_tree(n-2)
        right = fib_tree(n-1)
        fib_n = left.label + right.label
        return Tree(fib_n, [left, right])

def leaves(t):
    if t.is_leaf():
        return [t.label]
    else:
        all_leaves = []
        for b in t.branches:
            all_leaves.extend(leaves(b))
        return all_leaves

剪枝

1
2
3
4
5
# 通过每次重新构建branches列表,将等于n的值排除在列表外以实现
def prune(t, n):
    t.branches = [b for b in t.branches if b.label!=n]
    for b in t.branches:
        prune(b, n)

Representations

String Representations

字符串用来表达语言与程序
Python中,有两种字符串表达 - str:对人类可读 - repr:对解释器刻可读 这两种一般相同,但也有不同之处
### repr String for an Object repr函数返回一个python表达式(字符串),该表达式会评估为一个等同的对象
对一个对象的repr调用eval会给你一个与原始对象等价的对象
调用repr的结果与在python交互界面输出的结果相同

str String for an Object

str函数接受任何对象,返回一个字符串,字符串是原始对象的人类可以解释的表示
对表达式调用str的结果与调用print时输出的结果相同

F-strings

String Interpolation

字符串插值包括对一些含有表达式的字符串字面量求值 - 可以使用+进行字符串连接实现 'pi starts with'+str(pi)+'...' - 也可以使用f-string功能实现字符串插值 f'pi starts with {pi}' 花括号中的将会作为表达式看待并给出计算结果 f-string的计算结果包括str string和子表达式的值

Polymorphic Functions

多态函数是一种适用于多种数据类型的函数
如str和repr函数都是多态函数,它们接受任何类型的对象
原理:repr调用了一个零参数方法__repr__以便在参数上获取他返回的repr字符串
str同样调用一个 零参数方法__str__
对于repr函数:

  • repr函数内部只调用了一个名为repr的类属性,而实例属性__repr__被忽略了
  • 实现如下:
1
2
def repr(x):
    return type(x).__repr__(x)

对于str函数: - 实例属性__str__被忽略了 - 若类上根本没有__str__属性,则调用str只会返回repr返回的内容

Interface

对象传递信息的方式:通过互相查找属性或方法
属性的查找规则允许不同数据类型通过具有相同属性名称来响应相同信息

共享信息:在许多不同类上引起相似行为的属性名称
接口:一组共享信息与一些规范(表示其含义)

如实现__repr__与__str__的类实现了一个接口,一下是用类来展示该接口的代码

1
2
3
4
5
6
7
8
9
class Ratio:
    def __init__(self, n, d):
        self.numer = n
        self.denom = d

    def __repr__(self):
        return 'Ratio({0}, {1})'.format(self.numer, self.denom)
    def __str__(self):
        return '{0}/{1}'.format(self.numer, self.denom)

当我们令half = Ratio(1,2),直接打印half,输出为可读版本,而直接在解释器展示half,就会输出python表达式

Special Method Names

python中,以两个下划线包围的名字表示它们具有某种内置行为 - __init__:构造对象时会自动调用的方法 - __repr__:将对象以python表达式的方式输出,在交互式python界面时用于显示值的方法 - __add__:用于将一个对象加入另一个对象 - __bool__:用于将一个对象转换为布尔值 - __float__:用于将一个对象转换为实数 同时,以下代码实现了将两个对象相加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Ratio:
    def __init__(self, n, d):
        self.numer = n
        self.denom = d

    def __repr__(self):
        return 'Ratio({0}, {1})'.format(self.numer, self.denom)
    def __str__(self):
        return '{0}/{1}'.format(self.numer, self.denom)
    def __add__(self, other):
        if isinstance(other, int):
            n = self.numer+self.denom*other
            d = self.denom
        elif isinstance(other, Ratio):
            n = self.numer*other.denom +self.denom*other.numer
            d = self.denom*other.denom
        elif isinstance(other, float):
            return float(self)+other
        g = gcd(n,d)
        return Ratio(n//g, d//g)
    __radd__ = __add__

    def __float__(self):
        return self.numer/self.denom

def gcd(n, d):
    while n!=d:
        n, d = min(n, d), abs(n-d)
    return n

Inheritance

继承是将多个类关联起来的一种方法 当两个类相似时,可以使用继承,相似的类可能具有与通用类相同的所有属性,外加自带的特殊属性 语法:

1
2
class <name>(<base class>):
    <suite>
通过该语句创建的子类与其基类共享所有属性,子类可能会覆盖某些继承的属性,但未更改的保持不变 当编写子类是,只需要指出其与基类不同之处即可 ## 例 创建一个支票账户(CheckingAccount)类,作为账户(Account)类的子类

1
2
3
4
5
6
7
8
9
10
11
12
13
# 类的继承
class CheckingAccount(Account):
    """A bank account that charges for withdrawals"""
    withdraw_fee = 1
    interest = 0.01
    def withdraw(self, amount):
        return Account.withdraw(self, amount + self.withdraw_fee)

ch = CheckingAccount('Tom')
print(ch.interest)
ch.deposit(20)
ch.withdraw(5)
print(ch.balance)

此处我们在Account类的基础上修改了interest属性与withdraw方法,而其余属性与方法可以继续使用

在类上寻找属性名称

基类属性不会复制到子类中,确保了未经修改的属性与基类保持一致,在类中寻找名称遵循如下规则 - 如果名称为类中的属性,则返回属性值 - 若不在,在其基类中寻找该名称

面向对象设计

在进行面向对象编程时,建议遵循如下原则 - 不要重复复制粘贴已经存在的,而是利用已经存在的实现 - 已被覆盖的属性仍需要通过类来访问

如以上代码,在计算提款时最佳方式是查找实例本身上的withdraw_fee 这样,如果当前实例有一个特定的withdraw_fee,我们就使用它,否则使用Account类中的

继承与组合

继承最适合is-a关系,如支票账户是(is a)账户的特定类型,故适合使用继承 组合最适合has-a关系,如一个银行具有(has a)有一组他管理的账户,该组账户是它的一个属性,而不会继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Bank:
    """a bank has accounts"""
    def __init__(self):
     self.accounts=[]   # 账户列表

    def open_account(self, holder, amount, kind = Account):
        account = kind(holder)   # 创建某种类型的账户
        account.deposit(amount)   # 存入amount的钱
        self.accounts.append(account)   # 将创建的账户加入当前银行的账户列表
        return account

    def pay_interest(self):
        # 计算列表中的利息
        for a in self.accounts:
            a.deposit(a.balance*a.interest)

bank = Bank()
john = bank.open_account('john', 10)
jack = bank.open_account('jack', 5, CheckingAccount)
bank.pay_interest()
print(jack.balance)
print(john.balance)

多重继承

一个子类可以有多个基类 其中,这里直接调用父类account_holder时,使用super()实现了超类调用,防止破坏多重继承的调用逻辑

1
2
3
4
5
6
7
8
9
10
# 类的多重继承
class SavingsAccount(Account):
    deposit_fee = 2
    def deposit(self, amount):
        return Account.deposit(self, amount-self.deposit_fee)
class GoodAccount(CheckingAccount, SavingsAccount):
    def __init__(self, account_holder):
        super().__init__(account_holder)
        self.holder = account_holder
        self.balance = 1

Decomposition

Modular Design

程序的设计原则:将程序的不同部分分离开来,使得每个模块可以独立开发与测试 以以下餐厅搜索代码为例,实现从文件中读取评分与评论(使用similarity评估相似性)搜索对应评分前k个餐厅去过的其他餐厅的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
def search(query, ranking = lambda r: -r.stars):
    results  =[r for r in Restaurant.all if query in r.name]
    return sorted(results, key= ranking)
def reviewed_both(r, s):
    return len([x for x in r.reviewers if x in s.reviewers])
class Restaurant:
    all = []
    def __init__(self, name, star, reviewer):
        self.name = name
        self.star = star
        self.reviewer = reviewer
        Restaurant.all.append(self)

    def similar(self, k, similarity=reviewed_both):
        """Return the k most similar restaurants to self"""
        others = Restaurant.all
        others.remove(self)
        different = lambda r: -similarity(r, self)
        return sorted(others, key=different)[:k]


    def __repr__(self):
        return '<'+self.name+'>'

import json
reviewers_for_restaurant = {}
for line in open('reviews.json'):
    r = json.loads(line)
    biz = r['business_id']
    if biz not in reviewers_for_restaurant:
        reviewers_for_restaurant[biz] = [r['user_id']]
    else:
        reviewers_for_restaurant[biz].append(r['user_id'])
for line in open('restaurant.json'):
    r = json.loads(line)
    reviewers = reviewers_for_restaurant[r['business_id']]
    Restaurant(r['name'], r['stars'], reviewers)
results = search('Thai')
for r in results:
    print(r,'share reviewer with', r.similar(3))

优化时间复杂度

1
2
3
4
5
6
7
8
9
10
11
12
13
def reviewed_both(r, s):
    return fast_overlap(r.reviewers, s.reviewers)

def fast_overlap(s, t):
    count, i, j = 0, 0, 0
    while i<len(s) and j<len(t):
        if s[i]==t[j]:
            count, i, j = count+1, i+1, j+1;
        elif s[i]<t[i]:
            i+=1
        else:
            j+=1
    return count