菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
120
0

现代程序设计 homework-10

原创
05/13 14:22
阅读数 40256

经过大半学期的学习和练习, 我们把学到的东西综合起来。

通过<现代程序设计>这门课,自己的确学到了好多东西.其实并不是说讲课有多棒,一是因为讲课的次数其实并不多,二是讲课的内容其实感觉并没有太大的提高,在课程快结束的时候,我现在还能够有记忆的知识性内容,也许只有C++11的一点点知识.但是之所以说通过这门课自己学到了好多,是因为这门课布置的作业和这门课一起上课的同学.

先说作业,回顾以前做过的作业

闲来无事统计了一下作业的有效代码量

记得这门课刚开始的时候老师发了一张表统计大家目前的代码量...因为刚刚参加完暑期ACM集训....自己很嚣张的填了五万行...现在看来真的是Too Simple,Sometimes Naive....

 

对于不同的人来说,这些作业的意义可能不同,对于现在正渴望学习很多东西的我来说,这些作业让我掌握了许多.从最初的动态规划解决各种子数组问题,到一步步的制作UI做成类库实现单例模式做成一个简单的软件,从解决看起来不可能的word search到利用TCP完成服务器/客户端连接处理用户提交数据并实现动态展现黄金数,再到读懂千行程序完成注释并尝试添加新功能修改Bug,还有这次的利用网页脚本编程学习JavaScript,通过这大大小小十次作业,语言方面熟练了C#,学会了JavaScript,了解了C++11,方法方面熟悉了单元测试,单例模式,学习了C#和C++的网络通信内容

再说一起上这门课的同学,都是早已闻达于诸侯的骨灰级大神人物,这样的氛围也驱使自己可以不断的去学习进步

 

 

关于本次作业

本次作业要求写成一个网页能够直接在网上显示,开始的时候我想用ASP.NET来实现,正好可以借此机会学习一下,在基本掌握了ASP.NET的初步实现方式之后,我开始纠结于如何在网上直接显示,很明显我需要申请域名放我的前端网页,然后再配我的后台进行用户输入处理(不知道是不是这个样子)....好麻烦..后来了解了一下JavaScript之后发现这个东西应该可以很轻松的解决我的这个需求,于是整个程序我最终采用JavaScript来实现,没有采用任何类似于JQuery的库,一是因为用原始的JS语言可以更好的了解其DOM模型,二是其实我当时不知道有JQuery这个库...

 

 

 

 

在作业2 (http://www.cnblogs.com/xinz/p/3318230.html ) 中, 同学们用各种方法 (主要是动态规划,外加一些遍历)计算了一维和二维数组中最小最大子数组的和。 当然,程序在飞快地运行的时候,我们可以通过debug 工具中的 单步执行 命令看到中间结果。 中间结果一般有这些数据:

这个数组目前暂定的最大子数组的范围是哪里?  目前的值是多少?

目前计算到哪些部分, 目前的牵涉到的子数组的和是多少?

我们的要求是,设计并实现一个系统,让一个普通用户就能通过单步执行的方式看到你的算法是如何工作的。

 

一个典型的流程是:

1. 用户用你的程序读入一个数组文件 (就像我们以前做过的那样),显示初始状态 (就像围棋打谱程序那样)

这里当用户单击"输入指定按钮"这个Button之后,会按照用户输入的行列要求产生指定的表格,用户可以输入数组文件

主要实现方法是根据用户输入不停的在DOM这个模型树上添加标签<tr><td>子标签,在用户输入数组之后利用正则文法进行输入正确性有效性检查,然后将输入内容导入计算数组中,最终在网页端显示初始状态

1.1. 用户也可以自行定义数组的大小,或者要求随机生成一个数字矩阵。

这里当用户单击"生成随机数组"这个Button之后,会按照用户输入的行列要求产生随机数组

主要实现方式是根据用户输入产生相应表格之后,利用随机数产生相应数字,例如我要产生-1000~1000之间的整数,那么就可以利用下面的一行代码来实现

1 Math.floor(Math.random() * (2000) - 1000)

 

2. 用户这时候有两个选择

2.1  按 单步执行 键, 在 GUI 看到你的程序是如何一步一步算出目前最大子数组的范围,当前计算到的临时子数组是在哪里,等等。 最好用不同的颜色标识不同的状态。

这里当用户选择"单步执行"这个RadioButton之后,用户可以按">>"键一步一步看到演示,当前计算到的临时子数组会以紫色区域显示,当前的最大子数组会以白边区域显示

主要实现方式是事先计算好整个动态规划过程中的每个状态.例如我可以用一个二维数组来记录,第一维表示当前的状态序号,第二维利用当前整个数组的01组合来表示.关于01组合表示,我想到了两个方法,第一个是第二维大小等于row*col,这样每一个(i,j)坐标都可以根据公式i*col+j表示成一个下标,然后用01来表示;第二个是可以利用位运算实现状态压缩,将每个(i,j)利用位运算压缩成一个长度为row*col字符串.相比较而言,位运算效率更高. 

单步执行过程中,每次展示当前的状态序号表示的状态,其中为了避免颜色冲突的问题,我用紫色来表示当前正在计算的子数组,白边区域表示目前位置已经算出的最大子数组.整个界面的配色方案采用win8的Metro配色风格.

2.2  按 自动运行 键, 在 GUI 看到程序自动运行,并自动显示过程, 每次改变状态的时候要稍作停留 (例如 1 秒钟的时间)

这里当用户选择"自动运行"这个RadioButton并设置好Interval之后,程序会按照用户自定义的Interval一步一步执行,在每次改变状态的时候会停留相应间隔

不得不说JavaScript对于计时器的支持其实很有限,SetTimeout()和SetInterval()两种方法.我才用SetInterval(),第一个参数传递我改变状态的这个函数AutoRunning(),并在当前状态序号超过状态总数的时候clearInterval()停止我的当前计时器,第二个参数传递用户自定义的Interval属性.

3. 咳,我没看清楚!  这时最好有一个 倒带 / 回滚 的键, 让用户可以看清楚关键的几步。

这里用户可以随时按"Pause"按钮暂停自动运行,并可以随时切换自动/手动模式,前进或者后退

这个实现起来也很简单,倒带/回滚就是返回上一个状态

(当然,用户可以选择是普通模式还是扩展定义的连通模式)

下面的逻辑选项分别有一维/二维/水平相连/垂直相连/水平垂直都相连/联通块/水平垂直相连联通块等模式,用户可以自由选择

 

对于一维和二维普通模式我采用了普适的动态规划算法,对于水平相连的情况我是采用我在第二次作业中提到的方法:

对于一维左右联通情况,我们可以知道它的最大和要么是没有跨过了a[n]和a[1],要么是没有跨过;如果没有跨过,那么就是简单的一维普通情况,记最大值为ans1;如果跨过了,那么不妨设此时的最大子数组为a[j],a[j+1],....a[n],a[1],a[2],...a[i],i<j,此时可以证明a[i],a[i+1],....a[j]一定是最小子数组,并且a[i]和a[j]一定是小于0的(否则可以加到最大子数组中得到更优的解),那么我们扫描一遍的时候,只要同时记录最大子数组,最小子数组,和数组总和,那么ans=max(ans1,all-ans2);

而与二维垂直相连情况,其实和二维普通情况是一样的,只不过循环的时候枚举纵向开始和结束的时候不需要保证结束点>开始点就好,同时要根据不同的开始/结束关系指定不同的染色方案

对于联通块的几种情况,我还是用了状压dp的朴素解决方法,展示起来类似于枚举,所以可以处理的状态数有限,我在程序中也做了限制,用户输入的row*col的值不能超过16,这样对于O(m*n*2^mn)的复杂度来说,已经算是可以勉强接受了.

要求: 这个要求的各个方面我们都已经单独写代码试验过了,把它们合起来也不是太难。

写JavaScript还是遇到了一些问题,JS是一门弱类型的语言,所以在进行运算的时候稍不注意就会发生字符串连接的情况,这在前期导致我debug了好久.另外对于这样一门脚本语言,直接在网页端debug貌似不是那么方便...

 最终我的页面用Index.html来显示

Declaration.js用来定义一些基本的全局变量(全局变量这种东西虽然很危险,但对于这样一个小型的项目来说的确很方便)

BaseFunction.js用来处理用户输入,得到数组与行列等基本参数

Check.js用来检验用户各种输入有效性,采用正则文法来验证

MaxSum.js用来处理程序逻辑,处理求最大子数组的过程

State.js用来改变展示状态

ButtonEvent.js用来处理各种控件响应事件

 

 

要求还那样: 写程序和单元测试,签入GitHub,写博客描述,总结所花费的时间和估计。

程序已经导入GitHub.https://github.com/oldoldb/homework-10

 

每次的总结所花费的时间和估计都是特别水的事情,因为一开始对于所要学习的知识和作业没有很好地了解,无法准确估计时间,而真正开始编码的时候又会太过投入忘记记录花费时间..

所以以下又是复制粘贴以前的....

  Personal Software Process Stages 时间百分比(%)  实际花费的时间 (分钟) 原来估计的时间 (分钟)
Planning 计划 2.0  45            60 
·         Estimate  ·         估计这个任务需要多少时间,把工作细化并大致排序 2.0  45 60 
Development 开发 88.7  2000  1500
·         Analysis  ·         需求分析 (包括学习新技术) 2.7  60  60
·         Design Spec ·         生成设计文档   0  0
·         Design Review ·         设计复审 (和同事审核设计文档) 0  0  0
·         Coding Standard ·         代码规范 (制定合适的规范) 5.3  120  60
·         Design ·         具体设计 5.3  120  60
·         Coding ·         具体编码 62.1  1400  1200
·         Code Review ·         代码复审 10.6  240  60
·         Test ·         测试(自我们测试,修改代码,提交修改) 2.7  60  60
Reporting 总结报告 9.3  210  60
·         Test Report ·         测试报告 5.3  120  0
·         Size Measurement ·         计算工作量 1.3  30  0
·         Postmortem & Improvement Plan ·         事后总结, 并提出改进 2.7  60  60
Total 总计 100% 总用时 总估计的用时
       2255  1260

 

评分:

功能 (分数范围 –30 到 30分):  在PC 桌面上运行  (满分 20 分); 如果能在程序能直接在网上显示 (例如使用 Javascript 在网页上让用户直接看到过程),则满分是 30 分。

我采用的是Html+CSS+JavaScript,程序可以直接在网上显示,满分30分.

 

代码质量 (分数范围:  -30 分到 30 分):  同学们在课程中已经看了很多书,实践了不少原理,也看过烂的代码 (很多同学还大义凛然地鄙视过烂代码),并且纷纷表示要写高效/好懂/可扩展的算法。现在就来试试看,请写博客,截图,画图描述:

首先我没有看很多编码规范方面的书,这学期为了这门课学习的书有<编程之美>,<数学之美>,<C#方面的书>,<Windows Form方面的书>,WPF方面的书>,<Visual C#网络编程方面的书>,<WinSock编程方面的书>,<Asp.net方面的书>,<JavaScript方面的书>,代码大全这本书老师虽然推荐但是其实并没有读...也许这就是短视吧...毕竟就这门课目前来说可能追求的短平快..

 

    注释,命名: 有一致规范的规范,合适的注释  

  我的代码的命名和注释一向都比较完成和规范,命名规范已经逐渐过渡到帕斯卡命名法,注释也会随手写好

  命名方面我比较倾向于用有意义的完整单词来表示函数/变量的意义,例如CreateTable,InitSum等等,不太支持用简写来代表整个单词,因为这样经常会出现简写含义不清楚,而且不

  同的人对于同一个单词的简写方法理解不同,而用完整单词不会出现这个问题.对于Button事件我都同一用ButtonName+Clicked来表示等等

  

  

    结构: 结构清晰,各种类/结构的定义 正确地反映了现实世界实体,以及实体之间的关系.

  可能是对于JS的编码经验不够,JavaScript定义成类个人感觉完全没有必要,我定义许多function需要的时候就用就好..

  程序结构如下:

  

  这次用的纯JavaScript实现,没有使用JQuery库,对自己也是一个锻炼,逻辑放在.js里实现,.css实现样式表,.html用于基本构建页面.

    单元测试: 有单元测试保证 非UI 模块的正确性  (UI 模块不要求单元测试自动覆盖),有代码覆盖率。

  我们的授课老师邹老师不愧是单元测试之王,C++/C#我们要做单元测试,这次的脚本也要做单元测试.

  JavaScript的单元测试我采用QUnit,这个单元测试框架主页在这里:http://qunitjs.com/

  由于很多地方不方便做单元测试,对于非UI模块,我挑选了MaxSum(b,n),MinSum(),CheckMargin(x,y)三个典型的函数就行单元测试,测试如下:

 1 test("Test for MaxSum(b,n)",function()
 2 {
 3     var testArray = new Array(10, -1, -10, 10, 2);
 4     var ActualResult=MaxSum(testArray,5);
 5     equal(ActualResult, 12, "MaxSum(testArray,5) is 12");
 6     testArray = new Array(-10, -10, -10, -10, -100);
 7     ActualResult = MaxSum(testArray, 5);
 8     equal(ActualResult, -10, "MaxSum(testArray,5) is -10");
 9 })
10 test("Test for MinSum(b,n)",function()
11 {
12     var testArray = new Array(10, -1, -10, 10, 2);
13     var ActualResult=MinSum(testArray,5);
14     equal(ActualResult, -11, "MinSum(testArray,5) is -11");
15     testArray = new Array(-10, -10, -10, -10, -100);
16     ActualResult = MinSum(testArray, 5);
17     equal(ActualResult, -140, "MinSum(testArray,5) is -140");
18 })
19 test("Test for CheckMargin(x,y)",function()
20 {
21     var row=10;
22     var col=5;
23     var ActualResult=CheckMargin(10,5);
24     equal(ActualResult, false, "CheckMargin(10,5) is false");
25     ActualResult = CheckMargin(-1, -1);
26     equal(ActualResult, false, "CheckMargin(-1,-1) is true");
27 })
 1 <!DOCTYPE html>
 2 
 3 <html lang="en" xmlns="http://www.w3.org/1999/xhtml">
 4 <head>
 5     <meta charset="utf-8" />
 6     <title></title>
 7     <script type="text/javascript" src="QUnit.js"></script>
 8     <script type="text/javascript" src="homework-10.js"></script>
 9     <script type="text/javascript" src="test.js"></script>
10     <link href="QUnit.css" rel="stylesheet" type="text/css" />
11 </head>
12 <body>
13     <h1 id="qunit-header">QUnit Test Suite</h1>
14     <h2 id="qunit-banner"></h2>
15     <div id="qunit-testrunner-toolbar"></div>
16     <h2 id="qunit-userAgent"></h2>
17     <ol id="qunit-tests"></ol>
18     <div id="qunit-fixture">test markup</div>
19 </body>
20 </html>

 

  我们可以看到有一个点没有过,当我输入的数组为为(-10,-10,-10,-10,-100)时,我期望得到的子数组最大和为-10,但结果确实测试不正确,但实际运行的结果:

所以我觉得可能是一些全局变量单元测试不能很好地照顾到,导致出现了这一问题...

 

关于代码覆盖率,JS的代码覆盖率检查我在网上搜到使用ScriptCover来实现,但是这东西貌似down不下来?而且对于单元测试这种东西我的意见一向是这种东西是对于大型项目封装的很彻底才能发挥它的作用,而对于一些小型项目来说,无谓的刷代码覆盖率是没有多少实际意义的...

(注:  以上的各个部分,达不到基本要求的,倒扣分,扣到 –30 分为止。 抄袭按学校规定处理)

 

我不会达不到基本要求嗒

 

附加题:  你已经做了这么多,不妨再进一步: 如果博客中描述了动态规划的原理,并通过录制屏幕的方式让一般的读者 (例如,正在学习算法的大学生)能通过你的动态程序理解动态规划的算法,以及这个算法的扩展,那么可以得到附加分  10 分。你可以宣传你的博客,让大家都来学习。 

 

"你已经做了这么多,不妨再进一步".......

动态规划,维基百科上的原理解释是这样的

就我的理解来说,动态规划主要有两点:

1.最优子结构

2.重叠子问题

最优子结构保证了我们可以利用动态规划来解决问题,而重叠子问题保证了我们可以得到比朴素方法复杂度更低的算法.

我在暑假的时候较为系统的学习过算法,当时影响我比较深的两篇博客推荐在这里:

第一个是July大神的博客,他的博客不用多说已经被很多人拜读过了:http://blog.csdn.net/v_july_v/article/details/6110269

第二个是liufeng_king的博客,他的博客基本上系统的阐述了大二下学期算法课本的内容:http://blog.csdn.net/liufeng_king/article/details/8490770

关于动态规划的知识点研究方法相信可以在以上两篇博客中找到答案.

 

在这里我们利用最大子数组问题探讨一下动态规划算法

 

题目描述:输入一个整型数组,数组元素有正有负,数组中连续的一个或多个整数组成一个子数组,求所有子数组的和的最大值.

样例:例如输入的数组为-10,-1,1,2,-10,那么最大的子数组和为1+2=3

动态规划解法:

记原数组为a[i],sum[j]=max{a[i]+a[i+1]+...+a[j]},0<=i<=j,且0<j<n,那么所求的最大子数组和为max{sum[j]},0<=j<n

那么我们很容易知道,当sum[j-1]>0时,sum[j]=sum[j-1]+a[j],(因为a[j]加上一个正数总会大于自身的),否则sum[j]=a[j],(因为a[j]加上一个负数肯定比自身小) 注意我们的sum[j]的含义,它代表j以前的最大子数组和.

那么我们可以得到动态规划方程为

sum[j]=max(sum[j-1]+a[j],a[j]),0<=j<n,

已知遍历一遍数组,同时维护一个最大ans,即可得到答案.复杂度为O(n)

 

当我们将数组扩展到二维,只需在进行动态规划之前,先将数组纵向压缩为一维就好,关于扩展的算法可以参见我以前的作业:http://www.cnblogs.com/oldoldb/p/3331975.html

至于联通块问题,目前我的解决方式还是停留在暴力状压DP的解决层面上...

 

下面根据我们的演示程序演示一下最大子数组的求解方法:

我们先随机生成一个一维数组,ans初始化为-Number.MAX_VALUE

此时扫描第0个元素,当前计算子数组为a[0]=-789,暂定最大和=sum[0]=a[0]=-789,ans=max(ans,sum[0])=ans=sum[0]=-789

扫描第1个元素,当前计算子数组为a[1]=523,sum[1-1]=sum[0]=-789<0,所以暂定子数组=sum[1]=a[1]=523,ans=max(ans,sum[1])=523

扫描第2个元素,当前计算子数组为a[2]=-779,由于sum[1]=523>0,所以sum[2]=sum[1]+a[2]=-256,ans=max(ans,sum[2])=523

a[3]=788,sum[2]<0,sum[3]=a[3]=788,ans=max(ans,sum[3])=788

依次类推

扫描最后一个元素,我们得到最大子数组和(白边区域)ans=2443.

这个演示程序是不是很方便~!如果有任何地方不清楚可以随时后退~

以上示例生成的GIF演示如下

 

 

 

 

感觉最后一次作业做的比较完美了.

现代程序设计是一门很好的课,

这个学期有编译/数据库/软工这三门亲手写项目的课,而且工程量都不小,

但是我还是认为现代程序设计带给我的收获更大

那么,

现代程序设计,

再见!

 

发表评论

0/200
120 点赞
0 评论
收藏