Textbook (SICP for python)
It’s adapted from the legendary book ----《Structure and Interpretation of Computer Programs (SICP)》《计算机程序的构造和解释》
Chapter 1
Creating and using pure functions.
Chapter 2
A range of -pure functions and their use.
Chapter 3
How does the conceptual framework of environments, names, and functions implementing a working interpreter for a programming language.
Chapter 4
Pure functions are essential for writing concurrent programs, in which multiple call expressions may be evaluated simultaneously.
1 Building Abstractions with Functions
We concentrated in Chapter 1 on computational processes and on the role of functions in program design.
1.1 Getting Started
1.1.3 Interactive Sessions
If you see Python prompt, >>>
, then you have successfully started an interactive session.
1.1.4 First Example
Python has built-in support for a wide range of common programming activities. The line of Python code
>>> from urllib,requesr import urlopen
is an import statement that loads functionality for accessing data on the Internet.
Statements & Expressions. Broadly, computer programs consist of instructions to either 1. Carry out some action 2. Compute some value
Functions. Functions encapsulate logic that manipulates data.
Objects. An object seamlessly bundles together data and the logic that manipulates that data, in a way that manages the complexity of both.
Interpreters. A program that evaluating compound expressions, is called an interpreter.
1.1.5 Errors
Learning to interpret error and diagnose the cause of unexpected errors is called debugging. Some building principles of debugging are:
1.Test incrementally: Try out everything you write as soon as possible to identify problems early and gain confidence in your components.
2.Isolate errors: When trying to diagnose a problem, trace the error to the smallest fragment of code you can before trying to trying to correct it.
3.Check your assumptions: Know your assumptions, then focus your debugging effort on verifying that your assumptions actually hold.
4.Consult others: A lot of valuable programming knowledge is shared in the process of group problem solving.
examples
TypeError: ‘function’ object is not subscriptable.
# HW04 Q1
def planet(size)
assert size > 0
return['planet', size]
def size(w)
assert is_planet(w), 'must call size on a planet'
# return planet[1] # TypeError
return w[1]
1.2 Elements of Programming
Every powerful language has three such mechanisms: primitive expressions and statements, meas of combination and means of abstraction.
1.2.1 Expressions
>>> -1 - -1
0
1.2.2 Call Expressions
The most important kind of compound expression is a call expression, which applies a function to some arguments.
>>> max(7.5, 9.5)
9.5
1.2.3 Importing Library Functions
>>> from math import sqrt
>>> sqrt(256)
16.0
1.2.4 Names and the Environment
A critical aspect of a programming language is the means it provides for using names to refer to computational objects. Names can also be bound to functions.
>>> max
<built-in function max>
>>> f = max
>>> f(2, 3, 4)
4
In Python, names are often called variable names or variables.
The possibility of binding names to values and later retrieving those values by names means that the interpreter must maintain some sort of memory that keeps track of the names, values, and bindings. This memory is called an environment.
1.2.5 Evaluating Nested Expressions 嵌套
1.2.6 The Non-Pure Print Function
Pure function. 纯函数 Functions have some input (their arguments) and return some output (the result of applying them). 给定参数返回对应值
The return
statement ill give the result of some computation back to the function and exit the function. If Python reaches the end of the function body without executing a return statement, it will atomatically return None
. Calling a function in the Python interpreter will print out the function’s return value.
>>> return 'big'
'big'
Non-pure function. 非纯函数
Applying a non-pure function can generate side effects, which make some change to the state of the interpreter or computer. 引起副作用,修改程序状态(改变全局变量)
The print
function is used to display values in the terminal. Unlike a return
statement, when Python evaluates a print
expression, the function does not terminate immediately.
>>> print('huge')
huge
>>> print("nothing")
nothing
>>> negative = -12
>>> print(negtive)
-12
A common side effect is to generate additional output beyond the return value, using the print function. 一个常见的副作用是通过print函数输出超过返回值的额外输出
Be careful with print! The fact that it returns None means that it should not be the expression in an assignment statement. print返回None意味着它不应作为赋值语句中的表达式
The print
function is only used so that we can see the intermediate results of computations. print仅用于展示中间值
>>> print(print(2), print(3))
2
3
None None
1.3 Defining New Functions
def <name>(<formal parameters>):
return <return expression>
1.3.1 Enviroments
An environment in which an expression is evaluated consists of a sequence of frames, depicted as boxes. Each frame contains bindings, each of which associates a name with its corresponding value. 环境由一些列frame组成,每个frame包含绑定。There is a single global frame. 只有一个global frame。
The name appearing in the function is called the intrinsic name 固有名称. The name in a frame is a bound name 绑定名称.
f = max
max = 3
result = f(2, 3, 4)
max(1, 2) # Causes an error
# TypeError: 'int' object is not callable.
The name max (currently bound to the number 3) is an integer and not a function. Therefore, it cannot be used as the operator in a call expression.
Function Signatures. A description of the formal parameters of a function is called the function’s signature.
1.3.2 Calling User-Defined Functions
Name Evaluation A name evaluates to the value bound to that name in the earliest frame of the current environment in which that name is found.
1.3.4 Local Names
We say that the scope of a local name is limited to the body of the user-defined function that defines it.
1.3.5 Choosing Names
1.Function names are lowercase, with words separated by underscores. Descriptive names are encouraged.
2.Single letter parameter names are acceptable when their role is obvious, but avoid “l”(lowercase ell), “O”(capital oh), or “I”(capital i) to avoid confusion with numerals.
1.3.6 Functions as Abstractions
Aspects of a functional abstraction. The domain of a function is the set of arguments it can take. The range of a function is the set of values it can return, The intent of a function is the relationship it computes between inputs and output (as well as any side effects it might generate).
1.3.7 Operators
Python expressions with infos operators each have their own evaluation procedures, but you can often think of them as short-hand for call expressions. When you see
>>> 2 + 3 * 4 + 5
19
simply consider it to be short-hand for
>>> add(add(2, mul(3, 4), 5)
19
When it comes to division, Python provides two infix operators: /
and //
. The former is normal division, so that it results in a floating point, or decimal value, even if the divisor evenly divides the dividend. The //
operator, on the other hand, rounds the result down to an integer 将结果向下舍入为整数:
True Division: /
(decimal division)
Floor Division://
(integer division)
Modulo: %
(remainder)
>>> 5 // 4
1
>>> -5 // 4
-2
These two operators are shorthand for the truediv
and floordiv
functions.
>>> from operator import truediv, floordiv
>>> truediv (5, 4)
1.25
>>> floordiv(5, 4)
1
运算 | 语法(python内置运算符) | operator模块函数 |
---|---|---|
左移 | a << b 运算数a的各二进位数左移b位,高位丢弃,低位补0 | operator.lshift(a, b)返回a左移b位的结果 |
右移 | a >> b 运算数a的各二进位数右移b位, | operator.rshift(a,b)返回a右移b位的结果 |
1.4 Designing Functions
1.4.1 Documents
When you call help
with the name of a function as an argument, you see its docstring (type q
to quit Python help).
>>> help(pressure)
"""docstring
"""
Remember, code is written only once, but often read many times.
Comments. Comments in Python can be attached to the end of a line following the #
symbol.
1.4.2 Default Argument Values
1.5 Control
1.5.1 Statements
Rather than being evaluated, statements are executed. Each statement describes some change to the interpreter state, and executing a statement applies that change.语句会对解释器做状态一些更改 We have seen three kinds of statements already: assignment, def
, and return
statements.
1.5.2 Compound Statements
Practical Guidance. When indenting a suite, all lines must be indented the same amount and in the same way (use spaces, not tabs). Any variation in indentation will cause an error.
1.5.3 Defining Function II: Local Assignment
Whenever a user-defined function is applied, the sequence of clauses in the suite of its definition is executed in a local environment – an environment starting with a local frame created by calling that functiond. 函数定义套件中的子句序列在本地环境中执行,该环境以调用该函数创建的本地框架开始。A return
statement redirects control: the process of function application terminates whenever the first return statement is executed, and the value of the return expression is the returned value of the function being applied. return语句重定向控制,每当执行第一个return语句时,函数应用程序的进程就会终止,而return表达式的值就是所应用函数的返回值。
1.5.4 Conditional Statements
Conditional statements. A conditional statement in Python consists of a series of headers and suites: a required if
clause, an optional sequence of elif
clause, and finally an option else
clause:
if <expression>:
<suite>
elif <expression>:
<suite>
else:
<suite>
Boolean contexts. The expressions inside the header statements of conditional blocks are said to be in boolean contexts.
Boolean values. The built-in comparison operations, >
, <
, >=
, <=
, ==
, !=
, return these values.
Boolean operators. and
, or
and not
.
Truthy and Falsey Values: It turns ot and
nd or
work on more than just booleans (True
, False
). Python values such as 0
, None
, ''
(the empty string), and []
(the empty list) are consider false values. All other values are considered true values.
A feature called short-circuiting that the truth value of a logical expression can sometimes be determined without evaluating all of tis subexpressions.
and
and or
do always return the last thing they evaluate, whether they short circuit or not. Keep in mind that and
and or
don’t always return booleans when using values other than True
and False
.
>>> True or 1 / 0
True
>>> True and 13
13
>>> not 10
False
>>> not None
True
>>> False and 1 / 0
ZeroDivisionError: division by zero
1.5.5 Iteration
Only through repeated execution of statements do we unlock the full potential of computers. We have already seen one form of repetition: a function can be applied many times, although it is only defined once. Iterative control structures are another mechanism for execution the same statements many times.
Consider the sequence of Fibonacci numbers, in which each number is the sum of the preceding two: 0, 1, 1, 2, 3, 5, 8, 13, 21, …
We can use a while
statement to enumerate n
Fibonacci numbers. We need to track how many values we’ve created (k
), along with the the kth value (curr
) and its predecessor (pred
). Step through this function and observe how the Fibonacci numbers evolve one by one, bound to cure
.
def fib(n):
"""Compute the nth Fibonacci number, for n>= 2."""
pred, curr = 0, 1 # Fibonacci numbers 1 and 2
k = 2 # Which Fib number is curr?
while k < n:
pred, curr = curr, pred + curr
k = k + 1
return curr
result = fib(8) # 13
Remember that commas seperate multiple names and values in an assignment statement. The line: pred, curr = curr, pred + curr
has the effect of rebinding the name pred to the value of curr, and simultanously rebinding curr to the value of pred + curr. All of the expressions to the right of = are evaluated before any rebinding takes place.
1.5.6 Testing
Assertions. Programmers use assert
statements to verify exceptions, such as the output of a function being tested. An assert
statement has an expression in a boolean contest, followed by a quoted line of text (single or quotes are both fine, but be consistent) that will be displayed if the expression evaluates to a false value. assert语句格式:assert 布尔表达式 “报错提示语句”
When the expression being asserted evaluates to a true value, executing an assert statement has no effect. When it is a false value, assert
causes an error that halts execution.
A test function for fib
should test serval arguments, including extreme values of n
.
>>> def fib_test():
assert fib(2) == 1, 'The 2nd Fibonacci number should be 1'
assert fib(3) == 1, 'The 3rd Fibonacci number should be 1`
assert fib(50) == 7778742049, 'Error at the 50th Fibonacci number`
When writing Python in files, rather than directly into the interpreter, tests are typically written in the same file or a neighboring file with the suffix _test.py
.
Doctests. Python provides a convenient method for placing simple tests directly in the doctoring of a function. The first line of a doctoring should contain a one-line description of the function, followed by a blank line. A detailed description of arguments and behavior may follow. In addition, the doctoring may include a sample interactive session that calls the function:
>>> def sum_naturals(n):
"""Return the sum of the first n natural numbers.
>>> sum_naturals(10)
55
>>> sum_naturals(100)
5050
"""
total, k = 0, 1
while k <= n:
total, k = total + k, k + 1
return total
Then, the interaction can be verified via the dockets module. To verify the dockets interactions for only a single function, we use a doctest
function called run-docstring_examples
. Its first argument is the function to test. The second should always be the result of the expression globals()
, a built-in function that returns the global environment. The third argument is True
to indicate that we would like “verbose” output: a catalog of all tests run.
>>> from doctest import run_docstring_examples
>>> run_docstring_examples(sum_naturals, globals(), True)
Finding tests in NoName
Tring:
sum_naturals(10)
Expecting:
55
ok
Trying:
sum_naturals(100)
Expecting:
5050
ok
When writing Python in files, all doctests in a file can be run by starting Python with the dockets command line option:
python3 -m doctest <python_source_file>
The key to effective testing is to write (and run) tests immediately after implementing new functions. A test that applies a single function is called a unit test. Exhaustive unit testing is a hallmark of good program design.
临时
Note:未被调用的函数都在global fram里面
frame中name均为形参
【assert】
Debugging
Traceback Messages It prints out the chain of function calls that led up to the error, with the most recent function call at the bottom. You can follow this chain to figure out which function(s) caused the problem. 导致错误的函数调用链,最新调用的函数在最底部(通常为报错处)
Traceback (most recent call last):
File"<pyshell#29>", line 3, in <module> # File "<filename>", line <number>, in <function>
result = buggy(5) # the most recent function call: buggy(5)
File "<pyshell#29>". line 5 in buggy
return f + x # the next function call
TypeError: unsupported operand type(s) for +: 'function' and 'int'
The lines in the traceback appear to be paired together. The second line in the pair (It’s indented farther in than the first) displays the actual line of code that makes the next function call. This give you a quick look at what arguments were passed into the function, and what context the function as being used, etc.
Debugging Techniques
Using print
statements
1.6 Higher-Order Functions
1.6.1 Functions as Arguments
def sum_naturals(n):
total, k = 0, 1
while k <= n:
total, k = total + k, k + 1
return total
def sum_cubes(n):
total, k = 0, 1
while k <= n:
total, k = total + k*k*k, k+1
return total
def pi_sum(n):
total, k = 0, 1
while k <= n:
total, k = total + 8 / ((4*k-3) * (4*k-1)), k + 1
return total
When these functions are for the most part identical, differing only in name and the function of k
used to compute the tern to be added. We can generate each of th functions by filling in slots in the same template:
def <name>(n):
total, k = 0, 1
while k <= n:
total, k = total + <term>(k), k + 1
return total
We can do so readily in Python by taking the common template shown above and transforming the “slot” into formal parameters:
def summation(n, term): # Using exactly the same summation function that sum natural numbers
total, k = 0, 1
while k <= n:
total, k = total + term(k), k + 1
return total
def identity(x): # Using an identity function that returns its argument
return x
def sum_naturals(n)
return summation(n, identity)
1.6.2 Functions as General Methods
When a user-defined function is applied to some arguments, the formal parameters are bound to the values of those arguments (which may be functions) in a new local frame.
The following example which implements a general method for iterative improvement and uses it to compute the golden ratioo, is a number near 1.6. The golden ratio can be computed by repeatedly summing the inverse of any positive number with 1, and that it is one less than its square.
def improve(update, close, guess=1):
while not close(guess):
guess = update(guess)
return guess
def golden_update(guess):
return 1/guess + 1
def square_close_to_successor(guess):
return approx_eq(guess * guess, guess + 1)
def approx_eq(x, y, tolerance=1e-3): # 微小量xe-3,如1e-3=0.001
return abs(x - y) < tolerance
phi = improve(golden_update, square_close_to_successor)
For this test, no news is good new: improve_test
returns None
after its assert
statement is executed successfully.
from math import sqrt
phi = 1/2 +sqrt(5)/2
def improve_test():
approx_phi = improve(golden_update, square_close_to_successor)
assert approx_eq(phi, approx_phi), 'phi differs from from its approximation'
improve_test()
1.6.3 Defining Functions III: Nested Definitions 嵌套定义
def sqrt(a):
def sqrt_update(x):
return average(x, a/x)
def sqrt_close(x):
return approx_eq(x * x, a)
return improve(sqrt_update, sqrt_close)
Lexical scope. 词汇范围 This discipline of sharing names among nested definitions is called lexical scoping.
We require two extensions to our enviroment model to enable lexical scoping.
1.Each user-defined function has a parent enviroment: the enviroment in which it was defined.
2.When a user-defined function is called, its local frame extends its parent enviroment.
Extended Enviroments. An environment can consist of an arbitrarily long chain of frames, which always concludes with the global frame.
Locally defined functions are often called closures.闭包
1.6.4 Functions as Returned Values
An important feature of lexically scoped programming languages is that locally defined functions maintain their parent environment when they are returned.
def compose1(f, g):
def h(x):
return f(g(x))
return h
1.6.5 Example:Newton’s Method
HW01
Q5: If Function vs Statement
def if_function(condition, true_result, false_resule):
if condition:
return true_result
else:
return false_result
def with_if_statement():
"""
>>> result = with_if_statement()
47
>>> print(result)
None
"""
if cond():
return true_func()
else:
return false_func()
def with_if_function():
"""
>>> result = with_if_function()
42
47
>>> print(result)
None
"""
return if_function(cond(), true_func(), false_func())
def cond():
return False
def true_func():
print(42)
def false_func():
print(47)
If statement: When executing a conditional statement, each clause is considered in order. Evaluate the header’s expression. If it is a true value, execute the suite. Then, skip over all subsequent clauses in the conditional statement. If the else clause is reached, its suite is executed.
If function: This call expression has subexpressions: the operator is an expression that precedes parentheses, which enclose a comma-delimited list of operand expressions. The operator specifies a function. When this call expression is evaluates, we will call the operand true_func() and false_func().
usage: python3 ok [–help] [options]
1.6.6 Curring
We can use higher-order functions to convert a function that takes multiple arguments into a chain of functions that each take a single argument. This transformation is called currying.
柯里化:通过一系列函数将多个参数的函数转变成单个单数的函数(最初函数的第一个参数),如果固定某些参数,将得到由剩余参数组成的函数。
>>> def curried_pow(x):
def h(y):
return pow(x, y)
return h
>>> curried_pow(2)(3)
8
We can define a function curried_pow
, a curried version of the pow
function, such that curried_pow(x)(y)
is equivalent to pow(x, y)
.
Why currying? Some programming languages, such as Haskell, only allow functions that take a single argument. In more general languages such as Python, currying is useful when we require a function that takes in only a single argument.
1.6.7 Lambda Expressions
In Python, we can create function values on the fly using lambda expressions, which evaluate to unnamed functions. 使用lambda表达式动态创建函数值,该表达式计算未命名的函数
A lambda expression evaluates to a functioun that has a single return expression as its body. Assignment and control statements are not allowed. (We don’t need to associate intermediate values with a name.) We can understand the structure of a lambda expression by constructing a corresponding English sentence: A function that takes x and returns f(g(x)). lambda函数只接收单一参数
lambda x: f(g(x))
The result of a lambda expression is called a lambda function. It has no intrinsic name (and so Python prints <lambda> for the name), but otherwise it behaves like any other function.
>>> s = lambda x: x * x
>>> s
<function <lambda> at 0xf3f490>
>>> s(12)
144
1.6.8 Abstractions and First-Class Functions
Elements with the fewest restrictions are said to have first-class status.
1.6.9 Function Decorators 函数装饰器
Python provides special syntax to apply higher-order functions as part of executing a def
statement, called a decorator. The most common example is a trace.
>>> def trace(fn):
def wrapped(x):
print('->', fn, '(', x, ')')
return fn(x)
return wrapped
>>> @trace
def triple(x):
return 3 * x
>>> triple(12)
-> <function triple at 0x102a39848> (12)
36
The def
statement for triple
has an annotation, @trace
. The name triple
is not bound to this function. Instead, the name triple
is bound to the returned function value of calling trace
on the newly defined triple
function. In code, this decorator is equivalent to:
>>> def triple(x):
return 3 * x
>>> triple = trace(triple)
1.7 Recursive Functions 递归函数
A function is called recursive if the body of the function calls the function itself, either directly or indirectly.
1.7.1 The Anatomy of Recursive Functions 结构
The body begins with a base case, a conditional statement that defines the behavior of the function for the inputs that are simply return that argument.
iterative & recursive
The iterative function constructs the result from the base case of 1 to the final total. The recursive function, on the other hand, constructs the result directly from the final term, n, and the result of the simpler problem, f(n-1).
In general, iterative functions must maintain some local state that changes throughout the course of computation. On the other hand, recursive function is characterized by its single argument. The state of the computation is entirely contained within the structure of the environment, and binds n to different values in different frames rather than explicitly tracking k.
区别 | 迭代函数 | 递归函数 |
---|---|---|
构成 | 从基础用例1至最终总数 | 最终项n 和更简单的问题f(n-1) |
参数 | 必须保有整个计算过程中变化的局部状态k | 单一参数n ,计算状态完全包含在环境结构中,并且在不同的帧中将n联结不同的值,而不是显性追踪k |
For this reason, recursive functions can be easier to define correctly.
Consider a function fact to compute n factorial, where for example fact(4) computes 4!=4⋅3⋅2⋅1=24
A natural implementation using a while statement accumulates the total by multiplying together each positive integer up to n.
On the other hand, a recursive implementation of factorial can express fact(n) in terms of fact(n-1), a simpler problem. The base case of the recursion is the simplest form of the problem: fact(1) is 1.
If we must need a counter in a recursive function, we can definite a nested function of the counter, then call the function of the base case.
e.g.(Lab03 Q6):The ping-pong sequence counts up starting from 1 and is always either counting up or counting down. At element k, the direction switches if k is a multiple of 8 or contains the digit 8.
def num_eights(x):
if x // 10 == 0 and x % 10 != 8:
return 0
elif x // 10 == 0 and x % 10 == 8:
return 1
else:
return num_eights(x % 10) + num_eights(x // 10)
def pingpong(n):
def helper(i, value, direction):
if i == n:
return value
elif i % 8 == 0 or num_eights(i) > 0:
return helper(i + 1, value - direction, -direction)
else:
return helper(i + 1, value + direction, direction)
return helper(1, 1, 1)
1.7.2 Mutual Recursion
When a recursive procedure is divided among two functions that call each other, the functions are said be mutually recursive.
Mutually recursive functions can be turned into a single recursive function by breaking the abstraction boundary between the two functions.
1.7.3 Printing in Recursive Functions
# Lab03 Q2
def crust():
print("70km")
def mantle():
print("2900km")
def core():
print("5300km")
return mantle()
return core
return mantle
>>> drill = crust # 赋值
>>> drill = drill() # 调用drill,依次执行:打印70km,定义mantle且未被调用,返回mantle,即drill=mattle
70km
>>> drill = drill() # 调用drill,即mantle(),依次执行:打印2900km,定义core且未被调用,返回core,即drill=core
2900km # drill = core
>>> drill = drill() # 调用drill,即core(),依次执行:打印5300km,返回调用的mantle(),打印2900km,返回core,即drill=core()
5300km
2900km
>>> drill() # 调用core
5300km
2900km
e.g.(hw03 Q1):
Write a higher-order function composer that returns two functions, func
and func_adder
. func
is a one-argument function that applies all of the functions that have been composed so far to it. The functions are applied with the most recent function being applied first (see doctests and examples). func_adder
is used to add more functions to our composition, and when called on another function g, func_adder should return a new func
, and a new func_adder
.
If no parameters are passed into composer, the func returned is the identity function.
For example:
>>> add_one = lambda x: x + 1
>>> square = lambda x: x * x
>>> times_two = lambda x: x + x
>>> f1, func_adder = composer()
>>> f1(1)
1
>>> f2, func_adder = func_adder(add_one)
>>> f2(1)
2 # 1 + 1
>>> f3, func_adder = func_adder(square)
>>> f3(3)
10 # 1 + (3**2)
>>> f4, func_adder = func_adder(times_two)
>>> f4(3)
37 # 1 + ((2 * 3) **2)
Part:
def composer(func=lambda x: x):
def func_adder(g):
"*** YOUR CODE HERE ***"
return func, func_adder
Your func_adder
should return two arguments func
and func_adder
. What function do we know that returns func
and func_adder
? composer()
The functions are applied with the most recent function being applied first (see doctests and examples). For example:
f1 = identity
f2 = f1(add_one)
f3 = f2(square)
f4 = f3(times_two)
...
func = func(g)
So:
def composer(func=lambda x: x):
def func_adder(g):
"*** YOUR CODE HERE ***"
h = lambda x: func(g(x))
return composer(h)
return func, func_adder
1.7.4 Tree Recursion
A function with multiple recursive calls is said to be tree recursion because each call branches into multiple smaller calls.
Consider computing the sequence of Fibonacci numbers, in which each number is the sum of the preceding two.
def fib(n):
if n == 1:
return 0
elif n == 2:
return `
else:
return fib(n-2) + fib(n-1)
A iterative function of Fibonacci is below.(C 1.5.5)
def fib_iter(n):
k, pred, curr = 2, 0, 1
while k < n:
k, pred, curr = k + 1, pred + curr
return curr
Consider the relationship of between the recursive definitions of a tree recursive problem.
Example: Counting Partitions
The number of partitions of a positive integer n, using parts up to size m, is the number of ways in which n can be expressed as the sum of positive integeer parts up to m in increasing order.
e.g.:count_partitions(6, 4)
2 + 4 = 6
1+ 1 + 4 = 6
3 + 3 = 6
1 + 2 + 3 = 6
1 + 1 + 1 + 3 = 6
2 + 2 + 2 = 6
1 + 1 + 2 + 2 = 6
1 + 1 + 1 + 1 + 2 = 6
1 + 1 + 1 + 1 + 1 + 1 = 6
count_partitions(6, 4) = 9
Using parts up to size m means each part is always up to m but never bigger.
Thought path:
- Recursive decomposition: finding simpler instance of the problem.
- Explore two possibilities:
Use at least one m
Don’t use any m - Solve two simpler problems:
count_partition(n-m, m)
count_partition(n, m-1)
Tree recursion often involves exploring different chioces. So:
def count_partitions(n, m):
if n == 0:
return 1
elif m == 0:
return 0
else:
with_m = count_partitions(n - m, m)
without_m = count_partitions(n, m - 1)
return with_m + without_m
we don’t want a start from the largest coin. Another valid approach would be to start from the smallest coin.Since start from smallest, we won’t write a function to calculate the largest coin. Because we already know about what the smallest coin is the smallest coin one. So that makes things slightly easier that we don’t have to write a separate function.
We still need the helper function. Because if we were to use this given function, we only have access to one variable and which is the total variable and won’t be able to keep track essentially of the small coin. We split into the cases where we do include the smallest coin and we do not include the largest coin. So we want to keep track of the smallest coin as we go through all the calls
Q4: Count change
Given a positive integer total, a set of coins makes change for total if the sum of the values of the coins is total. For example, the following sets make change for 7:
7 1-cent coins
5 1-cent, 1 2-cent coins
3 1-cent, 2 2-cent coins
3 1-cent, 1 4-cent coins
1 1-cent, 3 2-cent coins
1 1-cent, 1 2-cent, 1 4-cent coins
Thus, there are 6 ways to make change for 7. Write a recursive function count_change that takes a positive integer total and returns the number of ways to make change for total using these coins of the future.
def count_change(total):
def helper(n, m):
if n == 0: # 若总值为0,兑换方式1种即0
return 1
elif n < (1 << m): # 若最小硬币值大于总值一半,兑换方式为0
return 0
else:
return helper(n, m + 1) + helper(n - (1 << m), m) # 最小硬币值+1的兑换方式数;最小硬币值不变,总值为原总值减去2的最小硬币值幂次
return helper(total, 0)
【没看懂,看原课程讨论】