C# 四则运算 软件工程--花生米

四则运算软件工程

1. github项目地址

https://github.com/Sranmi/Calculation

2. 项目要求
  • 1 题目:实现一个自动生成小学四则运算题目的命令行程序。
  • 2 说明
    +自然数:0, 1, 2, …。
    真分数:1/2, 1/3, 2/3, 1/4, 1’1/2, …。
    运算符:+, −, ×, ÷。
    等号:=。
    分隔符:空格(用于四则运算符和等号前后)。
    算术表达式:
    e = n | e1 + e2 | e1 − e2 | e1 × e2 | e1 ÷ e2 | (e),
    其中e, e1和e2为表达式,n为自然数或真分数。
    四则运算题目:e = ,其中e为算术表达式。

  • 3 需求
  1. 使用 -n 参数控制生成题目的个数,例如
    Myapp.exe -n 10
    将生成10个题目。
  2. 使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围,例如
    Myapp.exe -r 10
    将生成10以内(不包括10)的四则运算题目。该参数可以设置为1或其他自然数。该参数必须给定,否则程序报错并给出帮助信息。
  3. 生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1 − e2的子表达式,那么e1 ≥ e2。
  4. 生成的题目中如果存在形如e1 ÷ e2的子表达式,那么其结果应是真分数。
  5. 每道题目中出现的运算符个数不超过3个。
  6. 程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目。例如,23 + 45 = 和45 + 23 = 是重复的题目,6 × 8 = 和8 × 6 = 也是重复的题目。3+(2+1)和1+2+3这两个题目是重复的,由于+是左结合的,1+2+3等价于(1+2)+3,也就是3+(1+2),也就是3+(2+1)。但是1+2+3和3+2+1是不重复的两道题,因为1+2+3等价于(1+2)+3,而3+2+1等价于(3+2)+1,它们之间不能通过有限次交换变成同一个题目。
  7. 生成的题目存入执行程序的当前目录下的Exercises.txt文件,格式如下:
    /1. 四则运算题目1
    /2. 四则运算题目2
    ……
    其中真分数在输入输出时采用如下格式,真分数五分之三表示为3/5,真分数二又八分之三表示为2’3/8。
  8. ✓在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件,格式如下:
    /1. 答案1
    /2. 答案2
  9. 程序应能支持一万道题目的生成。

    3.预计耗费时间

    1482025-20180930200155771-1490837545.png

4.解题思路
  • 计算数和运算符的存储:将计算数分子分母分别存入一个二维数组中,运算符存入字符数组。
  • 在主函数实现大致过程:用户参数及相关内容输入,调用生成题目及答案函数,并调用查重函数,生成相应r,n的题目和答案并使用数据流存入txt文件中。
  • 在计算过程中负号运算时,若有负数生成,则题目重新生成
  • 真分数除了将分子大于分母的约分成a'b/c的形式之外,还要注意约分。 a/b :若a>b,则输出形式为 a/b‘ a%b/b
  • 运算符不超过三个,即计算式不超过4个,第一个和最后一个不为0,中间两个数字由随机数(0,1)决定有无,运算符则由计算数的数量决定。
  • 查重方面,选择了难易换取时间,简单的将每道计算式中的计算数相加,若与之前生成的题目相加数相同,则舍去,重新生成。
  • 生成的题目存入指定目录下的Exercises.txt文件,同时计算出所有题目的答案,存入指定目录的Answers.txt文件。
  • 程序应能支持一万道题目的生成。

    5.设计实现过程
  • 在类Program中main函数:获得相关内容输入,调用calculation.GetProblem,该函数中包含了生成计算式及计算结果的主要步骤包括辅助查重功能,用StreamWrite数据流将相应r,n的题目和答案分别存入txt文件中。
  • 通过GetCommon函数来获取两个数字的最大公因数,并分子分母同时除于最大公因数进行约分。
  • Get Number 和GetSymbol 来随机获取计算数和运算符。
  • 计算结果,建立二叉树结构,将计算式按照优先级的由低到高的顺序从树的根开始建立,计算时,按照后缀表达式获取运算符的前后两个数进行运算。

    6.代码说明
    主函数Program.Main中,查重和将题目和答案分别写入txt文件
calculation c = new calculation();
int[][] ic = new int[n][];//n个计算式中计算数的和数
for (int i = 0; i < n; i++)
{
   string a = c.GetProblem(r, ref result, ref ic[i]);
   for (int j = 0; j < i; j++)
   {
      if (ic[j] == ic[i])
      a = "重新生成";
   }
   while (a == "重新生成")
   {
      a = c.GetProblem(r, ref result, ref ic[i]);
      for (int j = 0; j < i; j++)
      {
          if (ic[j] == ic[i])
          a = "重新生成";
      }
   }
   //将题目存入Exercises.txt中,可追加
   using (StreamWriter sw = new StreamWriter(@"F:\作业\软件工程\2\Exercises.txt", true))
   {
      sw.WriteLine(i+1 + ".四则运算题目 " + a);
   }
   //将答案存入Answers.txt
   using (StreamWriter sw = new StreamWriter(@"F:\作业\软件工程\2\Answers.txt", true))
   {
      if (result[0] == 0 || result[1] == 1)
         sw.WriteLine(i+1 + ".答案 " + result[0].ToString());
      else
         sw.WriteLine(i+1 + ".答案 " + result[0].ToString() + "/" + result[1].ToString());
   }
}

生成题目和结果的主要全过程

 /// <summary>
        /// 生成一个题目
        /// </summary>
        /// <param name="r">运算数范围</param>
        /// <returns></returns>
        public string GetProblem(int r, ref int[] result,ref int [] ic )
        {
            a = new int[4][] { new int[] { 0, 1 }, new int[] { 0, 1 }, new int[] { 0, 1 }, new int[] { 0, 1 } };//四个运算数
            X = new char[3] { ' ', ' ', ' ' };//默认为空格,方便判断
            string problem = "";
            //第一个和最后一个计算式不能为0
            while (a[0][0] == 0)
            {
                GetNumber(a[0], r, ref problem);
            }
            GetSymbol(a[0][0], ref problem, ref X[0]);
            GetNumber(a[1], r, ref problem);
            GetSymbol(a[1][0], ref problem, ref X[1]);
            GetNumber(a[2], r, ref problem);
            GetSymbol(a[2][0], ref problem, ref X[2]);
            while (a[3][0] == 0)
            {
                GetNumber(a[3], r, ref problem);
            }
            problem += "=";
            ic= IsCommon();
            //按照优先级,将计算式存入二叉树
            TreeNode<Number> sym = new TreeNode<Number>();
            GetTree(0, 3, sym);
            LinkBinaryTree<Number> link = new LinkBinaryTree<Number>(sym.Data);
            result = link.GetResult(sym, ref problem);//传入二叉树计算结果
            return problem;
        }
        /// <summary>
        /// 四个运算数等于随机自然数或真分数
        /// </summary>
        /// <param name="r">数字范围</param>
        /// <param name="pb">运算数输出形式</param>
        /// <returns>运算数计算形式</returns>
        private void GetNumber(int[] a, int r, ref string pb)
        {
            Random rd = new Random(Guid.NewGuid().GetHashCode());
            a[0] = rd.Next(0, r);
            //是否生成真分数
            int c = rd.Next(0, 2);
            //a=0时,不需要写入problem
            if (c == 1 && a[0] != 0)
            {
                //生成分母应大于1
                a[1] = rd.Next(2, r);
                // n += "/" + a[1].ToString();
                if (a[0] >= a[1])
                {
                    if (a[0] % a[1] == 0)
                    {
                        pb += (a[0] / a[1]).ToString() + "  ";
                    }
                    else
                    {
                        int gg = GetCommon(a[0] % a[1], a[1]);
                        pb += (a[0] / a[1]).ToString() + "'" + (a[0] % a[1] / gg).ToString() + "/" + (a[1] / gg).ToString() + "  ";
                    }
                }
                else
                {
                    int g = GetCommon(a[0], a[1]);
                    a[0] /= g;
                    a[1] /= g;
                    pb += a[0].ToString() + "/" + a[1].ToString() + "  ";
                }
            }
            else if (c == 0 && a[0] != 0)
            {
                pb += a[0].ToString() + "  ";
            }
        }
        /// <summary>
        /// 随机取运算符
        /// </summary>
        /// <param name="a"></param>
        /// <param name="problem"></param>
        /// <param name="X"></param>
        private void GetSymbol(int a, ref string problem, ref char X)
        {
            if (a != 0)
            {
                Random rd = new Random(Guid.NewGuid().GetHashCode());
                X = symbol[rd.Next(0, 4)];
                problem += X.ToString() + "  ";
            }
        }
        /// <summary>
        /// 辗转相除法,计算两个数的最大公因子,为分数约分
        /// </summary>
        /// <param name="a"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        public int GetCommon(int a, int b)
        {
            int d = 0;
            if (a == 0)
            {
                b = 1;
            }
            else
            {
                while (b % a != 0)
                { 
                    d = b % a;
                    b = a;
                    a = d;
                }
                b = a;
            }
            return b;
        }
        /// <summary>
        /// 获取运算符等级
        /// </summary>
        /// <param name="c">当前字符</param>
        /// <returns></returns>
        private static int GetOperationLevel(char c)
        {
            switch (c)
            {
                case '+': return 1;
                case '-': return 1;
                case '*': return 2;
                case '/': return 2;
                default: return 3;
            }
        }
        /// <summary>
        /// 获取优先级最小的运算符
        /// </summary>
        /// <returns></returns>
        private int GetMin(int s, int e, ref int j)
        {
            int min = s;

            for (int i = s; i < e; i++)
            {
                if (X[i] != ' ')
                {
                    j++;
                    if (GetOperationLevel(X[i]) <= GetOperationLevel(X[min]))
                        min = i;
                }
            }
            //运算符空,结束遍历
            return min;
        }
        //将计算式按照优先级,有序的生成二叉树
        private void GetTree(int s, int e, TreeNode<Number> sym)
        {
            int j = 0;
            int min = GetMin(s, e, ref j);
            if (j == 0)
            {
                sym.Data = new Number(a[e][0], a[e][1], ' ');
                // Console.WriteLine(sym.Data.Son.ToString() + "/" + sym.Data.Mother.ToString()+sym.Data.Symbol.ToString());
            }
            else
            {
                if (min != -1)
                {
                    sym.Data = new Number(0, 0, X[min]);
                    //   Console.WriteLine(sym.Data.Son.ToString() + "/" + sym.Data.Mother.ToString() + sym.Data.Symbol.ToString());
                    X[min] = ' ';
                    TreeNode<Number> left = new TreeNode<Number>();
                    GetTree(s, min, left);
                    sym.LChild = left;
                    TreeNode<Number> right = new TreeNode<Number>();
                    GetTree(min + 1, e, right);
                    sym.RChild = right;
                }
            }
        }

后序遍历二叉树,若为运算符则取出存入list集合中,若为计算数则存入另一个集合中,遇到运算符,则取出最末端的两个数据进行计算,结果重新插回。

private List<int[]> num = new List<int[]>();
private List<char> sym = new List<char>();
char s;
//后序遍历
//遍历根结点的左子树->遍历根结点的右子树->根结点
private void postorder(TreeNode<Number> ptr,ref string problem)
{
    if (IsEmpty())
    {
        Console.WriteLine("Tree is Empty !");
        return;
    }
    if (ptr != null)
    {
         postorder(ptr.LChild,ref problem);
         postorder(ptr.RChild,ref problem );
         // Console.WriteLine(ptr.Data.Son.ToString ()  + "/"+ptr .Data.Mother .ToString ()+ptr .Data .Symbol .ToString () );
         char c = ptr.Data.Symbol;
         if (c == '+' || c == '-' || c == '*' || c == '/')
         {
            sym.Add(c);
         }
        else
        {
            int[] nn = new int[2] { ptr.Data.Son, ptr.Data.Mother };
            num.Add(nn);
        }
        if(sym.Count ==1&&num.Count>=2 )
        {
            num [num.Count - 2] =  Arithmetic(num[num.Count - 2], num[num.Count -1], sym[0],ref problem);
            num.RemoveAt(num.Count - 1);
            sym.RemoveAt(0);
        }
    }
}

public int [] GetResult(TreeNode<Number> ptr,ref string problem)
{
    postorder(ptr,ref problem );
    return num[0];
}

int s1, s2, s3;
//计算两个计算数和一个运算符,传入计算式字符串是为了出现负数时改变内容,重新生成
public int[] Arithmetic(int[] b1, int[] b2, char s, ref string problem)
{
    int[] result = new int[2];
    switch (s)
    {
        case '+':
            s1 = b1[0] * b2[1] + b2[0] * b1[1];
            s2 = b1[1] * b2[1];
            calculation c1 = new calculation();
            s3 = c1.GetCommon(s1, s2);
            result[0] = s1 / s3;
            result[1] = s2 / s3;
            break;
        case '-':
            s1 = b1[0] * b2[1] - b2[0] * b1[1];
            s2 = b1[1] * b2[1];
            calculation c2 = new calculation();
            s3 = c2.GetCommon(s1, s2);
            result[0] = s1 / s3;
            result[1] = s2 / s3;
            //运算过程中出现负数,则主函数进行判断,舍去后重新生成计算式
            if (s1 < 0)
            {
                problem = "重新生成";
            }
            break;
        case '*':
            s1 = b1[0] * b2[0];
            s2 = b1[1] * b2[1];
            calculation c3 = new calculation();
            s3 = c3.GetCommon(s1, s2);
            result[0] = s1 / s3;
            result[1] = s2 / s3;
            break;
        case '/':
            s1 = b1[0] * b2[1];
            s2 = b1[1] * b2[0];
            calculation c4 = new calculation();
            s3 = c4.GetCommon(s1, s2);
            result[0] = s1 / s3;
            result[1] = s2 / s3;
            break;
    }
    return result;
}
7.测试运行

程序运行
1482025-20180930210253773-1624743359.png
若输入错误
1482025-20180930210837355-2102035123.png
确定后生成两个txt文件
1482025-20180930210424864-938692084.png
题目txt
1482025-20180930210534825-1396571937.png
答案txt
1482025-20180930210705153-392777843.png
生成10000道题目
1482025-20180930222832219-302382540.png

8.项目小结

关于计算数的存储,由于真分数的形式,一开始我是以字符串的形式存储,后来在设计运算过程中很不方便,我便把运算符拆成分子分母的形式存储于int[][]二维数组中,若是整数,则分母为1,输出形式在其中加入‘/’即可。这也大大方便了我后面关于分数的加减乘除。
计算结果时,运算符的先后顺序,和前后数字的选取,实在是思考得很头疼,后来查资料,要涉及到栈,树,遍历,递归,
便回去翻了翻数据结构的树,和网上的一些案例讲解,也请教了同学关于树的思路,最后还是成功实现了计算功能。第一次使用数据结构的东西写软件,对数据结构也有了更深刻的理解。

这个项目要求是对接合作,但由于找不到同样写c#的同学,这个项目便由我个人完成。

转载于:https://www.cnblogs.com/huashengmi/p/9733199.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值