个人第2次作业:熟悉使用工具
第二次作业
GIT地址 | https://github.com/swpuliuchuan |
GIT用户名 | swpuliuchuan |
学号后5位 | 24140 |
博客地址 | https://www.cnblogs.com/swpulc/ |
作业链接 | https://edu.cnblogs.com/campus/xnsy/GeographicInformationScience/homework/7582 |
Part1.配置环境
环境:Visual Studio 2017
Part2.克隆项目
- 1.登录自己的Github,将阿超的四则运算库拷贝到自己的同名仓库中
- 2.安装Git后克隆项目到本地文件夹D/work2
Part3.程序设计
-
1.设计思路
流程图
程序的关键是随机生成N道题目和计算结果
随机生成N道题目,可以用C#的Random函数来实现,当代码写完的时候才发现这是“伪随机数”,于是百度了一下,借鉴了https://blog.csdn.net/xiaochenXIHUA/article/details/89574431这篇文章,改进了代码。利用Guid生成种子产生了新随机数。随机数既然解决了,再利用switch分支就可以生成题目。
计算结果,刚拿到手里几乎是迷茫的,因为题目的括号,运算符号的优先级并不知道如何处理,后来请教了其他同学,可以利用二叉树来实现,例如:3+2*9-16/4可以用二叉树形式来表达。
计算过程*:“ * ”号结点的左右孩子均为数字,可以直接计算,为18,接下来计算“+”,为21,然后计算“/”,为4,最后计算根结点的“-”,最终结果为17。遵循着这个算法可以看出:操作数都是叶子结点,运算符都为内部结点,而且运算符优先级较高的都在下方。知晓了大概的做法后,开始编写代码。 -
2 代码
获取随机数
static int GetRandomSeed()
{
byte[] bytes = new byte[4];
System.Security.Cryptography.RNGCryptoServiceProvider rng = new System.Security.Cryptography.RNGCryptoServiceProvider();
rng.GetBytes(bytes);
return BitConverter.ToInt32(bytes, 0);
}
public static char getOperator()
{
char operator1 = '0';
Random ran = new Random(GetRandomSeed());
int i = ran.Next(4);
switch (i)
{
case 0:
operator1 = '+';
break;
case 1:
operator1 = '-';
break;
case 2:
operator1 = '*';
break;
case 3:
operator1 = '/';
break;
}
return operator1;
}
/*
* 根据范围获取随机数
*/
public static int getNumber(int max)
{
int number = 0;
Random ran = new Random(GetRandomSeed());
number = ran.Next(max + 1);
return number;
}
/*
* 随机确定子结点
*/
public static bool[] getChildPlace(int num)
{
int d = 0;
int size = 0, j = 1;
while (num >= (int)Math.Pow(2, j))
{
j++;
}
d = (int)Math.Pow(2, j) - 1 - num;
size = (int)Math.Pow(2, j - 1);
bool[] k = new bool[size];
for (int i = 0; i < size; i++)
{
k[i] = true;
}
for (int i = 0; i < d; i++)
{
Random ran = new Random(GetRandomSeed());
int f = ran.Next(size);
while (k[f] == false)
{
f = ran.Next(size);
}
k[f] = false;
}
return k;
}
}
计算
/*
* 得到运算结果,并检验:
* 1.除数为0
* 2.不能整除
* 出现以上两种情况的话将该运算符转换成其他三种运算符
*/
public String getResult()
{
if (hasChild())
{
switch (str)
{
case "+":
return (int.Parse(getLchild().getResult()) + int.Parse(getRchild().getResult())).ToString();
case "-":
return (int.Parse(getLchild().getResult()) - int.Parse(getRchild().getResult())).ToString();
case "*":
return (int.Parse(getLchild().getResult()) * int.Parse(getRchild().getResult())).ToString();
case "/":
if (getRchild().getResult().Equals("0"))
{
while (str.Equals("/"))
{
str = (Ran.getOperator().ToString());
}
return this.getResult();
}
else if (int.Parse(getLchild().getResult()) % int.Parse(getRchild().getResult()) != 0)
{
while (str.Equals("/"))
{
str = (Ran.getOperator()).ToString();
}
return this.getResult();
}
else
return (int.Parse(getLchild().getResult()) / int.Parse(getRchild().getResult())).ToString();
}
}
return str;
}
public String toString()
{
String Lstr = "", Rstr = "", Str = "";
if (hasChild())
{
//判断右子树
if (getRchild().hasChild())
{
//判断左邻括号的运算符是否为'/'
if (str.Equals("/"))
{
//获取右子树的表达式
Rstr = getRchild().toString();
}
//判断左邻括号的运算符
else if (str.Equals("*") || str.Equals("-"))
{
//判断op运算符
if (getRchild().str.Equals("+") || getRchild().str.Equals("-"))
{
Rstr = getRchild().toString();
}
else
{
//获取右子树的表达式
Rstr = getRchild().toString().Substring(1, getRchild().toString().Length - 2);
}
}
else
{
//去掉括号
Rstr = getRchild().toString().Substring(1, getRchild().toString().Length - 2);
}
}
else
{
Rstr = getRchild().str;
}
if (getLchild().hasChild())
{
if (str.Equals("*") || str.Equals("/"))
{
if (getLchild().str.Equals("+") || getLchild().str.Equals("-"))
{
Lstr = getLchild().toString();
}
else
{
Lstr = getLchild().toString().Substring(1, getLchild().toString().Length - 2);
}
}
else
{
Lstr = getLchild().toString().Substring(1, getLchild().toString().Length - 2);
}
}
else
{
Lstr = getLchild().str;
}
//获取当前的运算式,并加上括号
Str = "(" + Lstr + str + Rstr + ")";
}
else
{
//判断是否有孩子
Str = str;
}
return Str;
}
public bool hasChild()
{
if (lchild == null && rchild == null)
return false;
else
return true;
}
}
输出到txt文件
FileStream fs = new FileStream(@"D:\Calculator\Output.txt", FileMode.Create);
//创建文件流
StreamWriter sw = new StreamWriter(fs);
int z;
BinaryTree bt;
Console.WriteLine("How many questions do u need?");
z = int.Parse(Console.ReadLine());
for (int i = 0; i < z; i++)
{
bt = new BinaryTree(2);
bt.createBTree();
String result = bt.CalAndVal();
Console.WriteLine(bt.ToString() + "=" + result);
sw.WriteLine(bt.ToString() + "=" + result);
}
sw.WriteLine(" Time:" + DateTime.Now.ToString());
//清空缓冲区
sw.Flush();
//关闭文件流
sw.Close();
fs.Close();
结果
Part4.单元测试
- 新建单元测试
- 添加引用
单元测试内容,随机生成的符号是否正确
Part5.效能分析
修改主函数
打开效能工具
点击详细报告
可以得到代码中每个函数被利用的次数,如果能够减少次数并且达到同样的目的,是可以节约电脑的损耗和时间,这就是优化代码和采用更优算法的结果。
可以看到我所写的生成二叉树的方法,所耗用的时间占到了百分之52(使用了324毫秒的CPU时间),如果能够换个思路来写或者将一些占用时间的外部方法改进,也许能够减少使用CPU时间。
Part6.提交代码
Part7.总结与分享
初次使用Git还是有着诸多的不习惯,主要还是英语太差了,编程的能力也下滑了不少,不过通过这次作业还是学习到了git的基本操作,下次提交应该会熟练很多。