一 、Github项目地址:https://github.com/Mexinz/Calculation 张妙馨

           https://github.com/HoinLueng/Calculation 梁浩然


二、PSP表格

Planning计划3025
· Estimate· 估计这个任务需要多少时间3025
Development开发13001410
· Analysis· 需求分析 150120
· Design Spec· 生成设计文档6070
· Design Review· 设计复审 3050
· Coding Standard· 代码规范3020
· Design· 具体设计120100
· Coding· 具体编码800 900
· Code Review· 代码复审 60 80
· Test· 测试(自我测试,修改代码,提交修改) 5070
Reporting报告 105 140
· Test Report· 测试报告 50 70
· Size Measurement· 计算工作量 20 30
· Postmortem & Process Improvement Plan· 事后总结, 并提出过程改进计划 3540 
合计  1435 1575

三、效能分析

 这里能看出代码各个模块占用资源的情况,由于没有保存优化前的效能,故只有最后版本的数据,为了效能的提高,我们后来改用了多线程并发的方式进行编写。


四、设计实现过程

  在阅读完题目后,我们分析题目的难点是对生成算式的运算以及查重,围绕着这两个问题,程序的实现方式成了核心的问题,我们设想了两种方案,一种是用字符串表来存储算式,再建表来对其进行解析,一种是用树的结构来存储算式。而经过讨论后我们决定用前者来完成项目的开发。

  关于项目的每个模块,我们会用到不同的数据结构去处理,比如字符串用以做输入输出的中介结构,而在具体的运算过程中,我们会把字符串转换成hash map、map或者是list的结构,而在运算的过程中,还使用到了栈的结构,对算式进行分级的拆解运算。

  为了使程序的效能得到提高,在算式生成的模块里,我们运用了java并发编程的思想,利用future类来进行多线程编程。

  而查重功能的实现,是通过把算式的运算过程存放在过程类里(Process),再在运算结果的过程中比对各步过程,排除相同的过程,从而达到避免重复的目的。

 以上为项目的结构,其中

Main为主类

Equation是等式类,它用来处理单个算式,通过调用Process类来得到答案

Formula是算式类,它用来存放生成的算式,并且包含一些对算式进行格式转换的方法

Fraction是分数类,它定义了分数这一数字形式的规则

Process是过程类,它用以计算算式的结果,记录运算的过程

CalculateService是计算大类,包含了对所有生成算式进行计算的方法,涉及到了查重的功能

Calculation是计算类,是对单个算式进行计算的具体方法

FormulaFactory是生成类,用以随机生成单个具体的算式

GenerateService是生成大类,包含按需生成所需的算式的方法

JudgeService是判断类,判断用户文本中的算式是否正确

Constants类存放常量

FileUtil类存放文件操作的方法

FractionUtil类存放分数运算的方法

StringUtil类存放对用户输入信息的处理方法

调用图示如下


五、 部分代码说明

1.CalculateService 计算大类

public class CalculateService {

    private FormulaFactory formulaFactory = new FormulaFactory(Constants.max);

    public static ExecutorService es = GenerateService.es;

    public List<Fraction> calculateOfList (List<String> formulas) {
        List<Equation> equations = new ArrayList<>(formulas.size());
        List<Future<Equation>> futures = new ArrayList<>(formulas.size());
        for (String formula : formulas) {
            Future<Equation> future = es.submit(new Calculation(formula));
            futures.add(future);
        }
        Equation equation;
        Future<Equation> future;
        for (int i = 0; i < futures.size(); i++) {
            future = futures.get(i);
            try {
                equation = future.get();
                if (equation != null && !equations.contains(equation)) {
                    equations.add(equation);
                } else {
                    Formula formula = null;
                    while (equation == null || equations.contains(equation)) {
                        formula = formulaFactory.call();
                        equation = new Calculation(formula.toString()).call();
                    }
                    Collections.replaceAll(formulas,formulas.get(i),formula.toString());
                    equations.add(equation);
                }
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }

        List<Fraction> resultList = new ArrayList<>(equations.size());
        for (Equation e : equations) {
            List<Process> processes = e.getProcesses();
            resultList.add(processes.get(processes.size() - 1).getAnswer());
        }
        return resultList;
    }

}

该类包含了算式运算的总体方法,负责运算所有的算式,其中通过判断计算过程的比对来避免重复的算式,若有重复则重新生成,以保证没有重复。

2.Calculation 计算类

public class Calculation implements Callable<Equation> {

    private String formula;

    public Calculation(String formula) {
        this.formula = formula;
    }

    @Override
    public Equation call() {
        String[] elements = formula.split(" ");
        if (formula.contains("(")) {
            List<Process> processList = new ArrayList<>();
            Stack<String> calculateStack = new Stack<>();
            Equation equation = new Equation(formula,processList);
            Equation equation1;
            String s;
            List<String> strings = new ArrayList<>();
            for (String element : elements) {
                if (element.endsWith(")")) {
                    strings.add(" " + element);
                    while (!(s = calculateStack.pop()).startsWith("(")){
                        strings.add(" " + s);
                    }
                    strings.add(s);
                    StringBuilder stringBuilder = new StringBuilder();
                    for(int i = strings.size()-1 ; i >= 0 ; i--) {
                        stringBuilder.append(strings.get(i));
                    }
                    //去掉头尾括号
                    s = stringBuilder.toString().substring(1,stringBuilder.length() - 1);
                    equation1 = simpleCalculate(s);
                    if (equation1 == null) {
                        return null;
                    } else {
                        equation.getProcesses().addAll(equation1.getProcesses());
                        strings.clear();
                    }
                } else {
                    calculateStack.push(element);
                }
            }
            //处理完括号后栈中剩余的式子转换成字符串
            while (!calculateStack.empty()) {
                strings.add(calculateStack.pop());
            }
            StringBuilder stringBuilder = new StringBuilder();
            for (int i = strings.size() - 1; i > 0; i++) {
                stringBuilder.append(strings.get(i)).append(" ");
            }
            stringBuilder.append(strings.get(0));
            equation1 = simpleCalculate(stringBuilder.toString());
            if (equation1 == null) {
                return null;
            } else {
                equation.getProcesses().addAll(equation1.getProcesses());
                return equation;
            }
        } else {
            return simpleCalculate(formula);
        }
    }

    /**
     * 无括号的计算
     * @param s 无括号的式子
     * @return 式子和计算过程
     */
    private Equation simpleCalculate(String s) {
        List<String> elementList = Arrays.asList(s.split(" "));
        List<Process> processList = new ArrayList<>();
        Stack<String> stack = new Stack<>();
        String symbol;
        Iterator<String> it = elementList.iterator();
        Fraction a;
        Fraction b;
        Fraction answer;
        while (it.hasNext()){
            symbol = it.next();
            if("×".equals(symbol) || "÷".equals(symbol)) {
                a = Fraction.stringToFraction(stack.pop());
                String param = it.next();
                if (param.contains("'")) {
                    param = FractionUtil.toFakeFraction(param);
                }
                b = Fraction.stringToFraction(param);
                switch (symbol) {
                    case "×" :
                        answer = a.multiply(b);
                        break;
                    case "÷" :
                        answer = a.divide(b);
                        break;
                    default:
                        answer = a.multiply(b);
                }
                processList.add(new Process(a,b,symbol,answer));
                if (it.hasNext() || !stack.empty()) {
                    stack.push(answer.toString());
                }
            } else {
                if (symbol.contains("'")) {
                    symbol = FractionUtil.toFakeFraction(symbol);
                }
                stack.push(symbol);
            }
        }
        Stack<String> stack1 = new Stack<>();
        while (!stack.empty()) {
            stack1.push(stack.pop());
        }
        while (!stack1.empty()) {
            a = Fraction.stringToFraction(stack1.pop());
            symbol = stack1.pop();
            b = Fraction.stringToFraction(stack1.pop());
            switch (symbol) {
                case "+" :
                    answer = a.add(b);
                    break;
                case "-" :
                    if (!a.compare(b)) {
                        answer = null;
                    } else {
                        answer = a.subtract(b);
                    }
                    break;
                default :
                    answer = a.add(b);
            }
            if (answer == null) {
                return null;
            }
            processList.add(new Process(a,b,symbol,answer));
            if (!stack1.empty()) {
                stack1.push(answer.toString());
            }
        }
        return new Equation(s,processList);
    }
}

该类是对单个具体算式进行运算的方法,包含了对算式中有无括号的情况进行分别处理,还有对处理前后的字符串转换过程。其中运用了栈的结构进行处理。

3.GenerateService 生成大类

public class GenerateService  {

    public static ExecutorService es = Executors.newCachedThreadPool();


    public List<String> getFormulaStirngList(List<Formula> formulas) {
        List<String> formulaStrings = new ArrayList<>(formulas.size());
        for (Formula f : formulas) {
            formulaStrings.add(f.toString());
        }
        return formulaStrings;
    }

    public List<Formula> getFormulaList(Map<String,Integer> paramMap) {
        Integer num = paramMap.get("-n");
        Integer max = paramMap.get("-r");
        Constants.max = max;
        List<Formula> formulas = new ArrayList<>(num);
        List<Future<Formula>> futures = new ArrayList<>(num);
        for (int i = 0; i < num ; i ++) {
            Future<Formula> future = es.submit(new FormulaFactory(max));
            futures.add(future);
        }
     /*   es.shutdown();*/
        for (Future<Formula> future : futures) {
            try {
                formulas.add(future.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }

        return formulas;
    }

}

该类与CalculateService对应,是生成环节的总过程里面包含了根据用户传递的信息进行算式生成的方法,同时与计算一样,利用了多线程的编程思想,用以提高程序的效能。

4.FormulaFactory 生成类

public class FormulaFactory implements Callable<Formula> {

    private int max;

    public FormulaFactory(int max) {
        this.max = max;
    }

    @Override
    public Formula call() {
        int random = (int) (Integer.MAX_VALUE * Math.random());
        int figureNum = (random % 3) + 2;
        List<String> symbolList = new ArrayList<>();
        List<Fraction> fractionList = new ArrayList<>();
        fractionList.add(getRandomFraction(max));
        for (int i = 1; i < figureNum ; i++) {
            random = (int) (Integer.MAX_VALUE * Math.random());
            Fraction b = getRandomFraction(max);
            String symbol = Constants.symbols[random % 4];
            while ("-".equals(symbol)) {
                if (!fractionList.get(i-1).compare(b)) {
                    random = (int) (Integer.MAX_VALUE * Math.random());
                    symbol = Constants.symbols[random % 4];
                } else {
                    break;
                }
            }
            symbolList.add(symbol);
            fractionList.add(b);
        }
        return new Formula(symbolList,fractionList);
    }

    /**
     * 生成一个随机的数
     * @param max 限值
     * @return 随机的分数
     */
    public Fraction getRandomFraction(Integer max) {
        int numerator = (int) (Math.random() * max);
        while (numerator == 0) {
            numerator = (int) (Math.random() * max);
        }
        if (Math.random() > 0.5) {
            int denominator = (int) (Math.random() * max);
            while (denominator == 0) {
                denominator = (int) (Math.random() * max);
            }
            return new Fraction(numerator,denominator);
        } else {
            return new Fraction(numerator,1);
        }
    }
}

对应Calculation,这是生成的具体类,利用随机数生成的思想,再结合定义的分数类,来生成算式。

 

六、测试运行 

 1. 测试-n -r 功能:

    a.10道题,10以内数字

 算式:

 结果:

 经验证,结果符合要求

    b.测试生成10000题目

2 测试验证功能-e -a

 

文件内容:

输出结果:


七、项目小结 

本次项目最后基本完成了所有的需求,并对其进行了一定的优化。

这是结对项目,我们在项目过程中互相合作,分工合力完成了这项任务。从最初的讨论需求,设计结构,到分模块的项目开发,再到最后的磨合优化,期间体验到了不同人合作完成一项任务会产生的摩擦和交流,从队友身上也学到了很多的新东西,更从这个过程中体验到了合作开发的过程。

项目成员:梁浩然 3117004616 张妙馨 3117004643

01-25 16:26