四則運算(Java)--溫銘淇,付夏陽
GitHub項目地址:
https://github.com/fxyJAVA/Calculation
四則運算項目要求:
程序處理用戶需求的模式為:
Myapp.exe -n num -r size
Myapp.exe -e
基本功能列表:
(1)【實現】使用 -n 參數控制生成題目的個數。
(2)【實現】使用 -r 參數控制題目中數值(自然數、真分數和真分數分母)的範圍。
(3)【實現】生成的題目中計算過程不能產生負數,也就是說算術表達式中如果存在形如e1 ? e2的子表達式,那麽e1 ≥ e2。
(4)【實現】生成的題目中如果存在形如e1 ÷ e2的子表達式,那麽其結果應是真分數。
(5)【實現】每道題目中出現的運算符個數不超過3個。
(6)【實現】程序一次運行生成的題目不能重復,即任何兩道題目不能通過有限次交換+和×左右的算術表達式變換為同一道題目。生成的題目存入執行程序的當前目錄下的Exercises.txt文件
(7)【實現】在生成題目的同時,計算出所有題目的答案,並存入執行程序的當前目錄下的Answers.txt文件。
(8)【實現】程序應能支持一萬道題目的生成。
(9)【實現】程序支持對給定的題目文件和答案文件,判定答案中的對錯並進行數量統計。
PSP表:
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | 40 | 60 |
· Estimate | · 估計這個任務需要多少時間 | 40 | 60 |
Development | 開發 | 1280 | 1780 |
· Analysis | · 需求分析 (包括學習新技術) | 100 | 200 |
· Design Spec | · 生成設計文檔 | 50 | 100 |
· Design Review | · 設計復審 (和同事審核設計文檔) | 40 | 100 |
· Coding Standard | · 代碼規範 (為目前的開發制定合適的規範) | 10 | 20 |
· Design | · 具體設計 | 50 | 150 |
· Coding | · 具體編碼 | 800 | 900 |
· Code Review | · 代碼復審 | 30 | 50 |
· Test | · 測試(自我測試,修改代碼,提交修改) | 200 | 250 |
Reporting | 報告 | 120 | 150 |
· Test Report | · 測試報告 | 90 | 120 |
· Size Measurement | · 計算工作量 | 5 | 10 |
· Postmortem & Process Improvement Plan | · 事後總結, 並提出過程改進計劃 | 15 | 20 |
合計 | 1430 | 2090 |
解題思路:
初看題時,眼前的一道難關就是分數的問題,真分數,假分數,分數計算等。剛開始我和搭檔溫,對這一步的實現,首先我和溫是分開實現的,我是單純用字符串,字符串切分進行結果的計算,十分繁雜,且不利於後續的功能實現:加括號,順序隨機化等。後來發現溫將數字寫成一個類,十分驚喜於這個巧妙的設計,java本身就是面向對象的語言,我卻忽略了這個重要的點,純用面向過程思維來考慮了。取長補短走過了這一道難關,將數字定義為一個類極大便利了我們後續功能的實現。後續遇到的判重問題,我想的是用HashMap的特性,一個key對應一個value,key是唯一key。如果我將算式的結果作為key,那麽value就是算式本身。我們可知,結果不同的算式一定不相同,因此僅這一步我們就過濾掉了重復的問題,但因為此方法有一定的誤判可能,於是在後續補上了判重的方法,來降低誤判的可能。
設計實現過程
以main()為中心,通過cmd傳入參數到main(String[] args)獲取操作和文件路徑,根據參數來判斷進行何種操作 。生成算式則調用Utils的靜態方法生成,要寫文件和檢查則調用FileUtils的靜態方法生成,而Fraction類則貫穿其中,是功能實現的核心。
效能分析
因為測試的時候未發現生成算式和寫文件效率並未出很低的情況,在生成算式和寫文件時,以10000條數據為例,測試僅用了383毫秒,最壞情況也不超過2秒,暫無思路做到進一步的方法優化。
代碼說明
Main.java,通過args獲取要執行的操作:
根據程序處理用戶需求的模式
1)Myapp.exe -n num -r size
生成題目,其中num為題數,size為生成數的大小
調用FileUtils.creatFile(Utils.createSuanShi(num,size))
2)Myapp.exe -e exercisefile.txt -a answerfile.txt
判斷答案的正確率,其中exercisefile.txt為生成練習題的路徑 、answerfile.txt為答案的路徑
調用FileUtils.check(exercisePath,answerPath);
import java.io.IOException;
public class main {
public static void main(String[] args) {
int num = 0;
int size = 0;
String exercisePath = null;
String answerPath = null;
if(args.length != 0){
for(int i = 0; i < args.length; i++){
//"-n指令"
if(args[i].equals("-n")) {
try {
num = Integer.parseInt(args[i+1]);
}catch (NumberFormatException e) {
System.out.println("參數有誤!");
}catch (ArrayIndexOutOfBoundsException e){
System.out.println("參數有誤!");
}
}
//"-r指令"
if(args[i].equals("-r")) {
try {
size = Integer.parseInt(args[i+1]);
}catch (NumberFormatException e) {
System.out.println("參數有誤!");
}catch (ArrayIndexOutOfBoundsException e){
System.out.println("參數有誤!");
}
}
if(args[i].equals("-e")){
try{
exercisePath = args[i+1];
}catch (ArrayIndexOutOfBoundsException e){
System.out.println("參數有誤!");
}
}
if(args[i].equals("-a")){
try{
answerPath = args[i+1];
}catch (ArrayIndexOutOfBoundsException e){
System.out.println("參數有誤!");
}
}
}
if(num != 0 && size != 0){
FileUtils.creatFile(Utils.createSuanShi(num,size));
}
if(exercisePath != null && answerPath != null){
try {
FileUtils.check(exercisePath,answerPath);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
以下是相應的util類:
首先是創造算式集合方法 createSuanShi,在這裏傳入決定生成的算式數目,算式取值範圍;
createFun則是生成算式方法,根據傳過來的要求數目,while循環生成算式,放入HashMap。
這裏算式結果作為key,算式本身作為value。這樣做的目的是判重,當我生成的結果在hashmap中存在value,我就判定該算式不成立,算入重復,但這樣做會有錯誤過濾。因而在該方法中又重新定義了判重的方法,思路為:先判斷結果是否相同->再判斷運算符數目是否相同且相等->最後判斷運算數是否相等,相等則我認為是重復算式,不計入map中。
createFra為生成算數,同時判斷是否要生成分數
whoBig為判斷算數a,b的大小,以防出現結果為負數
最後簡要說說加入()的時機,因為傳參是數組的緣故,所以根據計算符號的先後位置來決定是否加入括號,例如:我傳入了運算符順序是:+,—,* 在運算到乘法的時候,因為我已經計算了+,-的結果,所以我在這裏需要判斷前兩位操作符是什麽,若為+,—,則代表著我需要加入乘法前,給已生成的算式加上()。其他情況皆以此類推。
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashMap;
public class Utils {
public static HashMap<String, String> createSuanShi(int createNum, int size) {
if (size == 0) {
size = 100;
}
HashMap<String, String> weWant = new LinkedHashMap<>();
while (weWant.size() < createNum) {
int opNum = (int) (Math.random() * 3) + 1;
String[] op = new String[opNum];
for (int j = 0; j < opNum; j++) {
int temp = (int) (Math.random() * 4);
switch (temp) {
case 0: {
op[j] = "+";
break;
}
case 1: {
op[j] = "-";
break;
}
case 2: {
op[j] = "*";
break;
}
case 3: {
op[j] = "÷";
break;
}
}
}
String[] result = createFun(op, size).split("=");
//判重
if ((weWant.get(result[1]))!=null) {
String whoAreYou = weWant.get(result[1]);
int calChar =0;
for (int a=0;a<op.length;a++) {
if (whoAreYou.contains(op[a]))
calChar++;
}
if(calChar == op.length) {
StringBuilder sb = new StringBuilder(whoAreYou);
while(sb.toString().contains("(")) {
sb.deleteCharAt(sb.lastIndexOf("("));
}
while(sb.toString().contains(")")) {
sb.deleteCharAt(sb.lastIndexOf(")"));
}
StringBuilder sb2 = new StringBuilder(result[0]);
while(sb2.toString().contains("(")) {
sb2.deleteCharAt(sb2.lastIndexOf("("));
}
while(sb2.toString().contains(")")) {
sb2.deleteCharAt(sb2.lastIndexOf(")"));
}
String[] numArr1 = sb.toString().split("[\\+\\-\\*\\÷]");
String numArr2 = Arrays.toString(sb2.toString().split("[\\+\\-\\*\\÷]"));
int xxx=0;
for (int b = 0;b<numArr1.length;b++) {
if(numArr2.contains(numArr1[b])) {
xxx++;
}
}
if(xxx==numArr1.length) {
continue;
}
}
}
weWant.put(result[1], result[0]);
}
return weWant;
}
public static String createFun(String[] op, int size) {
StringBuffer suanShi = new StringBuffer();
//確定符號數
int length = op.length;
//初始化結果為0
Fraction result = new Fraction(0, 1);
for (String c : op) {
switch (c) {
case "+": {
if (suanShi.length() > 0 && suanShi != null) {
Fraction c1 = createFra(size);
result = result.add(c1);
if ((suanShi.toString().contains("*") || suanShi.toString().contains("÷")) && (int) (Math.random() * 2) == 0 && length == 2) {
suanShi.insert(0, c1.getFraction() + "+");
} else if (suanShi.toString().contains("*") && suanShi.toString().contains("÷") && (int) Math.random() * 3 == 0) {
suanShi.insert(0, c1.getFraction() + "+");
} else {
if (length == 3 && suanShi.toString().contains("÷") && op[0] == "-") {
suanShi.insert(0, "(");
suanShi.insert(suanShi.length(), ")");
}
suanShi.append("+" + c1.getFraction());
}
} else {
Fraction a = createFra(size);
Fraction b = createFra(size);
result = a.add(b);
suanShi.append(a.getFraction() + "+" + b.getFraction());
}
break;
}
case "-": {
if (suanShi.length() > 0 && suanShi != null) {
Fraction c1 = createFra(size);
boolean flag = whoBig(result, c1);
if (flag) {
result = c1.sub(result);
} else {
result = result.sub(c1);
}
if ((flag && suanShi.toString().contains("*") || suanShi.toString().contains("÷")) && (int) (Math.random() * 3) == 0 && (length == 2 || length == 3)) {
if (length == 3 && (suanShi.toString().contains("*") || suanShi.toString().contains("÷")) && op[1] == "+") {
suanShi.insert(0, "(");
suanShi.insert(suanShi.length(), ")");
}
suanShi.insert(0, c1.getFraction() + "-");
} else if (flag && suanShi.toString().contains("*") && suanShi.toString().contains("÷") && (int) (Math.random() * 3) == 0) {
if (length == 3 && (suanShi.toString().contains("*") || suanShi.toString().contains("÷")) && op[1].equals("+")) {
suanShi.insert(0, "(");
suanShi.insert(suanShi.length(), ")");
}
suanShi.insert(0, c1.getFraction() + "-");
} else {
if (flag) {
suanShi.insert(0, "(");
suanShi.insert(suanShi.length(), ")");
suanShi.insert(0, c1.getFraction() + "-");
} else {
suanShi.append("-" + c1.getFraction());
}
}
if (length == 3 && op[1].equals("-") && (op[2].equals("*") || op[2].equals("÷"))) {
suanShi.insert(0, "(");
suanShi.insert(suanShi.length(), ")");
}
} else {
Fraction a = createFra(size);
Fraction b = createFra(size);
if (whoBig(a, b)) {
result = b.sub(a);
suanShi.append(b.getFraction() + "-" + a.getFraction());
} else {
result = a.sub(b);
suanShi.append(a.getFraction() + "-" + b.getFraction());
}
}
break;
}
case "*": {
if (suanShi.length() > 0 && suanShi != null) {
Fraction c1 = createFra(size);
result = result.mul(c1);
if (length == 3 && (op[0].equals("+") || op[0].equals("-")) && (op[1].equals("+") || op[1].equals("-")) && !suanShi.toString().contains("(")) {
suanShi.insert(0, "(");
suanShi.insert(suanShi.length(), ")");
} else {
if ((length == 2 || length == 3) && ((suanShi.toString().contains("+") || suanShi.toString().contains("-"))) && !suanShi.toString().contains("(")) {
suanShi.insert(0, "(");
suanShi.insert(suanShi.length(), ")");
}
if (length == 3 && (suanShi.toString().contains("÷") && (suanShi.toString().contains("+") || suanShi.toString().contains("-"))) && !suanShi.toString().contains("((") && op[0] != "*") {
suanShi.insert(0, "(");
suanShi.insert(suanShi.length(), ")");
}
}
suanShi.append("*" + c1.getFraction());
} else {
Fraction a = createFra(size);
Fraction b = createFra(size);
result = a.mul(b);
suanShi.append(a.getFraction() + "*" + b.getFraction());
}
break;
}
case "÷": {
if (suanShi.length() > 0 && suanShi != null) {
Fraction c1 = createFra(size);
while (c1.getNumerator() == 0) {
c1 = createFra(size);
}
result = result.div(c1);
if ((length == 3 && (op[0].equals("+") || op[0].equals("-")) && (op[1].equals("+") || op[1].equals("-")) && !suanShi.toString().contains("("))) {
suanShi.insert(0, "(");
suanShi.insert(suanShi.length(), ")");
}
if ((length == 2 || length == 3) && (suanShi.toString().contains("+") || suanShi.toString().contains("-")) && (suanShi.toString().contains("*") && suanShi.toString().contains("÷"))) {
suanShi.insert(0, "(");
suanShi.insert(suanShi.length(), ")");
}
if (length == 3 && (suanShi.toString().contains("*")) && ((suanShi.toString().contains("+") || suanShi.toString().contains("-"))) && !suanShi.toString().contains("((") && !op[0].equals("*")) {
suanShi.insert(0, "(");
suanShi.insert(suanShi.length(), ")");
}
if (length == 3 && (suanShi.toString().contains("+") || suanShi.toString().contains("-")) && (op[2].equals("-") || op[2].equals("+"))) {
suanShi.insert(0, "(");
suanShi.insert(suanShi.length(), ")");
}
if (length == 3 && op[1].equals("÷") && (op[0].equals("+") || op[0].equals("-"))) {
suanShi.insert(0, "(");
suanShi.insert(suanShi.length(), ")");
}
suanShi.append("÷" + c1.getFraction());
} else {
Fraction a = createFra(size);
Fraction b = createFra(size);
while (b.getNumerator() == 0) {
b = createFra(size);
}
result = a.div(b);
suanShi.append(a.getFraction() + "÷" + b.getFraction());
}
break;
}
}
}
return suanShi.append("=" + (result.getNumerator() == 0 ? 0 : result.getFraction())).toString();
}
private static boolean whoBig(Fraction l, Fraction r) {
boolean flag = false;
if (l.getNumerator() * r.getDenominator() < l.getDenominator() * r.getNumerator()) {
flag = true;
}
return flag;
}
private static Fraction createFra(int size) {
if ((int) (Math.random() * 3) == 0) {
return new Fraction((int) (Math.random() * size + 1), (int) (Math.random() * size) + 2);
} else {
return new Fraction((int) (Math.random() * size + 1), 1);
}
}
}
數字類,得意於將數字定義為一個類,且集合了加減乘除,約分等對分數的處理,使得後面的流程可以專註於業務需求上,計算方面只需丟給該類的方法即可完成運算。
public class Fraction {
private int numerator;//分子
private int denominator;//分母
public Fraction(int a,int b){
setNumeratorAndDenominator(a,b);
}
public void setNumeratorAndDenominator(int a, int b){ // 設置分子和分母
int c = 1;
if(a!=0) {
c = largestCommonDivisor(a, b);// 計算最大公約數
}
numerator = a / c;
denominator = b / c;
}
public int largestCommonDivisor(int a,int b){ // 求a和b的最大公約數
if(a < b){
int c = a;
a = b;
b = c;
}
int r = a % b;
while(r != 0){
a = b;
b = r;
r = a % b;
}
return b;
}
public int getNumerator(){
return numerator;
}
public int getDenominator(){
return denominator;
}
public String getFraction(){ //獲得分數的表達,根據分子分母的大小關系是否化為帶分數
String str;
if(denominator == 1) { //分母為1,只看分子的大小
str = String.valueOf(this.numerator);
}else if(numerator == denominator){ //分子分母相等,輸出1
str = "1";
}else if(numerator > denominator){ //分子大於分母,轉化為帶分數
int roundNumber = numerator / denominator;
int newNumerator = numerator - denominator * roundNumber;
str = roundNumber + "‘" + newNumerator + "/" + this.denominator;
}else{ //否則按正常的分數輸出
str = this.numerator + "/" + this.denominator;
}
return str;
}
public Fraction add(Fraction r){ // 加法運算,通分後再運算
int a = r.getNumerator();
int b = r.getDenominator();
int newNumerator = this.numerator * b + this.denominator * a;
int newDenominator = this.denominator * b;
Fraction result = new Fraction(newNumerator,newDenominator);
return result;
}
public Fraction sub(Fraction r){ // 減法運算
int a = r.getNumerator();
int b = r.getDenominator();
int newNumerator = numerator * b - denominator * a;
int newDenominator = denominator * b;
Fraction result = new Fraction(newNumerator,newDenominator);
return result;
}
public Fraction mul(Fraction r){ // 乘法運算
int a = r.getNumerator();
int b = r.getDenominator();
int newNumerator = this.numerator * a;
int newDenominator = this.denominator * b;
Fraction result = new Fraction(newNumerator,newDenominator);
return result;
}
public Fraction div(Fraction r){ // 除法運算
int a = r.getNumerator();
int b = r.getDenominator();
int newNumerator = numerator * b;
int newDenominator = denominator * a;
Fraction result = new Fraction(newNumerator,newDenominator);
return result;
}
}
FileUtils類主要用於生成文件,和讀取文件
createFile為生成文件方法,前面通過utils方法獲得算式集,叠代獲取算式和結果值,通過字符流(PrintWriter)分別寫入到兩個文件:Exercises@日期.txt Answers@日期
check方法主要用於比對答案是否正確,通過獲取傳進來的question獲取對應的答案文件讀取答案與讀取到的yourAnswer文件匹配,匹配結果寫入到grade文件中,以及輸出到控股臺。
import java.io.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
public class FileUtils {
public static void creatFile(HashMap map) {
LocalDateTime time = LocalDateTime.now();
String timeFor = time.format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"));
File question = new File("D:\\Exercises@"+timeFor+".txt");
File answer = new File("D:\\Answers@"+timeFor +".txt");
try {
question.createNewFile();
answer.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
try (PrintWriter questionWrite = new PrintWriter(new FileWriter(question, true));
PrintWriter answerWrite = new PrintWriter(new FileWriter(answer, true));) {
int jiShu = 0;
for (Object entry : map.entrySet()) {
Map.Entry<String, String> temp = (Map.Entry<String, String>) entry;
String key = temp.getKey();
String value = temp.getValue();
questionWrite.write(jiShu+1+". "+value+"= ");
questionWrite.write("\r\n");
questionWrite.write("\r\n");
answerWrite.write(jiShu+1+". "+key);
answerWrite.write("\r\n");
answerWrite.write("\r\n");
jiShu++;
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void check(String question,String yourAnswer) throws IOException {
String answers = question.split("@")[1];
File answersOff = new File("D:\\Answers@"+answers);
File yourAnswers = new File(yourAnswer);
if(!yourAnswers.exists()) {
System.out.println("未找到文件,請重新確認文件路徑");
System.exit(0);
}
if(!answersOff.exists()) {
System.out.println("答案文件不存在,請確認");
System.exit(0);
}
LocalDateTime time = LocalDateTime.now();
String timeFor = time.format(DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"));
File grade = new File("D:\\Grade@"+timeFor+".txt");
grade.createNewFile();
try(BufferedReader answersOffReader = new BufferedReader(new FileReader(answersOff));
BufferedReader yourAnswersReader = new BufferedReader(new FileReader(yourAnswers));
PrintWriter gradeWrite = new PrintWriter(new FileWriter(grade))) {
String temp ;
String ans ;
StringBuilder r = new StringBuilder();
r.append("(");
StringBuilder w = new StringBuilder();
w.append("(");
int right = 0;
int wrong = 0;
ans=answersOffReader.readLine();
while ((temp=yourAnswersReader.readLine())!=null ) {
if(temp.equals("\r\n") || temp.equals("")) {
continue;
}
while(ans==null || ans.equals("")) {
ans=answersOffReader.readLine();
}
String xuHao =ans.split("\\.")[0];
if(temp.split("\\.").length>1) {
ans =ans.split("\\.")[1].trim();
String tempAnswer = temp.split("\\.")[1].trim();
if(tempAnswer.equals(ans)) {
r.append(xuHao+",");
right++;
}else {
w.append(xuHao+",");
wrong++;
}
}else {
w.append(xuHao+",");
wrong++;
continue;
}
ans = answersOffReader.readLine();
}
if(r.toString().contains(",")) {
r.deleteCharAt(r.lastIndexOf(","));
}
if(w.toString().contains(",")) {
w.deleteCharAt(w.lastIndexOf(","));
}
r.insert(r.length(),")");
w.insert(w.length(),")");
gradeWrite.write("Correct:"+right+r.toString());
gradeWrite.write("\r\n");
gradeWrite.write("Wrong:"+wrong+w.toString());
System.out.println("Correct:"+right+r.toString());
System.out.println("Wrong:"+wrong+w.toString());
}
}
}
測試運行
答題者的答案需另起一個文檔,格式參照正確答案
命令行:
題目:
答案:
判斷答案:
生成文件:
覆蓋率:
項目小結
通過這次項目實戰,我們又復習了javaSE的一些知識,比如java的集合類HashMap,文件的輸入輸出流,面向對象等知識,還有些細節性的一些知識點,比如為什麽用StringBuilder而不用STring和StringBuffer等。又因為是兩人合作開發,在開發時就不能
一意孤行,要結合隊友的思路,彼此交流,言語表達要使對方能理解你的思路,探討不足和取長補短,這樣才能進行下一步的深入研究,也才有了我們現在完成的項目。通過此次開發,我們都體會到了合作開發的優勢,開始有了團隊意識,寫代碼時,會留
意註釋是否到位等,另外一項收獲就是如何進行項目管理,版本控制,防止開發時出現版本沖突等問題,較好的進行版本叠代。
四則運算(Java)--溫銘淇,付夏陽