3.3 程序变换

power0是有关算法的一个令人满意的实现,它适用于运算的代价高于函数递归调用开销的情况.本节要推导出一个迭代算法,它执行运算的次数和power0一样.这里将要做一系列程序变换,这些变换也可以用在其他许多情况中.5 在本书后面的部分,通常将只给出算法的最终版本或几乎最终版本.
power0包含两个相同的递归调用,它每次只执行其中一个.这使我们可能通过公共子表达式删除技术来缩小代码的规模:

template<typename I, typename Op> requires(Integer(I) && BinaryOperation(Op)) Domain(Op) power 1(Domain(Op) a, I n, Op op)
{
//前条件:associative(op)∧n>0
if (n == I(1)) return a;Domain(Op) r = power 1(op(a, a), n / I(2), op);if (n % I(2) != I(0)) r = op(r, a);return r;
}

现在的目标是删除递归调用,为此要做的第一步是把过程变换到尾递归形式(tail-recursiveform),其中在过程执行的最后一步是对自身的递归调用.完成该变换的一种技术是引入累积变量(accumulation-variableintroduction),用于在不同递归调用之间携带累积的结果:

template<typename I, typename Op> requires(Integer(I) && BinaryOperation(Op)) Domain(Op) power accumulate 0(Domain(Op) r, Domain(Op) a, I n, Op op)
{
//前条件:associative(op)∧n.0
if (n == I(0)) return r;

5.只有在运算的语义和复杂性已知的情况下,编译器才会对一些内部类型做类似变换.规范性概念是类型创建者的一个断言,它保证程序员和编译器可以安全地执行这些变换.

if (n % I(2) != I(0)) r = op(r, a);
return power accumulate 0(r, op(a, a), n / I(2), op); }

设r0,a0和n0是r,a和n的原值,下面不变式(recursioninvariant)在每次递归调用时都成立:ran=r0an0 0 .这个版本还有另一优点,它不仅计算幂,还能计算乘以一个系数的幂.它也处理了指数为0的情况.但是在指数从1变到0时poweraccumulate0将多做一次平方.增加一种情况就可以消除它:

template<typename I, typename Op> requires(Integer(I) && BinaryOperation(Op)) Domain(Op) power accumulate 1(Domain(Op) r, Domain(Op) a, I n, Op op)
{
//前条件:associative(op)∧n.0
if (n == I(0)) return r;
if (n == I(1)) return op(r, a);
if (n % I(2) != I(0)) r = op(r, a);
return power accumulate 1(r, op(a, a), n / I(2), op); }

增加额外情况导致重复出现的子表达式,也使三个检测不独立了.通过仔细分析检测之间的依赖性和顺序,考虑它们的出现频率,可以给出

template<typename I, typename Op> requires(Integer(I) && BinaryOperation(Op)) Domain(Op) power accumulate 2(Domain(Op) r, Domain(Op) a, I n, Op op)
{
//前条件:associative(op)∧n.0
if (n % I(2) != I(0)) {
r = op(r, a);
if (n == I(1)) return r;
} else if (n == I(0)) return r; return power accumulate 2(r, op(a, a), n / I(2), op);
}

在一个尾递归过程里,如果所有递归调用中的过程形参都是对应的实参,它就是一个严格尾递归的(stricttail-recursive)过程:

template<typename I, typename Op> requires(Integer(I) && BinaryOperation(Op)) Domain(Op) power accumulate 3(Domain(Op) r, Domain(Op) a, I n, Op op)
{
//前条件:associative(op)∧n.0
if (n % I(2) != I(0)) {
r = op(r, a);
if (n == I(1)) return r;
} else if (n == I(0)) return r;
a = op(a, a);
n = n / I(2);
return power accumulate 3(r, a, n, op); }

严格尾递归过程可以变换为一个迭代过程,方法是把每个递归调用代换为一个到过程开始的goto,也可以用一个等价的迭代结构:

template<typename I, typename Op> requires(Integer(I) && BinaryOperation(Op)) Domain(Op) power accumulate 4(Domain(Op) r, Domain(Op) a, I n, Op op)
{
//前条件:associative(op)∧n.0
while (true) {
if (n % I(2) != I(0)) {
r = op(r, a);
if (n == I(1)) return r; } else if (n == I(0)) return r;a = op(a, a);n = n / I(2);
} }

递归不变式变成了这里的循环不变式(loopinvariant).如果开始时n>0,在变成0前要先经过1.我们借用这种情况消去对0的检查并加强前条件(strengthening`javascript
precondition):
template requires(Integer(I) && BinaryOperation(Op))
Domain(Op) power accumulate positive 0(Domain(Op) r, Domain(Op) a, I n, Op op)
{
//前条件:associative(op)∧n>0
while (true) {
if (n % I(2) != I(0)) {r = op(r, a);if (n == I(1)) return r;
}
a = op(a, a);n = n / I(2);
} }

知道了n>0会很有用.在开发组件的过程中经常会发现新的接口情况.现在放松前条件(relaxingprecondition):

template requires(Integer(I) && BinaryOperation(Op)) Domain(Op) power accumulate 5(Domain(Op) r, Domain(Op) a, I n, Op op)

{
//前条件:associative(op)∧n.0
if (n == I(0)) return r;return power accumulate positive 0(r, a, n, op);
}
通过一个简单的等式,就可以用poweraccumulate实现power:
nn.1
a = aa

这一变换就是消去累积变量(accumulation-variableelimination):

template requires(Integer(I) && BinaryOperation(Op)) Domain(Op) power 2(Domain(Op) a, I n, Op op)
{
//前条件:associative(op)∧n>0
return power accumulate 5(a, a, n -I(1), op);
}

这个算法多做了一些不必要的运算.例如,当n是16时它要执行7次运算,其中只有4次是必要的.当n是奇数时这个算法很好.避免上述问题的方法是反复做a的平方,并不断将指数折半直至它变成奇数:

template requires(Integer(I) && BinaryOperation(Op)) Domain(Op) power 3(Domain(Op) a, I n, Op op)
{
//前条件:associative(op)∧n>0
while (n % I(2) == I(0)) {a = op(a, a);n = n / I(2);
}
n = n / I(2);if (n == I(0)) return a;
return power accumulate positive 0(a, op(a, a), n, op);
}

练习3.1 请自己确认最后三行代码是正确的. 

《编程原本 》一3.3 程序变换相关推荐

  1. 《编程原本 》一2.1 变换

    2.1 变换 虽然从任意一组类型到任意类型的函数都存在,但是有些具有特殊签名的函数类更为常用.本书中经常用的是两类函数:同源谓词和同源运算.同源谓词在形式上都是Tו••× T → bool;同源运算 ...

  2. c语言二维图形变换程序,C语言图形编程(五、二维图形变换-01)(国外英文资料).doc...

    C语言图形编程(五.二维图形变换-01)(国外英文资料) C语言图形编程(五.二维图形变换-01)(国外英文资料) The first user coordinates to the screen c ...

  3. 编程开发学习笔记之程序员如何用1年时间获得3年成长(图)

    2019独角兽企业重金招聘Python工程师标准>>> 编程开发学习笔记之程序员如何用1年时间获得3年成长(图) 前言 这世界存在这么一个银行,你一出生,就自动享有这家银行为你开设的 ...

  4. 计算机快捷键桌布,桌面改造 篇三:编程娱乐两不误 | 伪程序猿的Windows双屏组建/效率工具/桌面美化指南...

    桌面改造 篇三:编程娱乐两不误 | 伪程序猿的Windows双屏组建/效率工具/桌面美化指南 2020-07-10 11:41:39 153点赞 1107收藏 74评论 哈喽大家好,我是码呆茶!作为一 ...

  5. 编程第一个Apple Watch程序创建项目

    编程第一个Apple Watch程序创建项目 2.4  编程第一个程序 本节将通过编写第一个程序,为开发者讲解如何添加Watch应用对象.运行程序.界面设计.编写代码等内容本文选自Apple Watc ...

  6. [强烈推荐]ORACLE PL/SQL编程详解之七:程序包的创建与应用(聪明在于学习,天才在于积累!)...

    [强烈推荐]ORACLE PL/SQL编程详解之七: 程序包的创建与应用(聪明在于学习,天才在于积累!) --通过知识共享树立个人品牌.   继上七篇:            [推荐]ORACLE P ...

  7. 知乎热议:27岁没文凭、想自学编程,有机会成为程序员吗?互联网风口一去不复返了吗?

    缘起 前段时间在知乎里看到了一个问题,感慨万千,拿出来给大家分享分享,希望可以抛砖引玉给大家一点启发. 问题大家已经从文章的标题里看到了:一个27岁没文凭,想去努力自学编程,有机会成为程序员吗? 而问 ...

  8. java设计一个形状shape_编程定义一个图形接口 Shape, 内含2个抽象方法 get... JAVA编程题:编一个程序包含一个接口 shape(该接......

    导航:网站首页 > 编程定义一个图形接口 Shape, 内含2个抽象方法 get... JAVA编程题:编一个程序包含一个接口 shape(该接... 编程定义一个图形接口 Shape, 内含2 ...

  9. python开发cs程序_CSE209代做、代写Computer Graphics、代做CS/python编程设计代写Python程序|代做Processing...

    CSE209代做.代写Computer Graphics.代做CS/python编程设计代写Python程序|代做ProcessingCSE209 Computer Graphics~1~CSE209 ...

最新文章

  1. redis cluster 安装配置
  2. 如何快速评估16S rRNA基因引物的覆盖率及特异性
  3. 计算机在生物科学领域的应用论文,大学生物科学教学中计算机的应用
  4. php 大牛生小牛,C#算法之关于大牛生小牛的问题
  5. 【机器学习】异常检测算法速览(Python代码)
  6. 1G服务器网站,1核1g内存云服务器建网站
  7. linux 两个mysql_Linux 安装两个MySQL
  8. 丁磊推荐《你的灯亮着吗》为三大管理必读书
  9. 宏观经济的基本指标及其衡量
  10. 粒子群算法的几个适应度评价函数
  11. win7家庭版和旗舰版区别_Windows系统的家庭版、专业版、旗舰版,都有什么区别?...
  12. 动手学数据分析(四)- 数据可视化
  13. java开发常用软件下载地址及教程。
  14. macchanger克隆MAC地址
  15. 筋膜枪方案-无刷马达方波运用1
  16. 选择system bus还是session bus?
  17. 什么是飞书机器人?如何定时发送飞书机器人消息?
  18. IDEA更新至2020版后Version Control窗口的还原问题
  19. linux . 乱码,解决Linux乱码现象
  20. Opencv(C++)系列学习---opencv_contrib安装

热门文章

  1. echart移上去显示内容_echarts如何移动到柱状图上显示自己想显示的提示信息
  2. 介绍几个医学图像处理会议
  3. ASP.NET Core Web 应用程序系列(三)- 在ASP.NET Core中使用Autofac替换自带DI进行构造函数和属性的批量依赖注入(MVC当中应用)...
  4. parentNode,parentElement,childNodes,children的区别
  5. 《深入理解Spark-核心思想与源码分析》(四)第四章存储体系
  6. python入门学习的第三天
  7. 类模板与运算符重载(一个简单的例子)
  8. HDU 3342 Legal or Not(拓扑排序)
  9. 组合数学--约瑟夫环问题 Josephus
  10. 微信分享无响应的解决