熟悉使用工具
作业属性
作业课程 | 系统分析与设计 |
---|---|
作业要求 | 第二次个人作业 |
作业目标 | 熟悉工具 |
学号 | 201731062416 |
GitHub仓库地址 | 仓库地址 |
GitHub用户名 | zofun |
博客地址 | 博客园博客 |
环境配置
vs的安装
vs以前已经安装过了。vs的安装比较简单,下载vs的安装器,然后选择合适的工作负载,按照提示安装即可。
git的安装
git以前也安装过,git的安装也比较简单,无需复杂的配置。
以下是具体的过程:
- 去官网下载安装包
- 按照引导安装
- 安装成功后,进行简单的配置
因为以前已经配置过了,这里只是作为安装过程的回顾。
git config --global user.name 'zofun'
git config --global user.email '1483674736@qq.com'
代码思路与编码
读题与思考
题目要求开发一个四则运算生成器,它有几个要求;
- 接受命令行参数,生成指定个数的题目
- 只能包含加减乘除,且题目和结果均不能出现小数。
- 每个数字0-100之间
- 运算符在2-3个之间
- 输出题目和结果到一个txt文件中。
其中其它几项要求都很容易实现,困难的是如何处理满足除法不能出现小数,并保证除法的数量不会太少的问题,尽可能地保证各种预算出现的频率一致。(因为生成除法的条件显然要苛刻许多,如果单纯使用随机生成的话,可能除法很少出现)。
为了解决运算符出现的频率一致的问题,在生成题目的时候,首先生成运算符,然后反复生成运算数,直到满足条件。
编码前的简单设计
一个好的设计应该是“对修改关闭,对拓展开放的“。要能够较好的应对需求的变动。显然一把梭式的代码,是不合理的。为了方便拓展程序可以生成不同类型的题目,我使用了策略模式,主要的类图如下。
使用策略模式可以很方便的拓展题目的生成策略,当生成的题目的要求发生变动时,可以在不修改源代码的情况下,完成功能的拓展。
在生成题目时需要计算结果,为了解决结果的计算问题,我首先查找了C#的相关库,发现并没有类似python中eval()
使用很简单的方法,为此我使用了数据结构中学到的逆波兰式求解方法。
编码
代码较多,参见附录。
运行截图
控制台:
生成的题目文件;
git和GitHub的使用
GitHub的使用
- 将阿超的仓库fork到自己的同名仓库中
- 进入自己的仓库列表中参看fork的仓库
使用git将本地的项目push到GitHub
- 找到仓库的url
- 使用git clone到本地
- 在clone下来的文件夹中创建一个名为zofun的文件夹,并将项目文件放入
- 将刚才加入的项目文件夹添加到版本控制中
- 提交本次修改
- 推送到GitHub
- 进入GitHub确认
提交成功 - 合并到阿超的仓库
单元测试和回归测试
单元测试
我在单元测试时,主要测试的是我自己写的四则运算的代码。
- 在待测试的类中右键创建单元测试
- 解决方案中出现单元测试
- 编写单元测试的代码
- 运行测试代码,查看测试结果
- 定位问题,修改代码
打上断点,单步运行,定位问题。
单步运行
发现是运算符优先级比较时出现错误 - 修改代码
- 再次测试
测试通过
回归测试
单元测试后,对相关代码进行小调整后,进行回归测试
并没有引入新的错误
效能分析
- 利用vs自带的性能探查工具
- 查看分析结果
感悟和分享
- 用好单步调试可以帮助我们在较为复杂的程序中快速的定位问题。在加上单元测试的加持,可以避免我们每次修改后都需要手动去输入测试用例的麻烦,可以极大的提高开发的效率。
- 在使用单元测试时,编写一组好的测试用例也是非常重要的。比如,我的代码中因为比较运算符优先级的代码出现了错误,但是这个错误只会在表达式中出现乘法和除法的组合运算时才会出现,所以只有合适的测试用例才能发现这个问题,所有编写一个好的测试用例很重要,他要能够尽可能地覆盖代码地所有分支,模拟可能出现地多种情况。
- git和GitHub是一组非常实用地工具。在之前我也经常使用git和GitHub,它可以帮助多人协作进行版本控制,免去了互相传源代码的麻烦。并且git地一般使用非常地简单,只有代码发生冲突时处理起来稍微麻烦一点。
- 在编写代码之前,应该要比较仔细地考虑,设计一个好的结构,分好模块。之后再去写代码,思路就可以更加地清晰。这样也能更好地利用好面向对象地优势,避免一个类从头写到尾地情况。
- 在编写代码中,应该注意编码的规范。一些优秀的开源项目就是最好的教材。好的命名,恰到好处的注释。都能提高代码的可读性。
- 用好调试功能对于编程非常的重要,调试功能可以帮助我们快速的定位到问题。
- 在学习一门编程语言时,除了基础语法,学习其中类库的使用也是非常重要的。在编写这个四则运算题生成器的时候,因为之前很少使用C#,所以很多类库的使用方法都不清楚,为此多花费了许多的时间。
- 通过这次的学习,发现了vs许多强大的功能,像性能探查工具等,不愧是地表最强IDE。同时也激起了我探索我熟悉的IDEA的热情。用好工具对应编程也非常的重要。
- 数据结构真的很重要啊。在这次作业中,我使用了数据结构曾经讲过的逆波兰式求解复杂表达式的算法,自己写的时候才发现漏洞百出,光一个逆波兰式就花了整个作业绝大多数的时间。
代码附录
Arithmetic接口
Arithmetic接口,定义了生成题目的方法
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace zofunCalculator
{
public interface Arithmetic
{
Question MakeQuestion();
}
}
SimpleQuestionBuilder类
实现了简单的四则运算题目生成算法
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace zofunCalculator
{
/// <summary>
/// 简单四则运算生成器,即只含两个运算符
/// </summary>
public class SimpleQuestionBuilder : Arithmetic
{
private Random random = new Random();
private char[] symbol = new char[4] { '+', '-', '*', '/' };
public Question MakeQuestion()
{
//生成运算符
char operA = symbol[random.Next(4)];
char operB = symbol[random.Next(4)];
int numA;
int numB;
int numC;
string expression;
float answer;
//为了避免除法太少,所以出现小数时,不改变运算符,改变运算数
while (true)
{
//生成运算数
numA = random.Next(100);
numB = random.Next(100);
numC = random.Next(100);
expression = numA.ToString() + operA + numB.ToString() + operB + numC.ToString();
try
{
//捕获表达式中除数为零的异常
answer = AnswerUtil.calculate(expression);
}
catch
{
//该表达式不合法,重新生成
continue;
}
//这里限制了结果的最大值不能超过300
if (answer<300&&answer>=0&&!answer.ToString().Contains('.'))
{
break;
}
}
return new Question(expression, (int)answer);
}
}
}
MediumQuestionBuilder
实现了中等难度的四则运算生成算法
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace zofunCalculator
{
/// <summary>
/// 中等难度四则运算生成器,即包含三个运算符
/// </summary>
class MediumQuestionBuilder : Arithmetic
{
private Random random = new Random();
private char[] symbol = new char[4] { '+', '-', '*', '/' };
public Question MakeQuestion()
{
//生成运算符
char operA = symbol[random.Next(4)];
char operB = symbol[random.Next(4)];
char operC = symbol[random.Next(4)];
int numA;
int numB;
int numC;
int numD;
string expression;
float answer;
//为了避免除法太少,所以出现小数时,不改变运算符,改变运算数
while (true)
{
//生成运算数
numA = random.Next(100);
numB = random.Next(100);
numC = random.Next(100);
numD = random.Next(100);
expression = numA.ToString() + operA + numB.ToString() + operB + numC.ToString()+operC+numD.ToString();
try
{
//捕获表达式中除数为零的异常
answer = AnswerUtil.calculate(expression);
}
catch
{
//该表达式不合法,重新生成
continue;
}
//这里限制了结果的最大值不能超过300
if (answer < 300 && answer >= 0 && !answer.ToString().Contains('.'))
{
break;
}
}
return new Question(expression, (int)answer);
throw new NotImplementedException();
}
}
}
Context
策略模式的环境类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace zofunCalculator
{
class Context
{
private Arithmetic arithmetic;
public Context(Arithmetic arithmetic)
{
this.arithmetic = arithmetic;
}
public Question buildQuestion()
{
return this.arithmetic.MakeQuestion();
}
}
}
AnswerUtil
计算四则运算结果的工具类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace zofunCalculator
{
/// <summary>
///利用逆波兰式求解
/// </summary>
public static class AnswerUtil
{
//表达式中可能出现的符号
private static char[] symbol = new char[] { '+', '-', '*', '/', '(', ')' };
/// <summary>
/// 将表达式中拆分为数字和运算符
/// </summary>
/// <param name="expression">待拆分的表达式</param>
/// <returns></returns>
private static List<string> Split(string expression)
{
List<string> list = new List<string>();
char[] chars = expression.ToCharArray();
string value = "";
foreach(char ch in chars)
{
//当前字符是符号
if (symbol.Contains(ch))
{
if (!"".Equals(value))
{
//保留之前的结果
list.Add(value);
}
list.Add(ch.ToString());
value = "";
continue;
}
value += ch;
}
if (!"".Equals(value))
{
list.Add(value);
}
return list;
}
/// <summary>
/// 判断A的运算优先级是否高于B,是返回true,否则返回false
/// </summary>
/// <param name="A"></param>
/// <param name="B"></param>
/// <returns></returns>
private static bool priority(string A,string B)
{
if (("*".Equals(A) || "/".Equals(A)) && (!"*".Equals(B) && !"/".Equals(B)))
{
return true;
}
if (("*".Equals(A) || "/".Equals(A)) && ("*".Equals(B) || "/".Equals(B)))
{
return true;
}
if (("+".Equals(A) || "-".Equals(A)) && (!"*".Equals(B) && !"/".Equals(B)))
{
return true;
}
else
{
return false;
}
}
/// <summary>
/// 将中坠表达式准换为后缀表达式
/// </summary>
/// <param name="exp"></param>
/// <returns></returns>
private static List<string> PrefixToSuffix(List<string> exp)
{
List<string> list = new List<string>();
Stack<string> stack = new Stack<string>();
foreach(string item in exp)
{
if (!symbol.Contains(item.ToCharArray()[0]))
{
//数字直接加入list
list.Add(item);
continue;
}
if (item.Equals("(") || stack.Count==0)
{
stack.Push(item);
continue;
}
if (item.Equals(")"))
{
while (!stack.Peek().Equals("("))
{
list.Add(stack.Pop());
}
stack.Pop();
continue;
}
while (priority(stack.Peek(), item))
{
list.Add(stack.Pop());
if (stack.Count==0)
break;
}
stack.Push(item);
}
while (stack.Count!=0)
{
list.Add(stack.Pop());
}
return list;
}
/// <summary>
/// 使用逆波兰式的方法计算复杂表达式
/// </summary>
/// <param name="exp"></param>
/// <returns></returns>
public static float calculate(string exp)
{
List<string> list=PrefixToSuffix(Split(exp));
Stack<string> stack = new Stack<string>();
foreach(string item in list)
{
if (!symbol.Contains(item.ToCharArray()[0]))
{
stack.Push(item);
continue;
}
else
{
//如果是操作符,则弹出栈顶元素,计算后放入
float one = float.Parse(stack.Pop());
float two = float.Parse(stack.Pop());
float value = 0;
if (item.Equals("+"))
{
value = two + one;
}
else if (item.Equals("-"))
{
value = two - one;
}
else if (item.Equals("*"))
{
value = two * one;
}
else if (item.Equals("/"))
{
if (one == 0)
{
throw new Exception("除数不能为零");
}
value = two / one;
}
stack.Push(value.ToString());
}
}
return float.Parse(stack.Pop());
}
}
}
FileWriteUtil
文件操作工具类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
namespace zofunCalculator
{
/// <summary>
/// 写文件工具类
/// </summary>
public static class FileWriteUtil
{
public static void writer(List<Question> list)
{
string path = "question.txt";
StreamWriter sw = new StreamWriter(path, false);
sw.Write("");
sw.Close();
sw = new StreamWriter(path, true);
foreach(Question q in list)
{
sw.WriteLine(string.Format("{0,-30}答案:{1,-10}",q.getExpression()+"=",q.getAnswer()));
}
sw.Close();
}
}
}
Question
四则运算类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace zofunCalculator
{
public class Question
{
private string expression;
private int answer;
public Question(string expression,int answer)
{
this.expression = expression;
this.answer = answer;
}
public int getAnswer()
{
return answer;
}
public string getExpression()
{
return this.expression;
}
}
}
Program
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace zofunCalculator
{
class Program
{
static void Main(string[] args)
{
List<Question> list = new List<Question>();
Context easy = new Context(new SimpleQuestionBuilder());
Context medium = new Context(new MediumQuestionBuilder());
Random random = new Random();
Console.WriteLine("请输入想要生成的题目的数量:");
int count = int.Parse(Console.ReadLine());
for(int i = 0; i < count; i++)
{
int n = random.Next(2);
if (n == 0)
{
list.Add(easy.buildQuestion());
}
else
{
list.Add(medium.buildQuestion());
}
}
//写入文件
FileWriteUtil.writer(list);
Console.WriteLine("生成成功,按任意键退出");
Console.ReadLine(); //防止退出
}
}
}