WEEK2-Recursion and invariant programming

Paradigms of Computer Programming - Fundamentals (EDX)

WEEK2-Recursion and invariant programming 笔记



1. Overview of invariant programming

declare
fun {SumDigitsR N}
   if (N==0) then 0
   else (N mod 10) + {SumDigitsR (N div 10)} end
end

In the previous lesson we saw a simple example of a recursive function, namely SumDigitsR. This function is able to calculate the sum of all digits of any integer, no matter how many digits it has (within the limits of the system's representation). The recursive call and the conditional together are similar to a while loop: an execution that is repeated while some condition is satisfied (like N==0). Each function call does one iteration of the loop.

上节课看了一个递归函数的简单例子(SumDigitsR),这个函数可以计算任何整数的所有位数之和,无论有多少位,只要在系统的能力范围内。“递归调用和条件”与“while”循环(某个条件满足时就反复执行)类似。每个函数调用做一次循环的重复。


In this lesson we will go to the root of this intuition. We will understand in a deep way the connection between recursion and loops, and we will see how to write correct and efficient programs that use recursion. We will see that a recursive function is exactly the same as a while loop, if one essential condition is satisfied: if the single recursive call that is executed is the last operation done by the function (this is calledtail recursion). To write a tail recursive function, we need to introduce one or more extra arguments, to help calculate the result. Anaccumulator is an extra argument in which the result is calculated piece by piece (it is accumulated).

这节课继续探索。我们会更深入了解递归和循环的联系,以及怎样用递归写出正确的、高效的程序。我们会看到递归函数和while循环一模一样在满足一个必要条件(执行的递归调用是函数上一次的操作(尾递归))时。为了写出尾递归函数,我们需要引入一个或多个额外的变量来帮助计算结果。累积器就是一个结果是一点点累积起来的变量。
(argument 字典跟我说有什么自变量、引数的意思,这里先翻译成变量了。)


We will give a technique, called invariant programming, to program correct and efficient loops. This technique applies both to the functional paradigm and to the imperative paradigm. (An imperative paradigm allows variables to be assigned more than once; many widely used languages, such as Java and C++, are based on imperative paradigms.) As part of this technique, we define two formal concepts, thespecification of a function (a mathematical formula that defines what the function calculates) and theinvariant of a recursive function (a mathematical formula that is true at each recursive call). The right way to program a function is to start with the specification. The right way to program a loop (a recursive function or while loop) is by starting with the invariant.

我们会用一种技术(不变量编程)来写出正确高效的循环。这种技术应用于函数范式和命令范式。(命令范式允许变量被多次赋值,很多常用语言比如JAVA和C++都是命令范式。)这种技术的两个概念:函数规范(定义函数计算什么的数学式),递归函数的不变量(每次递归调用都为TRUE的数学式)。正确的写函数方式是从规范开始,而正确的写循环(递归函数或while循环)是从不变量开始。

(术语可能会翻译的有问题,我的笔记自己看得懂最重要。)


2. Factorial with communicating vases


The sum of digits function we did in the last lesson seems to be very natural, so natural that it's hard to imagine another way! But this intuition is wrong. There is a second approach to write the function, quite different that leads to another, and better definition. To introduce this approach, let's start with a simpler function, namely factorial. This will let us focus on the key insight. Afterwards, we will come back to sum of digits.

上节课写的求位数和的函数简直太完美了,但是这种直觉是错的。还有第二种方式来写这个函数,和第一种很不一样,而且更好。为了引入这种方式,先从简单的求阶乘函数开始,这会帮助我们关注重点,之后会回到求位数和。



We first give the specification of the factorial function, a mathematical formula that defines it. Our first implementation of factorial follows exactly this specification. This definition is in the same style as the sum of digits function. It is fine from a mathematical point of view, but quite bad from a programming language point of view.

先给出阶乘函数的规范,用数学式定义它。第一种完全按照这个规范来。这个定义和位数和函数的形式一样。从数学角度看还好,但是从编程语言角度来看就不太好了。


% Factorial function
% 0! = 1
% n! = n * (n-1)! when n>0
declare
fun {Fact1 N}
   if N==0 then 1
   else N*{Fact1 N-1} end
end


The second implementation uses an approach called the principle of communicating vases. We start with a formula that separates the factorial into two parts: n!=i!*a. We know n but we don't know n! (yet). The other variables, i and a, are introduced together. Initially, i=n and a=1, so the formula holds. Now, we come to the key insight: we will change i and a together, in such a way that the formula still holds. If we can arrive at a point where i=0, then a is equal to n!, the answer.

第二种使用一种叫做沟通容器法则的方式。先把阶乘分成两部分:n!=i!*a。我们知道n但是还不知道n!。其余的变量i和a是一起引入的。首先,i=n,a=1,所以式子成立。现在,关键的是:我们要一起改变i和a,用一种让式子仍然成立的方式。当i=0时,a就等于n!,得到了答案。




For factorial, we will decrease i and increase a together. We will decrease i by 1 and increase a by multiplying it by i. By doing this, the formula remains true. We can easily show this rigorously: i!*a = i*(i-1)!*a = (i-1)! * (i*a). Let i'=i-1 and a'=i*a. Then the last expression is equal to i'!*a', which is exactly our formula. Now we can write the second implementation.

对阶乘来说,同时减少i,增加a。i -= 1,a *= i。这样,式子一直成立。i!*a = i*(i-1)!*a = (i-1)! * (i*a)。i'=i-1 and a'=i*a。最后一个表达式是i'!*a',完全就是我们的式子。现在可以写第二种了。


% Principle of communicating vases
% n! = i! * a
%    = i * (i-1)! * a
%    = (i-1)! * (i*a)
% We have: i'=i-1 and a'=i*a
declare
fun {Fact2 I A}
   if I==0 then A
   else {Fact2 I-1 I*A} end
end


3. Sum of digits with communicating vases




fun {SumDigits2 S A}
   if S==0 then A
   else
      {SumDigits2 (S div 10) A+(S mod 10)}
   end
end


4. The golden rule of tail recursion

#We will see why Fact2 (with tail recursion) is more efficient than Fact1 (no tail recursion):
1. Fact1 is based on a simple mathematical definition
2. Fact2 is designed with invariant programming

#Comparing Fact1 and Fact2
1. Tail recursion is when the recursive call is the last operation in the function body
2. N * {Fact1 N-1} % No tail recursion
After Fact1 is done, we must come back for the multiply.
Where is the multiplication stored? On a stack!
3. {Fact2 I-1 I*A} % Tail recursion
The recursive call does not come back!
All calculations are done before Fact2 is called.
No stack is needed (memory usage is constant).


#Comparing functional and imperative loops
1. A while loop in the functional paradigm:

fun {While S}
   if {IsDone S} then S
   else {While {Transform S}} end /*tail recursion */
end

2. A while loop in the imperative paradigm:
(in languages with multiple assignment like Java and C++)

state whileLoop(state s){
   while (!isDone(s))
      s=transform(s);/*assignment*/
   return s;
}

3. In both cases, invariant programming is an important design tool

这一小节的练习(PRIME)我有点卡住,后来用我唯一会一点的python写了一下,再翻译成基本不会的oz终于对了,把python代码贴这里:

def p(n, d):
    if n == 1:
        return False
    elif d == 1:
        return True
    elif n%d == 0:
        return False
    else:
        return p(n, d-1)

def prime(n):
    return p(n, n-1)


5. Invariant programming to calculate powers

#summary

1. A recursive function is equivalent to a loop if it is tail recursive
2. To write functions in this way, we need to find an accumulator
3. We find the accumulator starting from an invariant using the principle of communicating vases
4. This is called invariant programming and it is the only reasonable way to program loops
5. Invariant programming is useful in all programming paradigms


#求幂的一种比较好的方式
#This program is a true loop (it is tail-recursive) and it uses very few multiplications

fun {Pow3 X N}
   fun {PowLoop Y I A}
      if I==0 then A
      elseif I mod 2 == 0 then
             {PowLoop Y*Y (I div 2) A}
      else   {PowLoop Y (I-1) Y*A} end
   end
in
   {PowLoop X N 1}
end


#Invariants and goals
1. Changing one part of the invariant forces the rest to change as well, because the invariant must remain true
The invariant’s truth drives the program forward
2. Programming a loop means finding a good invariant
Once a good invariant is found, coding is easy
Learn to think in terms of invariants!
3. Using invariants is a form of goal-oriented programming
We will see another example of goal-oriented programming when we program with trees in lesson 5




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值