EliteWorks 的个人资料EliteWorks Space for Spe...日志网络 工具 帮助

EliteWorks Space for Special U

Elite, it works! Elite, 我看行!

EliteWorks

职业
地点
刘 鑫 PM

程博文 Dev Lead
杨学智 SDE

张 坚 Test Lead
唐良杰 SDET
丁吉昌 SDET
8月2日

基于Microsoft UI Automation实现Elevator E2E Test

Author: Andy(张坚)

图形界面的自动化测试一直是测试中的难点。我们的电梯界面又是用WPF技术实现的,如何驱动和验证这种新技术构建的图形界面成为我们实现真正意义上的E2E Test所必须解决的问题。好在Microsoft早已将这个问题考虑在他的.NET Framework 3.0中了——Microsoft UI Automation。UI Automation是专为UI测试自动化任务设计的,下面我们来基于这个框架实现电梯的E2E Test。

首先需要引用%PROGRAMFILES%\Reference Assemblies\Microsoft\Framework\v3.0下的UIAutomationClient.dll 和 UIAutomationTypes.dll,并添加如下using语句:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Windows.Automation;
using System.Diagnostics;
using System.Threading;

接着创建电梯程序进程:
Process p = Process.Start("d:/Elevator.exe");

为了确保电梯程序完成启动,需要留有启动时间:
Thread.Sleep(WAIT_LAUNCHING);

然后使用AutomationElement.FromHandle静态方法获取电梯程序窗体对象的引用:
AutomationElement elevatorForm = AutomationElement.FromHandle(p.MainWindowHandle);

此外,我们也需要获取电梯内外各按钮的引用以便驱动界面运行,这是由查找界面上所有Button类型的AutomationElement来完成的:
AutomationElementCollection buttons = elevatorForm.FindAll(TreeScope.Descendants, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Button));
再花一点时间进行试验以确定buttons各索引位与窗体上各控制按钮之间的映射关系。

模拟用户点击按钮是通过使用InvokePattern启用按钮单击来实现的:
InvokePattern ip = (InvokePattern)button.GetCurrentPattern(InvokePattern.Pattern);
ip.Invoke();

既然是测试,就需要验证步骤。类似于获取按钮的引用,通过查找Edit类型的AutomationElement来得到用于显示当前楼层的文本框的引用:
AutomationElement currentFloorBox = elevatorForm.FindFirst(TreeScope.Descendants, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.Edit));
再通过调用currentFloorBox.GetCurrentPropertyValue(ValuePattern.ValueProperty)便可获取楼层数字。

当这些底层技术问题解决之后,我们再用一些高层的贴近领域的函数对它们进行封装,以便于测试代码的书写:
// 电梯外指定楼层呼叫电梯,请求上楼
void OutUpFloor(int floor);
// 电梯外指定楼层呼叫电梯,请求下楼
void OutDownFloor(int floor);
// 电梯内指定目的地楼层
void InFloor(int floor);
// 关门
void CloseDoor();
// 等待电梯上/下X层楼
void WaitXFloors(int numOfFloor);
// 获取当前楼层显示
int GetCurrentFloor();

这样我们核心的电梯领域特化的E2E Test基础设施便建立起来了。下面展示一个“验证迟来的请求被提前响应的情况:上楼”的场景来举例说明测试代码的书写方式:
OutUpFloor(3);
OutUpFloor(2);
WaitXFloors(1);
Assert.AreEqual(2, GetCurrentFloor());
InFloor(5);
CloseDoor();
WaitXFloors(1);
Assert.AreEqual(3, GetCurrentFloor());
InFloor(4);
CloseDoor();
WaitXFloors(1);
Assert.AreEqual(4, GetCurrentFloor());
CloseDoor();
WaitXFloors(1);
Assert.AreEqual(5, GetCurrentFloor());

参考资料:测试运行: Microsoft UI 自动化库

"Elite, it works!"

                        ——“Elite,我看行!”

7月18日

TORUS系列之二:求矩阵中元素和最大的子矩阵

Author: Kevin(刘鑫)

1. 问题描述

给定一个矩阵,求出该矩阵中元素和最大的子矩阵,包括横向和纵向边界上的子矩阵。下面三幅图是子矩阵的三种情况:边界内的子矩阵、横向边界上的矩阵、纵向边界上的矩阵。

 Image2008-07-13T16-01-51    Image2008-07-12T23-09-44    Image2008-07-12T23-33-16

 2. 算法描述及实现

下面是C#编写的算法框架代码,每行代码的前一行是对该行代码实际意义的解释。

5 

2.1 求解边界内的最大和子矩阵

下面解释求解矩阵内最大和子矩阵的算法。假设给定nxm矩阵,不失一般性设求解该矩阵中第i行到第j行之间最大和子矩阵,其中1<=i<=j<=n。我们首先求解当i=j时,第i行的最大和子矩阵,求解的方向是从最后一列开始向前求解;如果前列的和值不为负,则将当前列的值累加到前列和值上,否则在累加之间先将当前记录和值的变量置为0。接下来,保持i值不变,变化j值,使矩阵的高度每次加1,然后再重复进行累加。最后得到最大的和值。这个过程类似于我们博客中关于一维数组求解最大和子数组的思想。

2

下面是上述算法描述的具体实现,在该实现中对当前最大和子矩阵的位置也同时进行了记录。

6 

2.2 求解横向边界上的最大和子矩阵

我们分析一下下面左侧的矩阵,黄色部分为最大和子矩阵。在分析前,我们来处理一下这个矩阵,得到右侧的扩展矩阵,其中黄色部分为最大和子矩阵。显然,为了方便求解横向边界上的矩阵,我们将原来的矩阵宽度加倍,并用原有的矩阵数据填充新的矩阵。这样的扩展使得我们在求解时,可以继续使用2.1节中的思想。但是,值得我们注意的一点是在边界处求解时,列循环索引不必从0开始,而是从2开始,这样就避免重复2.1节中的求解过程,即求解边界内的解。

3              4

下面给出了横向边界上求解的实现。

   1:          /// <summary>
   2:          /// 求矩阵左右边界处的最大值
   3:          /// </summary>
   4:          /// <param name="array">The given matrix</param>
   5:          /// <param name="n">The length of the row of the matrix</param>
   6:          /// <param name="m">The length of the column of the matrix</param>
   7:          /// <returns>The sum and coordinates of the largest-sum submatrix on the left-right borders</returns>
   8:          private Recorder lrBorderMatrixMaxSum(int[,] array,int n,int m)
   9:          {
  10:              // TODO:
  11:              Recorder tempmax = new Recorder(Int32.MinValue, 0, 0, 0, 0);
  12:   
  13:              int[,] newArray = new int[n + 1, 2 * (m + 1)];
  14:   
  15:              int column = newArray.GetUpperBound(1);
  16:   
  17:              // 使用nxm维矩阵array中的数据填充nx2m维矩阵newArray
  18:              for (int i = 0; i <= n; i++)
  19:              {
  20:                  for (int j = 0; j <= newArray.GetUpperBound(1); j++)
  21:                  {
  22:                      if (j <= m)
  23:                      {
  24:                          newArray[i, j] = array[i, j];
  25:                      }
  26:                      else
  27:                      {
  28:                          newArray[i, j] = array[i, j - m - 1];
  29:                      }
  30:                  }
  31:              }
  32:   
  33:              // 在左右边界上求最大和子矩阵
  34:              for (int c = 2; c <= m; c++)
  35:              {
  36:                  for (int i = 0; i <= n; i++)
  37:                  {
  38:                      sum.value = criteria.value = 0;
  39:                      sum.row1 = criteria.row1 = i;
  40:   
  41:                      for (int j = i; j <= n; j++)
  42:                      {
  43:                          sum.row2 = j;
  44:                          sum.col1 = sum.col2 = c + m - 1;
  45:                          sum.value = SubmatrixSum(newArray, i, j, c + m - 1);
  46:                          criteria = sum;
  47:   
  48:                          for (int k = c + m - 2; k >= c; k--)
  49:                          {
  50:                              if (sum.value < 0)
  51:                              {
  52:                                  sum.value = 0;
  53:                                  sum.col2 = k;
  54:                              }
  55:                              sum.value += SubmatrixSum(newArray, i, j, k);
  56:                              sum.col1 = k;
  57:                              if (sum.value > tempmax.value)
  58:                              {
  59:                                  tempmax = sum;
  60:                              }
  61:                          }
  62:                          if (criteria.value > tempmax.value)
  63:                              tempmax = criteria;
  64:                      }
  65:                  }
  66:              }
  67:   
  68:              return tempmax;
  69:          }

2.3 求解纵向边界上的最大和子矩阵

求解纵向边界上解的过程与横向边界上的求解过程类似,也需要对其进行一个扩展,并且行循环索引也是从2开始,并且对于第i行来说其求解范围是从第i行到第i+n-1行。

下面给出纵向边界上求解过程的实现。

   1:          /// <summary>
   2:          /// 求矩阵上下边界处的最大值
   3:          /// </summary>
   4:          /// <param name="array">The given matrix</param>
   5:          /// <param name="n">The length of the row of the matrix</param>
   6:          /// <param name="m">The length of the column of the matrix</param>
   7:          /// <returns>The sum and coordinates of the largest-sum submatrix on the up-down borders</returns>
   8:          private Recorder udBorderMatrixMaxSum(int[,] array, int n, int m)
   9:          {
  10:              // TODO:
  11:              Recorder tempmax = new Recorder(Int32.MinValue, 0, 0, 0, 0);
  12:   
  13:              int[,] newArray = new int[2 * (n + 1), m + 1];
  14:   
  15:              // 使用nxm维矩阵array中的数据填充2nxm维矩阵newArray
  16:              for (int i = 0; i <= newArray.GetUpperBound(0); i++)
  17:              {
  18:                  for (int j = 0; j <= m; j++)
  19:                  {
  20:                      if (i <= n)
  21:                      {
  22:                          newArray[i, j] = array[i, j];
  23:                      }
  24:                      else
  25:                      {
  26:                          newArray[i, j] = array[i % (n + 1), j];
  27:                      }
  28:                  }
  29:              }            
  30:   
  31:              // 在上下边界上求最大和子矩阵
  32:              for (int i = 2; i <= n; i++)
  33:              {
  34:                  sum.value = 0;
  35:                  sum.row1 = i;
  36:                  sum.row2 = i + n - 1;
  37:                  sum.col1 = sum.col2 = m;
  38:                  sum.value = SubmatrixSum(newArray, sum.row1, sum.row2, m);
  39:                  criteria = sum;
  40:   
  41:                  for (int k = m - 1; k >= 0; k--)
  42:                  {
  43:                      if (sum.value < 0)
  44:                      {
  45:                          sum.value = 0;
  46:                          sum.col2 = k;
  47:                      }
  48:                      sum.value += SubmatrixSum(newArray, i, i + n - 1, k);
  49:                      sum.col1 = k;
  50:                      if (sum.value > tempmax.value)
  51:                      {
  52:                          tempmax = sum;
  53:                      }
  54:                  }
  55:   
  56:                  if (criteria.value > tempmax.value)
  57:                      tempmax = criteria;
  58:              }           
  59:   
  60:              return tempmax;
  61:          }

2.4 其它

在上述的实现过程中,均用到了Recorder数据类型和求解子矩阵和的函数,下面给出了该方向的实现细节。

7

8 

3. 总结

上述算法的思想比较简单、直观,并且实现起来也很容易。但是,如果再进一步思考还是有可以改进的地方的。例如,在我们求解边界上的解时,我们已经想到避免重复求解的过程,但是并不彻底,仍然存在大量重复的工作。目前想到的方法是以空间换时间,就是增加一个数据结构用于存储前面计算过的子矩阵的所有元素的和值。

一个算法之所以有趣不仅在于它可以解决你的问题,更在于你可以发起挑战去证明它是否是最好的!

 

 

7月16日

TORUS系统之一:使用WPF实现最大子矩阵的3D展示

Author: Kevin(刘鑫)

1. WPF 3D编程模型

为了使用WPF进行3D编程,有必要先弄清楚WPF的3D编程模型。

WPF实质上是一个二维技术。基于panel的布局系统负责将2D元素布置到一个2D屏幕上。那么3D内容是如何融入到这个2D世界的呢?Viewport3D元素起到了2D和3D之间桥梁作用,解决转换的问题。从WPF布局系统而言,Viewport3D仅仅是一个矩形元素,它能够显示移动的图像。

使用Viewport3D需要我们做三件事:描述camera对象、light对象、3D模型。camera对象用于描述应该从哪个视点对场景进行绘制;光源(light)对象描述模型的光影效果;3D模型则是我们在场景中所要看到的目标对象,以我们作业为例就是那个轮胎(torus)。

下图描述了camera、light和model三个属性各自包含的属性值,这些属性值所代表的实际意义及如何正确地使用在《Windows Presentation Foundation Unleashed》中都有所论述。因为时间和空间的限制,这里就不再详细论述。

image

2. 实现过程

实现过程的第一步是考虑采用何种方法创建我们所需要的3D模型。就我们的作业来说,目标3D模型是一个圆环体(torus)。因此,我们有两种可选的建模方式:一种方式是以编程的方式实现目标模型;另外一种方法是通过一个第三方的3D建模工具创建一个模型,而后再将其导入到WPF中。这里要说明一点:并不是所有的3D模型都适于通过编程的方式实现,这里之所以说可以通过这种方式实现是因为torus模型相对来说还较为简单。但是,我个人仍然建议使用3D建模工具来创建你想要的模型,因为省时省力效率高。

在一些资料中,我们可以发现目前可以为WPF编程提供3D模型的建模工具并不是很多(3DSMAX, ZAM3D, SWIFT3D)。我所采用的建模工具是3DSMAX。使用该工具建立的3D模型可以通过保存为.OBJ文件的形式导入WPF工程中,WPF编程模型将会将其转换以XAML形式进行描述。转换的过程是自动完成的,因此你无须担心。下图描述了在Expression Blend中导入obj文件的情况。

1

导入模型之后,你就会在XAML文件中查看到该模型中所包括的一些模型信息,包括:Transform,Material,Geometry三种属性的信息。通过对这三种属性值的控制,你就可以实现对3D模型进行动态操作。例如你可以通过设置下图中ScaleTransform3D对象的ScaleX、ScaleY、ScaleZ这三个值实现对模型的放大和缩小的控制,也可以通过RotateTransform3D对象相应的属性实现3D模型的旋转操作。

2

在3D模型成功加载到工程中之后,你可以编译并运行你的工程。这时你看到的将是一个原始的3D模型。接下来,我们要做的就是在模型的表面进行贴图,也就是把你想要通过这个模型展示的图片贴到上图去。当我告诉你方法之后,你会发现实现它太简单了。其实,你只需要对上图中ImageBrush对象中增加并设置一个ImageSource属性值为你的图片名字就可以了。如果想动态加载你的图片,你要做的只是在后台代码中,以代码的方式设置这个属性的值,详细的我就不说了,相信都可以做到!下图是一个加载图片后的效果图。

3

3. 总结体会

个人感觉在进行设计时应该侧重使用设计软件,比如3DSMAX、Expression Blend。尤其是Expression Blend支持WPF和Silverlight设计,对于基于XAML的设计可以说使用起来很方便。基于XAML的编程方式,让编程者有一个非常清晰的结构感。

通过这次作业,对WPF有了一些认识并且也小小地积累了一些WPF的编程体验。从开始准备到最后实现,几乎每天都泡在实验室,而且几乎每天都会取得一些进展,这种感觉美妙之极。

7月15日

走进微软

Author: Kevin(刘鑫)

今天,平生第一次走进了向往已久的微软(中国),感觉与她的距离越来越近!因为有来自微软的师兄、师组陪伴,所以整个参观的过程并没有太多的陌生感,相反感觉起来更像是每周的学习讨论会。不同之处就是更轻松、更愉快!

参观过程中有两个环节,我个人比较感兴趣:一是STC的一位PM(忘记了姓名)展示Live Search,因为有一段时间没有使用Live Search了,今天发现它变了许多,暂不说它的功能变化有多大,单从UI来说,看上一眼就想试试了!不过,我还是有个小小的建议:最好能把图片搜索与Live Gallary关联在一起,这样一来,在保存图片的时候就不用每次都要右键保存,然后再指定目录那么麻烦(我的父母常常在保存后,忘记了保存的路径)!第二个比较有趣的环节是与师姐、马歆姐的交流,感觉收获不小。两位女士关于一些问题所给出的回答还是非常值得我们总结一下的。相信对于我们每一位参加交流的同学都会有一定的启发。

今天的参观总体来说,还是蛮不错的!但是,对于我个人来说还有一个小小的遗憾:没有看到Surface实物,只看到了视频!不过不要紧,一定会有机会的!至少,我相信!

下面是我们Eliteworks团队中三兄弟的照片,好可惜另外三兄弟不在:因为他们在推进我们的Elite Elevator,向他们致敬!

P1020532 

(从左至右:Allen, Kevin, Andy)

P1020534

(中国智慧,中国智造   Elite,我看行)

 

"Elite, it works!"

                      ——“Elite,我看行!”

3D Torus Silverlight版本

Author: Andy(张坚)

Kevin先前花了很多时间调研WPF和Silverlight,很早就告诉我们Silverlight内置是不支持3D的。但老师说了“总是有办法的”。于是我沿着Kevin的足迹,也在Google上检索可能的解决方案。我同样发现了Kit3D,看到了贴着图打转的立方体,看到了只有模型的3D老虎。我纳闷为何不直接给我看个贴着图的老虎,只看网上展示的例子还真不敢确定它到底现阶段是否支持在任意模型上进行纹理映射。那时已经过午夜了,我无精打采地下载Kit3D的工程源码,心里对它不是很抱希望。当我用Visual Studio打开工程看到项目里有一个Tiger.jpg时,我眼睛一亮,在看到TigerTexture.xaml这个文件名时我便确信这正是我想要的。花了一些时间配置环境后那只3D老虎终于旋转显示在网页上了。那只老虎也一直在我脑袋里转呀转呀的直到我睡着。

clip_image002[9]

第二天我把喜讯第一个告诉Kevin,他不相信说只看到老虎模型,没有贴图。我和他一起重新跑起那个例子后我们都舒心了:总算有点眉目了。接下来的工作就是要把那个轮胎的模型替换掉老虎的模型,我依次将normals、positions、textureCoords和triangleIndices的坐标信息填入源码。可是运行起来后显示的图形很怪,像被扭曲的空间。

clip_image002[11]

我以为是坐标设置错误了,花了好长时间检查坐标设置。后来才发现其实是“只缘身在此山中”,我在轮胎圈里,被轮胎围着转,自然晕了。又花了好长时间调整PerspectiveCamera的position、lookDirection、upDirection和fieldOfView等属性,直到后来耐下心来查阅WPF的书才明白了各属性的大致含义,才调出了下面这个比较像样的轮胎。

clip_image002[13]

放大和缩小功能通过调整PerspectiveCamera的fieldOfView很快就实现了。
关于能否使用轨迹球或自己计算鼠标位置来实现360翻转都因技术问题和时间限制而宣告失败,最后在Kevin的建议下用6个按钮控制X、Y和Z轴的顺逆时针操作来实现要求的翻转功能。
关于如何生成和读取用于纹理映射的MaxSubArray图片也耗了我们不少时间。最后采取的方式是这样的:通过在承载该Silverlight程序的ASP.NET页面的PageLoad事件中随机产生数据并生成MaxSubArray图片写到站点的根目录中,然后在Silverlight中便可以通过诸如http://localhost:4223/matrix.png的URI来访问到图片。这样每次刷新页面后都会有一个不同的纹理。
最后的Silverlight程序运行起来的效果如下:

clip_image002[15]

老师说对了,“总是有办法的”。

"Elite, it works!"
                        ——“Elite,我看行!”

Torus Show Download Info

Author: EliteWorks

今天是提交作业的日子,我们很高兴能够准时且保证质量的情况下完成这次作业!在完成作业的过程中,我们感受到了许久不曾有过的那份激情,也深深地体会到了WPF和Silverlight所带来的不一样的编程体验和用户体验!我们只能用一句话来形容我们所有的体会:cool!

我们按照邹老师的要求,完成了WPF和Silverlight两个版本的实现,感兴趣的同学可以在日志结束部分下载到我们的应用。我们实现的具体细节将会在后续的日志中与大家一起分享!

最后是一个小小的提议,希望老师或者同学能够在学院范围内搞一次WPF和Silverlight的软件设计比赛!其实,我们可以改变用我们的IDEA去改变这个世界!相信我们可以做到!

 

下载地址:WPF Implementation!  Silverlight2 Implementation!

"Elite, it works!"

                      ——“Elite,我看行!”

Python与公共单词问题

Author: Andy(张坚)

看到题目后我的第一反应是用哈希表,这样可在常量时间内查找元素。为了尽快向组员传达解决的思路,情急之中我用Python写下了对于通常情况下的输入的解决方案:

    def find_common_words(a, b):
        seta = set(a.split(" "))
        setb = set(b.split(" "))
        result = seta & setb
        return list(result)

基本思想就是求集合的交集。Python中的set是无序的,可以在线性时间内求交集。但组员告诉我标准C++ STL中的map和set都是有序的,查找元素需要对数时间,好像没有Java中的HashMap和HashSet的对应实现。后来发现Visual C++里自带了HashMap的实现,但由于时间紧迫,我们到交卷时都没完成实现。无奈之下就请示了老师可否使用其它语言实现,在得到允许之后我们交了最初的Python版本。这个简陋的版本的测试用例通过率为60%。分析了失败的测试用例后发现是没有考虑字符串的首尾空格,单词之间的多个空格,以及将空字符串也认为是公共单词这3个特殊情况造成的。下面是修复后的代码,其中strip方法用来去掉字符串首尾的空格,正则表达式模块re中的split方法使用\s+来分割由多个空格分隔的字符串。

    import re
    def find_common_words(str_a, str_b):
        seta = set(re.split(r'\s+', str_a.strip()))
        setb = set(re.split(r'\s+', str_b.strip()))
        setc = seta & setb - set([""])
        return list(setc)

可以发现短短6行代码就给出了测试用例通过率为100%的实现,我想大家一定也体会到了动态语言在这方面的威力。

Python作为动态语言中的杰出代表,无论是在科学计算、网络编程,还是在算法设计与描述、原型实现等方面都扮演着越来越重要的角色。相比Perl,Python显得更优雅飘逸;相比Ruby,Python显得更朴实严谨。如果之前没有动态语言背景,我个人强烈推荐Python作为C++/Java/C#这样的主力开发语言之外的工作语言,这正是编程语言中所谓的“动静相宜”。


"Elite, it works!"

                          ——“Elite我看行!


7月2日

数组子序列求和问题!

Author: Kevin (刘鑫)

问题描述

有一整型数组,其中元素可正、可负、也可为零。要求在该数组中找到一个子序列,其和在所有子序列中最大,并将该最大值及该子序列的起始和终止位置输出。

算法描述

1.max记录和的最大值,其初始值为矩阵的第一个元素;

2.sum记录当前的和;

3.遍历整个数组

3.1 将当前指向的数组元素累加到sum中;

3.2 判定sum是否大于max;如果大于就把sum值赋给max,并将end指向当前位置;

3.3 若上述条件不成立,则判定sum是否小于零;如果小于零,就说明前面所有元素的和对后面的求和过程起到消减的作用,就抛弃掉,即把sum赋值为零,并且将记录子序列起始位置的变量值修改为当前数组索引位置的下一个位置。

4.遍历结束后,输出结果{max,start,end}

上述算法的时间复杂为O(n),即在线性时间内可完成作业。

算法实现1

MaxSeq01

实现1存在的问题

我使用了一些测试用例对上述实现进行了测试。其中,当array = {-1,0,0}时,程序的输出为(max,start,end) = (-1,0,2),很明显并未通过该次测试。在第20行添加一个断点,对其进行调试。在调试的过程发现:在第一轮循环过程中,因为(sum,max)=(-1,-1),所以判定分支将进入路径P1={23-24-25-26-27-28-34},可以发现这一过程没有移动起始标志的操作,也没有将sum置为零的处理。这样一来,在接下来的几轮循环中,程序的控制逻辑将始终不会进行判定分支路径P2={29,30,31,32,33},从而记录起始位置的变量将始终不会发生变化。而事实上,在完成第一轮循环时,记录子序列起始位置的变量的值就应该增一,即向后移动一位。

算法实现2

在算法实现1的基础上进行改进。橙色矩形中为新增代码。

MaxSeq02

加入新的代码后,在array = {-1,0,0}时,程序的输出为(max,start,end) = (0,1,2)。问题解决!!!

测试用例执行情况

下面列举了算法实现1与实现2的测试情况表。

MaxSeqTestResult

上表中的12个测试用例基本通过。在表中可以看到测试用例TC8的实际结果与预期结果不一致。经过分析,如果程序达到完美输出的话,应该将满足最大值条件且长度相同的子序列全部输出,但是因为当前实现的程序只能输出一个结果值,因此还是有可改进空间的。

总结

经过课堂上的这次小测验,感觉以前学习到的知识单从文字上来说,好像已经明白了,但实际上只是理解了主线上的内容。当亲自动手将它转换成实际代码时,却发现还有许多细节之处没有掌握,这也许就是与他人存在差距的地方,也是需要弥补的地方。

这里特别感谢TA周智毅,及时、准确地发现了程序中的缺陷!特此感谢!

6月30日

初探Silverlight (1): Hello, the Silverlight World!

Author: Kevin(刘鑫)

images

什么是Silverlight?

Silverlight是一种跨浏览器、跨平台、跨设备的插件,用于发布下一代基于.NET的多媒体体验及针对Web的交互式应用。

准备工作

在开始你的旅程之前,你需要安装Visual Studio 2008开发平台,并且在这里下载Silverlight Tools Beta 2 for Visual Studio 2008。

如果你已经万事俱备,我们就开始令人兴奋的Silverlight之旅。

探险1: Hello, the Silverlight World!

有过编程经验的人都会对“Hello World”无比熟悉,为了让你减少陌生感,我们依然从一个小巧的“Hello World”程序开始,但是这一次你敲开的将是一个不一样的世界之门!

首先,打开你的Visual Studio 2008(以下简称vs2008,哈哈,我是一个懒人!),并创建一个新的项目,项目模板选择Silverlight Application,如图所示:

pic001

当你选择完项目模板,别忘记在“Name”栏中为你心爱的项目起个漂亮的名字!接下来,请点击[OK]。这时会弹出一个新的窗口,如下图所示:

pic002

因为Silverlight自身不能独立运行,因此它需要一个承载器,在这承载器之上运行。这里,我们在“Project Type”栏中选择“Web Application Project”做为承载器。设定完成后的情况,如下图所示:

pic003

完成上述设置后,点击[OK],vs2008将为你创建指定的Silverlight项目。项目创建完成后,你可以在Solution Explorer中看到如下情景(根据你定义的项目名称的不同,一些名称信息会有所区别)。

pic004

在此处,我想请你注意一件事情。从上图中,我们可以看到ClientBin文件夹中当前不包含任何文件。那么,现在请你和我一起Build刚刚建立的项目。下面是项目build之后的情况。

pic005

现在,你可能已经发现了一些问题。是的,在ClientBin文件夹下多出了一个后缀名为xap的文件。xap文件实际上就是一个标准的.NET程序集。当你编译项目时,所有的XAML文件和资源文件都会被包含在里面。XAP文件采用了标准的ZIP压缩算法。你可以拷贝一份XAP文件,然后将后缀名改为ZIP,接着解压这个ZIP文件。以上述项目为例,你会在解压的文件夹下发现如下图中所示的两种类型的文件:

pic006

下面,我们开始为这个程序加入实质性的内容,来打个招呼吧!

首先打开Page.xaml文件,切换到XAML视图。在未添加任何内容前,我的XAML视图中有如图所示的代码。这些代码是由vs2008自动创建的。

pic007

在上面代码的基本之上,我们来加入一些代码来创建一个Button,并且设置Button的名字为btnHello,Button上显示的内容为“Hello, the Silverlight World!”,Button的宽度和高度分别为100和50,Button所显示的内容的字体颜色为“Chocolate”。

pic008

加入上述代码后,运行该项目,我们会看到如下的情况:

pic009

接下来,我们为这个Button添加一个Click事件,以响应点击操作。首先,在Page.xaml视图中,加入事件代码,如图所示:

pic010

然后,在Solution Explorer中选择打开Page.xaml.cs文件。在这个文件中,我们可以看到vs2008已经自动为我们创建了btnHello_Click事件处理函数。

pic011

现在事件处理函数中加入相应的处理代码。

pic013

OK,加入事件处理代码后,我们再次运行我们的项目。这次,点击一下按钮试试,你会发现有变化了吧!

pic012

“Welcome to Silverlight World”!如果你看到了这句话,那真的应该恭喜你,你已经进入了Silverlight的世界!

结束语

在实现的过程,你可能会有一个问题:为什么你的IE中显示的区域不是整个IE窗口,而是在左上角的一块区域呢?其实这不是一个问题!只所以会显示在左上角的一块区域是因为在Page.xaml文件的UserControl这个标签内,vs2008缺省地对控件的Width和Height两个属性进行了设置。如果你想让它完整地显示在IE的页面中,你只需将这两个属性删除即可!

6月29日

电梯类设计

Author: Aaron ( 程博文 )

我们开发组这边首先就前两天电梯系统的状态图进行了讨论,当把电梯的状态理解清楚以后,后面的设计就变得很简单了。下面是我们更新以后的状态图(由于只涉及了电梯类的实现,所以并没有考虑很复杂的电梯调度算法,因此在决定电梯到底是向上或向下移动的处理上并不是很完善)

                                     未命名
在设计时,我们考虑到电梯的运动过程实际上与调度算法有很大的关联。而对电梯本身而言,只有向上、向下或者空闲等待的过程。而具体的移动方式实际上是由电梯的调度类实现的。因此,在设计电梯类的过程中,我们并不考虑它到底是按照哪种方式进行调度的,而是提供移动(向上或者向下),开门,关门,报警等方法,而把控制电梯运行方式的任务交给另外的调度类实现(实现的方法可以简单也可以复杂,但是与电梯类的设计关联并不大)。至于调度类的实现方式,我们打算放在UI层上实现具体的调度算法。

下面再说说这个电梯类是如何设计的,首先我们要根据电梯的状态图划分成不同的状态。经过对状态图的分析,我们确定了电梯的16种状态,分别是:
   enum e_ELEVATOR_STATE
   {
        ES_IDLE = 0,        //Elevator is idle, waiting person come in
        ES_F1,            //Elevator at floor 1
        ES_F2,            //Elevator at floor 2
        ES_F3,            //Elevator at floor 3
        ES_F4,            //Elevator at floor 4
        ES_F5,            //Elevator at floor 5
        ES_NORMAL,    //Elevator is normal
        ES_UP,            //Elevator is up running
        ES_DOWN,        //Elevator is down running
        ES_CLOSED,        //Elevator is closed
        ES_CLOSING,        //Elevator is closing
        ES_OPENED,        //Elevator is opened
        ES_OPENING,        //Elevator is opening
        ES_OVERWEIGHT,    //Elevator is over weight
        ES_OVERTIME,    //Elevator is over time
        ES_ERROR,        //Error existed..
    };
在确定了状态的基础之上,我们设计了电梯类需要使用的数据结构。由于设计时我们需要考虑对于多电梯的支持,所以在设计电梯类的数据结构的时候,我们使用了4个队列,分别是:上升请求队列,下降请求队列,上升请求临时队列,下降请求临时队列。设置零时队列的目的,就是为调度类实现具体的调度算法时考虑的。对于每个电梯电梯而言只负责相应上升请求队列和下降请求队列即可。而由具体的调度类负责将每个上升请求临时队列和下降请求临时队列中的请求分派到某个电梯响应。

另外就是一些开门,关门,报警,添加乘客的方法和一些关键时间点的属性。具体的定义参见下面的类定义:

class CElevator
{
private:
    unsigned long __ul_Elevator_Open_Time;
    unsigned long __ul_Elevator_Close_Time;
    unsigned long __ul_Elevator_Get_State_Time;
    int  __i_Elevator_Current_Weight;
    e_ELEVATOR_STATE __i_Elevator_Current_State;
    e_ELEVATOR_STATE __i_Elevator_Current_Direction;
    bool *__b_Elevator_Floor_Up_Queue;
    bool *__b_Elevator_Floor_Down_Queue;
    bool *__b_Elevator_Floor_Up_Temp_Queue;
    bool *__b_Elevator_Floor_Down_Temp_Queue;
    bool __b_If_Elevator_Queues_Empty();        //To found if the elevator queues is empty
    e_ELEVATOR_STATE __ES_Elevator_Floor_State();    //To get each floor's state

   
public:
    static const int sm_Elevator_Speed_Time = 5000;        //The time between two floors is 5m
    static const int sm_Elevator_Auto_Close_Time = 5000;    //The time to auto close the door is 5m
    static const int sm_Elevator_Open_Close_Time = 2000;    //The total time to open or close the door is 2m
    static const int sm_Elevator_Overtime = 10000;            //The normal waiting time is 10m
    static const int sm_Elevator_Floor_Num = 5;        //The total floors is 5
    static const int sm_Elevator_Over_Weight = 500;    //The max weight of elevator is 500

    CElevator();
    ~CElevator();

    void Open();    //Open elevator's door
    void Close();    //Close elevator's door
    e_ELEVATOR_STATE Add_Waiting_Elevator_Request(e_ELEVATOR_STATE direction, int floor);        //Add Waiting Request to elevator
    e_ELEVATOR_STATE Remove_Waiting_Elevator_Request(e_ELEVATOR_STATE direction, int floor);    //Remove Waiting Request to elevator
    e_ELEVATOR_STATE Get_Current_State();    //Get elevator's current state
    e_ELEVATOR_STATE Add_Passenger(int weight);        //Add a passenger to elevator
    e_ELEVATOR_STATE Remove_Passenger(int weight);    //Remove one passenger
    e_ELEVATOR_STATE Alarm();    //External alarm evoked
};

以上就是我们对于电梯类的设计。

                                       “Elite, it works!”

                                              ——Elite,我看行!