Control & Recursive Function
Whether the statement is executed
Control expression
Control expression can skip some errors while one-line-call function will execute all the components first which may occur the errors.
# eg1 :
"""
>>> real_sqrt1(-4)
0
"""
def real_sqrt1(x):
if x>0:
return sqrt(x)
else:
return 0
# eg2
"""
>>> real_sqrt2(-4)
ERROR
"""
def real_sqrt2(x):
return if_(x>0,sqrt(x),0)
def if_(c,f,e):
if c:
f
else:
e
We can see in the above examples . In the eg1 , if the header’s expression of the if statement is not True the suite won’t be executed . Then it will skip over all subsequent clauses in the conditional statement. And next it will execute the else part . So it just returns the 0.
While in the eg2, we call the real_sqrt func ,and it return the if_ part . We must notice that when we call a function ,the python will executed the parameters of the function first before calling this function’s body .So when it comes to the if_(x>0,sqrt(x),0), python will calculate the sqrt(-4) first , which occur the error.
Logical Operators
- If A and B , A is False , B will not be executed .
- If A or B, A is True , B will not be executed .
Short circuiting behavior is a form of control.
Conditional Expression
< Consequet > if < Predicate > else < Alternative >
Evaluation rules:
First evaluate < Predicate > expression , if it is True the whole expression is the value of < Consequent > otherwise is the < Alternative >.
The order of finding a value
local - > parent -> global
When finding a parameter’s value , we will find its local frame first then its parent frame finally its global frame . The first time we find the value ,it will be bound to the parameter which means that is the value we will use.
Local name are not visible to other (non-nested) frame.
Recursion
The difference between iteration and recursion
Iteration: Using the simplest way to explain interation , we can say where there is an iteration, there must exist a loop . And there must be at least one variable that continuously deduces the new value from the old value ,either directly or indirectly. We can call it iterative variable.
We often use for statement or while statement to express the repetition.
Recursion: A function is called recusive when the body of the function calls itself, either directly or indirectly.
A common part can be found in many recursive function. We always use the if - elif - else statement in a recursive funciton. And it will begin with the base case, which is the simplest part of the problem.Also the base case determines when then recursion ends. If it hits the base case, we will get the first value and then track back to get the right answer.
Difference1:
Recursive Decomposition
Example : 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 integer parts up to m in increasing order. For example, the number of partitions of 6 using parts up to 4 is 9.
count_partition(n,m) – eg : count_partition(6,4)
- 6 = 4 + 2
- 6 = 4 + 1 +1
- 6 = 3 + 3
- 6 = 3 + 2 +1
- 6 = 3 + 1 + 1 + 1
- 6 = 2 + 2 + 2
- 6 = 2 + 2 + 1 + 1
- 6 = 2 + 1 + 1 + 1 + 1
- 6 = 1 + 1 + 1 + 1 + 1 + 1
- Exploring the 2 possibilities:
- Using at least one 4
- Don’t use any 4
- Solving 2 simpler problems:
- count_partition(6,3) 【step 3-9】
- count_partition(2,4) – [is equivalent to count_partition(2,2)] 【step 1-2 ingoring 4】
- If we use n,m to replace the exact numbers, we will get count_partition(n,m-1) and count_partition(n-m,m) .
- Finding the base case:
- if n is equal to 0, there just exist 1 way to partition n including no parts , so it returns 1
- if n is less than 0 , no way to partition it, so it returns 0
- if m is equal to 0 ,we can not find any positive integer which is less than m so it returns 0
Now we can write the code:
# if we want see the process clearlier ,we can give a trace func
def trace(f):
def g(n,m):
res = f(n,m)
print(f.__name__ + '(' + str(n) + ')(' + str(m) + ')->' + str(res))
return res
return g
@trace
def count_partition(n,m):
if n == 0:
return 1
elif n < 0:
return 0
elif m == 0:
return 0
else:
with_m = count_partition(n, m-1)
without_m = count_partition(n-m ,m)
# (or without_m = count_partition(n-m, min(n-m,m))
return with_m + without_m
Conclusion:
- Tree recursion often involves exploring different choices.
- When we decompose the recursion , if we don’t have any idea about the else part , we can give specific value(s) to the function , list all the cases and then try to conclude some rules from the example.
- Always take that the function is effective for granted when you call the function you might use. ( Thinking about the data abstraction ) .That is, when we use that function , we do not care about how the function is implemented and just use it.
Exercise : hw02 – count coins
Given a positive integer total , a set of coins makes change for total if the sum of the values of the coins is total.Here we will use the standard US Coin values : 1,5,10,25.
It is very similar to the Partition example . It only change the gap between m . In the previous example , the we can use all the positive num which is less than m , so we let m-1 each time when we call the function then we can get all the possible m . But we can not do that anymore ,so we can build a funciton for the coin values to solve the gap problem (func order_max).
def count_coins2(total):
"""Return the number of ways to make change for total using coins of value of 1, 5, 10, 25.
>>> count_coins(15)
6
>>> count_coins(10)
4
>>> count_coins(20)
9
>>> count_coins(100) # How many ways to make change for a dollar?
242
>>> from construct_check import check
>>> # ban iteration
>>> check(HW_SOURCE_FILE, 'count_coins', ['While', 'For'])
True
"""
"*** YOUR CODE HERE ***"
def order_max(coin):
if coin == 25:
return 10
elif coin == 10:
return 5
elif coin == 5:
return 1
else:
return 0
def count_partition(total,max=25):
if total < 0:
return 0
elif max == 0:
return 0
elif total == 0:
return 1
else :
with_max = count_partition(total-max,max)
without_max = count_partition(total,order_max(max))
return with_max + without_max
return count_partition(total,max=25)
It seems quite simple if we follow the Partition example .But in the homework it gives us a function next_largest_coin, we need to use this function to implement the count_coins.
def next_largest_coin(coin):
"""Return the next coin.
>>> next_largest_coin(1)
5
>>> next_largest_coin(5)
10
>>> next_largest_coin(10)
25
>>> next_largest_coin(2) # Other values return None
"""
if coin == 1:
return 5
elif coin == 5:
return 10
elif coin == 10:
return 25
We can see in that function the order of the coin has changed , and notice that the first coin is 1 , so the local function should be changed to count_partition(total,max = 1) . If we just change the order_max function to next_largest_coin function in the else part , we definely need to modify the base case to implement it.
If we can not find the base case directly ,we can write down a simple example to figure out it .
*eg : count_partition(26,1) – at least make sure it contains 1,5,10,25 *
def count_coins(total):
"""Return the number of ways to make change for total using coins of value of 1, 5, 10, 25.
>>> count_coins(15)
6
>>> count_coins(10)
4
>>> count_coins(20)
9
>>> count_coins(100) # How many ways to make change for a dollar?
242
>>> from construct_check import check
>>> # ban iteration
>>> check(HW_SOURCE_FILE, 'count_coins', ['While', 'For'])
True
"""
def count_partition(total,coin=1):
if coin is None:
return 0
elif total== coin:
return 1
elif total< coin:
return 0
else:
with_coin = count_partition(total-coin,coin)
with_next_coin = count_partition(total,next_largest_coin(coin))
return with_coin + with_next_coin
return count_partition(total,coin=1)
In this example we can know that if need two arguments to implement a function , while the given function just contains one argument ,at this time we can build a local function for it and give a default value to the second argument we has added.
Default Argument Values
In Python, we can provide default values for the arguments of a function. When calling that function, arguments with default values are optional. If they are not provided, then the default value is bound to the formal parameter name instead. In the def statement header, = does not perform assignment, but instead indicates a default value to use when the pressure function is called2.So apparently Default Argument Values is different with defining an argument in the local frame.
Hw02
num_eights
def num_eights(x):
"""Returns the number of times 8 appears as a digit of x.
>>> num_eights(3)
0
>>> num_eights(8)
1
>>> num_eights(88888888)
8
>>> num_eights(2638)
1
>>> num_eights(86380)
2
>>> num_eights(12345)
0
>>> from construct_check import check
>>> # ban all assignment statements
>>> check(HW_SOURCE_FILE, 'num_eights',['Assign', 'AugAssign'])
True
# if we do not use recursion:
'''
num = 0
while x>0:
x,r = x//10,x%10
if r == 8:
num = num+1
return num
'''
"""
if x == 0:
return 0
elif x % 10 == 8:
return num_eights(x//10)+1
else:
return num_eights(x//10)
pingpong
the pingpong 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 .
The first 30 elements of the pingpong sequence are listed below, with direction swaps marked using brackets at the 8th 16th 24th 28th
[0,1,2,3,4,5,6,7,[8],7,6,5,4,3,2,1,[0],1,[2],1,0,-1,-2,-3,[-4],-3,-2,-1,[0],-1,-2]
Implement a function pingpong that return the nth element of the pingpong sequence without using any assignment statement
According to the requirements , we can use the assignment , so we need to know the rule among these number . if we take out 3 arbitrary consecutive numbers , then we can find abs(pre-mid) == abs(mid-aft)==1 , and we know the sequence begin with 1 , so we can know the nth number will be equal to the n-1th number minus 1 or add 1. Now we have the first rercursion about pingpong. Then we use another function to represent the relationship of the up and down ,and notice that we still cannot use assignment . Only the two conditions(have eight or divide eight) can change the direction .If it do not meet the condition ,the direction won’t change , so we have write another recursion funciton now.
def pingpong(n):
"""Return the nth element of the ping-pong sequence.
>>> pingpong(8)
8
>>> pingpong(10)
6
>>> pingpong(15)
1
>>> pingpong(21)
-1
>>> pingpong(22)
-2
>>> pingpong(30)
-2
>>> pingpong(68)
0
>>> pingpong(69)
-1
>>> pingpong(80)
0
>>> pingpong(81)
1
>>> pingpong(82)
0
>>> pingpong(100)
-6
>>> from construct_check import check
>>> # ban assignment statements
>>> check(HW_SOURCE_FILE, 'pingpong', ['Assign', 'AugAssign'])
True
"""
assert n>=0,'N MUST BE A NON-NEGATIVE NUMBER'
def up_or_down(x):
if x <= 7 :
return 1
elif num_eights(x)>0 or x%8 == 0:
return (-1)*up_or_down(x-1)
else:
return up_or_down(x-1)
if n == 0:
return 0
else:
return pingpong(n-1) + up_or_down(n-1)
missing_digits
def missing_digits(n):
"""Given a number a that is in sorted, increasing order,
return the number of missing digits in n. A missing digit is
a number between the first and last digit of a that is not in n.
>>> missing_digits(1248) # 3, 5, 6, 7
4
>>> missing_digits(1122) # No missing numbers
0
>>> missing_digits(123456) # No missing numbers
0
>>> missing_digits(3558) # 4, 6, 7
3
>>> missing_digits(35578) # 4, 6
2
>>> missing_digits(12456) # 3
1
>>> missing_digits(16789) # 2, 3, 4, 5
4
>>> missing_digits(19) # 2, 3, 4, 5, 6, 7, 8
7
>>> missing_digits(4) # No missing numbers between 4 and 4
0
>>> from construct_check import check
>>> # ban while or for loops
>>> check(HW_SOURCE_FILE, 'missing_digits', ['While', 'For'])
True
"""
"*** YOUR CODE HERE ***"
'''
missing_num = 0
n2 = 0
while n > 0:
n , r1 = n//10, n%10
n2 = n
# print('r1',r1)
if n2 > 0:
n2 , r2 = n2 //10, n2%10
# print('r2',r2)
assert r1-r2>=0,'n must be an increasing order num'
if r1-r2>1:
missing_num = missing_num + r1-r2-1
return missing_num
'''
if n < 10:
return 0
elif n % 10 > (n//10)%10:
return missing_digits(n//10)+n%10-(n//10)%10-1
else:
return missing_digits(n//10)
make_anonymous_factorial*
The question is difficult for me ,and the answer is from John’s lecture Q&A.
the recursive function can be written as a single expression by using a conditional expression
fact = lambda n : 1 if n==1 else mul(n,fact(sub(n,1)))
however this implemention relies on the fact(no put intented) that fact has a name, to which it refer in the body of fact
To write a recursive function we have always given it a name using a def or a assignment statement so that we can refer to this function within in its own body.
Now defining a fact recursively without giving it a name .
write an expression that computes n factorial using only call expression .conditional expression and lambda expression (no assignment or def statement)
can not use make_anonynous_factorial function in the return expression.
def make_anonymous_factorial():
"""Return the value of an expression that computes factorial.
>>> make_anonymous_factorial()(5)
120
>>> from construct_check import check
>>> # ban any assignments or recursion
>>> check(HW_SOURCE_FILE, 'make_anonymous_factorial', ['Assign', 'AugAssign', 'FunctionDef', 'Recursion'])
True
"""
# return 'YOUR_EXPRESSION_HERE'
return lambda n : (lambda f : f(n,f))(lambda n, fact: 1 if n==1 else n * fact(n-1,fact))
Some ideas about this question:
- If we want to call something while it is anonymous or we can’t have it already ,we can pass it as an argument.
- We have two choices. one is that we can build a parent function for the original function by using lambda to pass the fact .But it seems a little complex for the following part because at that time n becomes a local variable. So we consider another way that we can add an argument named fact to the original function .Now the original function has two arguments. In order to match the two-argument structure, we need to change fact(n-1). Because this calling just have one argument , then we change it to fact(n-1,fact).
–> lambda n, fact: 1 if n==1 else n * fact(n-1,fact) - Now we get a function with two arguments. When we make two arguments but we really want a function that takes one argument then we should write a function taking one argument and calling the function which takes two arguments. Finally get the above answer.