小學四則運算生成Java實現 (彭迪彬 李尤)
阿新 • • 發佈:2018-09-27
sof 容易 char 分鐘 體會 stack 功能 完成 sig
Java實現小學四則運算
by 彭迪彬 李尤
GitHub項目地址:https://github.com/2youyou/JDBC
項目要求
題目:實現一個自動生成小學四則運算題目的命令行程序
- 功能列表
- [完成] 使用 -n 參數控制生成題目的個數。
- [完成] 使用 -r 參數控制題目中數值的範圍。
- [完成] 生成的題目中計算過程不能產生負數。
- [完成] 生成的題目中如果存在形如e1 ÷ e2的子表達式,那麽其結果應是真分數。
- [完成] 程序一次運行生成的題目不能重復,生成的題目存入執行程序的當前目錄下的Exercises.txt文件。
- [完成] 每道題目中出現的運算符個數不超過3個。
- [完成] 在生成題目的同時,計算出所有題目的答案,並存入執行程序的當前目錄下的Answers.txt
- [完成] 程序應能支持一萬道題目的生成。
- [完成] 程序支持對給定的題目文件和答案文件,判定答案中的對錯並進行數量統計。
設計實現
1.表達式生成:根據輸入的兩個參數決定表達式的數量及數值範圍,隨機生成數值範圍內的自然數及運算符,隨機插入左括號並在相應的位置插入右括號。
2.分數:專門寫了一個類Fraction來生成分數,並且有約分的功能。
3.負數:負數只會在減法運算時產生,每當遇到減法運算時,若運算結果出現負數,就重新生成新的表達式。
4.計算:將整數也當成分數進行計算,先將上面生成的表達式(以字符串存儲)分別入數字棧和符號棧,再根據符號優先級計算最終結果。
代碼
分數類
public class Fraction { int x; //分子 int y; //分母 private Fraction temp; public Fraction(int a, int b) { x = a; y = b; } Fraction add(Fraction r) { temp = new Fraction(0, 0); temp.x = x * r.y + y * r.x; temp.y = y * r.y; return temp; } Fraction minus(Fraction r) { temp = new Fraction(0, 0); temp.x = x * r.y - y * r.x; temp.y = y * r.y; return temp; } Fraction multiply(Fraction r) { temp = new Fraction(0, 0); temp.x = x * r.x; temp.y = y * r.y; return temp; } Fraction divide(Fraction r) { temp = new Fraction(0, 0); temp.x = x * r.y; temp.y = y * r.x; return temp; } String print() { /** * 計算結果化簡 */ if (x == 0) { return "0"; } else { int n; if (x > y) n = x; else n = y; int maxn = 0; for (int i = 1; i <= n; ++i) { //約分 if (x % i == 0 && y % i == 0) maxn = i; } int a = x / maxn; int b = y / maxn; if (a == b) return "1"; else if(b==1) return a+""; else return a + "/" + b; } } }
表達式生成
public class CreateExercise { String[] sign = {"+", "-", "x", "÷", "/"}; int range_num; Random random = new Random(); public void setRange_num(int range_num) { this.range_num =range_num; } public String create() { String str = ""; int local = random.nextInt(3); for (int j = 0; j < 3; j++) { if (local == 0 && j == 0) { str += "("; } else if (local == 2 && j == 1) { str += "("; } str += random.nextInt(range_num) % range_num + 1; //產生指定範圍隨機數 if (local == 0 && j == 1) { str += ")"; } if (local == 2 && j == 2) { str += ")"; } String signElement = sign[random.nextInt(5)];//產生隨機運算符號 str += signElement; if (signElement == "/") { str += random.nextInt(range_num) % range_num + 1; signElement = sign[random.nextInt(5)]; while (true) { if (signElement != "/") { str += signElement; break; } signElement = sign[random.nextInt(5)]; } } } str = str.substring(0, str.length() - 1); return str; } public void belongFraction(String strfraction) { /** * 處理分數計算 */ String[] fractionlist = null; if (strfraction.contains("+")) { fractionlist = strfraction.split("\\+"); CalculateFraction(fractionlist, 0); } else if (strfraction.contains("-")) { fractionlist = strfraction.split("-"); CalculateFraction(fractionlist, 1); } else if (strfraction.contains("x")) { fractionlist = strfraction.split("\\x"); CalculateFraction(fractionlist, 2); } else if (strfraction.contains("÷")) { fractionlist = strfraction.split("÷"); CalculateFraction(fractionlist, 3); } } public void CalculateFraction(String[] strlist, int flag) { /** * 分數的四種基本運算 */ String[] fraction1 = new String[2]; String[] fraction2 = new String[2]; if (strlist[0].contains("/")) fraction1 = strlist[0].split("/"); else { fraction1[0] = strlist[0]; fraction1[1] = "1"; } if (strlist[1].contains("/")) fraction2 = strlist[1].split("/"); else { fraction2[0] = strlist[1]; fraction2[1] = "1"; } Fraction fr1 = new Fraction(Integer.parseInt(fraction1[0]), Integer.parseInt(fraction1[1])); Fraction fr2 = new Fraction(Integer.parseInt(fraction2[0]), Integer.parseInt(fraction2[1])); fr1.print(); switch (flag) { case 0: fr1.add(fr2).print(); break; case 1: fr1.minus(fr2).print(); break; case 2: fr1.multiply(fr2).print(); break; case 3: fr1.divide(fr2).print(); break; } } }
計算
public class Calculator {
/**
* 數字棧:存儲表達式中的數字
*/
private Stack<String> numStack = null;
/**
* 符號棧:存儲表達式中的運算符和括號
*/
private Stack<Character> charStack = null;
/**
* 計算四則運算表達式,返回計算結果
*
*/
public String calculate(String numStr) {
numStr = removeStrSpace(numStr);
if (numStr.length() > 1 && !"=".equals(numStr.charAt(numStr.length() - 1) + "")) {
numStr += "=";
}
if (!isStandard(numStr)) {
return "0";
}
numStack = new Stack<String>();
charStack = new Stack<Character>();
StringBuffer temp = new StringBuffer();
for (int i = 0; i < numStr.length(); i++) {
char ch = numStr.charAt(i);
if (isNumber(ch) || ch == ‘/‘) {
temp.append(ch);
} else {
String tempStr = temp.toString();
if (!tempStr.isEmpty()) {
numStack.push(tempStr);
temp = new StringBuffer();
}
while (!comparePri(ch) && !charStack.empty()) {
String a = numStack.pop();
String b = numStack.pop();
Fraction f1 = null;
Fraction f2 = null;
if (a.contains("/")) {
String[] alist = a.split("/");
f1 = new Fraction(Integer.parseInt(alist[0]), Integer.parseInt(alist[1]));
} else {
f1 = new Fraction(Integer.parseInt(a), 1);
}
if (b.contains("/")) {
String[] blist = b.split("/");
f2 = new Fraction(Integer.parseInt(blist[0]), Integer.parseInt(blist[1]));
} else {
f2 = new Fraction(Integer.parseInt(b), 1);
}
switch (charStack.pop()) {
case ‘+‘:
numStack.push(f2.add(f1).print());
break;
case ‘-‘:
if ((f1.x/f1.y) >= (f2.x/f2.y)) {
return null;
}
numStack.push(f2.minus(f1).print());
break;
case ‘x‘:
numStack.push(f2.multiply(f1).print());
break;
case ‘÷‘:
if (f1.x==0){
return null;
}
numStack.push(f2.divide(f1).print());
break;
default:
break;
}
}
if (ch != ‘=‘) {
charStack.push(new Character(ch));
if (ch == ‘)‘) {
charStack.pop();
charStack.pop();
}
}
}
}
return numStack.pop();
}
private String removeStrSpace(String str) {
return str != null ? str.replaceAll(" ", "") : "";
}
private boolean isStandard(String numStr) {
if (numStr == null || numStr.isEmpty())
return false;
Stack<Character> stack = new Stack<Character>();
boolean b = false;
for (int i = 0; i < numStr.length(); i++) {
char n = numStr.charAt(i);
if (!(isNumber(n) || "(".equals(n + "") || ")".equals(n + "")
|| "+".equals(n + "") || "-".equals(n + "")
|| "x".equals(n + "") || "÷".equals(n + "") || "/".equals(n + "")
|| "=".equals(n + ""))) {
return false;
}
if ("(".equals(n + "")) {
stack.push(n);
}
if (")".equals(n + "")) {
if (stack.isEmpty() || !"(".equals((char) stack.pop() + ""))
return false;
}
if ("=".equals(n + "")) {
if (b)
return false;
b = true;
}
}
if (!stack.isEmpty())
return false;
if (!("=".equals(numStr.charAt(numStr.length() - 1) + "")))
return false;
return true;
}
private boolean isNumber(char num) {
if (num >= ‘0‘ && num <= ‘9‘)
return true;
return false;
}
/**
* 比較優先級:如果當前運算符比棧頂元素運算符優先級高則返回true,否則返回false
*/
private boolean comparePri(char symbol) {
if (charStack.empty()) {
return true;
}
char top = charStack.peek();
if (top == ‘(‘) {
return true;
}
switch (symbol) {
case ‘(‘:
return true;
case ‘x‘: {
if (top == ‘+‘ || top == ‘-‘)
return true;
else
return false;
}
case ‘÷‘: {
if (top == ‘+‘ || top == ‘-‘)
return true;
else
return false;
}
case ‘+‘:
return false;
case ‘-‘:
return false;
case ‘)‘:
return false;
case ‘=‘:
return false;
default:
break;
}
return true;
}
String getFinalResult(String str) {
if (!str.contains("/"))
return str;
String[] part = str.split("/");
int a = Integer.parseInt(part[0]);
int b = Integer.parseInt(part[1]);
if (a == b)
return "1";
else if (a > b && a % b != 0) {
return a / b + "’" + a % b + "/" + b;
} else if (a < b && -a > b && (-a) % b != 0) {
return "-" + (-a) / b + "’" + (-a) % b + "/" + b;
} else if (b == 1)
return a + "";
else
return a + "/" + b;
}
}
生成txt文本
public class IO {
File ExerciseFile = null;
File AnswerFile = null;
String filename = "";
BufferedWriter ExerciseOut = null;
BufferedWriter AnswerOut = null;
public IO() {
if (this.CreateFile()) {
this.setOutBufferedWriter();
} else
System.out.println("創建文件失敗!");
}
public void setOutBufferedWriter() {
try {
this.ExerciseOut = new BufferedWriter(new FileWriter(ExerciseFile));
this.AnswerOut=new BufferedWriter(new FileWriter(AnswerFile));
} catch (IOException e) {
e.printStackTrace();
}
}
public boolean CreateFile() {
String relativelyPath=System.getProperty("user.dir");
ExerciseFile = new File(relativelyPath+"\\Exercise" + ".txt");
AnswerFile = new File(relativelyPath+"\\Answer" + ".txt");
if (ExerciseFile.exists()) {
ExerciseFile.delete();
}
if (AnswerFile.exists()) {
AnswerFile.delete();
}
try {
ExerciseFile.createNewFile();
AnswerFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
return true;
}
public boolean WriteToFile(String content, int flag) {
try {
switch (flag) {
case 0:
ExerciseOut.write(content);
ExerciseOut.write("\r\n");
ExerciseOut.flush();
return true;
case 1:
AnswerOut.write(content);
AnswerOut.write("\r\n");
AnswerOut.flush();
return true;
}
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
public boolean CloseOutBufferedWriter() {
try {
ExerciseOut.close();
AnswerOut.close();
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
}
主函數啟動類
public class MyApp {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
System.out.print("請輸入希望生成的題目數量:");
int problems_num = scan.nextInt();
System.out.print("請輸入題目的數值範圍n(範圍為0到n):");
int range_num = scan.nextInt();
System.out.print("是否開始答題(yes/no):");
String is_doNow=scan.next();
LinkedHashMap<Integer, String> rightAnswerMap = new LinkedHashMap<Integer, String>();
LinkedHashMap<Integer,String> exerciseMap = new LinkedHashMap<Integer, String>();
ArrayList<Integer> rightRecord = new ArrayList<Integer>();
ArrayList<Integer> wrongRecord = new ArrayList<Integer>();
CreateExercise ce = new CreateExercise();
IO save = new IO();
ce.setRange_num(range_num);
for (int i = 1; i <= problems_num; i++) {
String problem = ce.create();
exerciseMap.put(i,problem);
String ns = problem;
int rightbrackets;
int leftbrackets;
if (problem.contains(")")) {
rightbrackets = problem.indexOf(")");
leftbrackets = problem.indexOf("(");
if (rightbrackets != problem.length() - 1 && problem.charAt(rightbrackets + 1) == ‘/‘) {
StringBuilder sb = new StringBuilder(problem);
if (leftbrackets - 1 > 0 && problem.charAt(leftbrackets - 1) == ‘÷‘)
sb.replace(rightbrackets + 1, rightbrackets + 2, "x");
else
sb.replace(rightbrackets + 1, rightbrackets + 2, "÷");
ns = sb.toString();
}
}
Calculator cal = new Calculator();
String result = cal.calculate(ns);
if (result != null) {
result = cal.getFinalResult(result);
System.out.println(i + ": " + problem + "=" );
rightAnswerMap.put(i, result);
if (!save.WriteToFile(i + ": " + problem + "=", 0)) {
System.out.println("生成Exercise文件失敗!");
System.exit(0);
}
if (!save.WriteToFile(+ i + ": " + problem + "=" + result, 1)) {
System.out.println("生成Answer文件失敗!");
System.exit(0);
}
} else {
i--;
}
}
if(is_doNow.equals("yes")){
System.out.println("請輸入答案(帶分數用“ ’ ”分隔,如1’1/2):");
for (int i = 1; i <= problems_num; i++) {
System.out.print( + i + ": " + exerciseMap.get(i) + "=");
String input = scan.next();
if (rightAnswerMap.get(i).equals(input))
rightRecord.add(i);
else
wrongRecord.add(i);
}
System.out.println("結果為:");
if (rightRecord.size()!=0){
System.out.print("正確題目:"+rightRecord.size()+" (");
for (int i=0;i<rightRecord.size()-1;i++)
System.out.print(rightRecord.get(i)+",");
System.out.println(rightRecord.get(rightRecord.size()-1)+")");
}
else
System.out.println("正確題目:"+rightRecord.size());
if (wrongRecord.size()!=0){
System.out.print("錯誤題目:"+wrongRecord.size()+" (");
for (int i=0;i<wrongRecord.size()-1;i++)
System.out.print(wrongRecord.get(i)+",");
System.out.println(wrongRecord.get(wrongRecord.size()-1)+")");
}
else
System.out.println("錯誤題目:"+wrongRecord.size());
}
if (save.CloseOutBufferedWriter())
System.out.println("題目和答案文本創建成功");
else
System.out.println("題目和答案文本創建失敗");
}
}
測試運行
生成10000道題目
部分題目及答案
點擊查看完整題目
點擊查看完整答案
答案統計
故意答錯 1,3題
代碼覆蓋率
PSP
PSP2.1
PSP2.1 | Personal Software Process Stages | 預估耗時(分鐘) | 實際耗時(分鐘) |
---|---|---|---|
Planning | 計劃 | 60 | 120 |
· Estimate | · 估計這個任務需要多少時間 | 30 | 60 |
Development | 開發 | 1000 | 2200 |
· Analysis | · 需求分析 (包括學習新技術) | 100 | 180 |
· Design Spec | · 生成設計文檔 | 60 | 100 |
· Design Review | · 設計復審 (和同事審核設計文檔) | 30 | 30 |
· Coding Standard | · 代碼規範 (為目前的開發制定合適的規範) | 30 | 30 |
· Design | · 具體設計 | 120 | 150 |
· Coding | · 具體編碼 | 600 | 1500 |
· Code Review | · 代碼復審 | 50 | 60 |
· Test | · 測試(自我測試,修改代碼,提交修改) | 150 | 60 |
Reporting | 報告 | 80 | 80 |
· Test Report | · 測試報告 | 30 | 40 |
· Size Measurement | · 計算工作量 | 20 | 10 |
· Postmortem & Process Improvement Plan | · 事後總結, 並提出過程改進計劃 | 40 | 20 |
合計 | 1150 | 2400 |
總結
此次的結對編程,我主要負責表達式的生成、計算,李尤同學主要負責文件生成的IO,我們都相互審閱了對方的代碼。剛拿到題目的時候,我們的思路並不是很清晰,對於表達式的計算及負數的避免想要直接實現,嘗試過後發現過於麻煩,後來與李尤同學討論後發現使用數據結構的棧來實現會簡單很多,可以說這次的結對編程,通過兩個人的討論,使得項目的整體思路變得很清晰,實現起來也更加容易。此次編程較為遺憾的沒有實現題目的查重功能,我們以後會繼續努力完善這個功能。李尤同學的思維很活躍,經常有一些天馬行空的想法,在編寫程序的過程中給了我很多啟發。這次的結對編程,讓我體會到了合作的力量,同時也認識到了自己數據結構方面知識的不足,今後會多加學習這方面的知識,完善自己的不足。
小學四則運算生成Java實現 (彭迪彬 李尤)