github地址:https://github.com/ljw-wakeup/expression_project2node
對於這種結對的工做,因爲有過電子設計實踐的基礎,大概知道建一個工程須要作的事,有點經驗仍是有幫助的。ios
小組成員:李鑫PB16061107 林靜雯PB16060913git
1、問題要求:github
1·主要功能是隨機產生有效的運算式。express
2·能夠學霸或者老師選擇運算式的要求,好比:運算式的個數,運算式的長度,運算符的種類,運算數的大小範圍,運算數的種類(整數,小數,分數)。數組
3·當本身產生的式子得本身算,而後給出運算式和結果。數據結構
4.和UI對接,這也是此次做業的關鍵,咱們要接受來自UI的設置參數,並將表達式和結果以API形式傳給UI。函數
2、分析問題與處理思想學習
分析測試
1、 如何產生合法的表達式:
1. 產生的東西只有數與符
2. 數字的運算都是加減乘除乘方
3. 採用什麼方式產生表達式才能合法?逆波蘭式?波蘭式?表達式二叉樹?
2、 如何分類以及如何設計API
1. 這個項目的主要模塊是:設置參數模塊、表達式二叉樹結點模塊、表達式模塊
2. 這些模塊之間的耦合和模塊類型?類?結構體?
3. API的設計的關鍵是咱們要把數據以什麼形式傳給UI,文件?xml?指針
某鑫的思考:
在最初思考創建表達式中間有一環特別煩,那就是不能產生重複的表達式,所謂的重複,呵呵呵,關鍵一開始還有一種不重複的狀況我都沒有理解清楚,直到想到了中綴表達式所對應的二叉樹的時候才反應過來啥是重複,啥是不重複。直到這個時候,心中才有了眉目,應該怎麼去實現隨機算式的產生以及判斷重複。
由於要將符號與數生在一棵樹上,因此將兩者統一爲一種類,數字和字符隨機選擇,選擇完畢在隨機選擇更加細緻的內容:數字的大小,運算符的種類。
三種數的產生與儲存和運算處理
首先分數怎麼儲存?2/3怎麼放?13/37怎麼放?沒辦法,就這麼放了,分子放一邊分母放一邊。那麼檢查檢查整數和小數可不能夠融進分數這樣的結構中?不能兼容也得兼容呀,否則個人樹在創建的時候還得分是分數樹仍是非分數樹?那麼整數和小數區別於分數在於沒有分母(分母爲1)這是關鍵,相當重要喲,小數區別於整數的地方就是...就是...哎呀,你選擇保留幾位小數以後就不去別了嘛,就只有在除法的時候麻煩一點,判斷整除約分處理的時候注意一下下就行了。
數字的儲存統一格式以後就是必定要重載各類運算符了。
計算和產生表達式都是後序遍歷二叉樹的結果,因此就寫在一塊兒。
某林的思考:
如何區分子模塊,將子模塊寫成類仍是結構體,模塊與模塊之間的聯繫是什麼是我拿到這個題目後最早想的問題。首先第一次真正的寫C++,要將什麼定爲一個類是一個難題。鑑於咱們已經討論好用表達式二叉樹儲存表達式,那麼對象也漸漸清晰。
首先樹確定是一個類,也就是表達式,可是咱們在表達式這個層面,更多的是基於樹與樹之間的操做,而不是單棵樹的操做,好比說檢查表達式是否重複,因而就將這個類定爲產生一系列的樹generate
那麼樹的結點也是一個類,這個結點能夠是運算符,也能夠是運算數,裏面存放着關於數和運算符的全部信息,以後的+-*/^這些運算都是這個類的方法,包括約分等等,這個類是計算的重點,node
還有一個設置參數模塊,這個模塊應該用結構體仍是類?鑑於這個結構體會被generate這個類和node類調用,咱們就暫時把它設爲結構體。但事實上,咱們後來須要基於這個類的信息處理一些函數,而且這些函數與node和generate沒有直接關係,因而又把它建成了類。類setting。
這三個類的關係很簡單,generate類調用node類的方法和setting類的方法,node類調用setting類的方法。node 並不會調用generate,setting 則不會調用其餘的類。
事實證實,這個結構劃分給咱們的程序已比較簡潔明瞭的層次感,也下降了出現致命錯誤的機率。
3、PSP表格:
4、源碼展現:
數據結構:
Setting類:
主要用於參數設置和修改,以及基於參數作一些純數學處理。
class Setting:exception
{
private:
int expnumber; //生成表達式的個數
int operator_account; //操做符數量
int operation; //操做符對應函數值
bool is_proper_fraction; //是否支持真分數運算
bool is_decimal; //是否支持小數運算
int accuracy; //精度
int range[2]; //範圍
struct Operate { //操做符選擇//這個好煩啊
bool add;
bool sub;
bool mul;
bool div;
bool pow;
};
Operate operate;
public:
int funcOperate();
int numbertype();
std::string load(double number); //將int 型轉換成char*數組
Setting(int ExpNumber, int operator_number,int Accuracy, bool fraction, bool decimal, int min, int max, bool Add, bool Sub, bool Mul, bool Div, bool Pow);
int getRange_min();
int getRange_max();
int getAccuracy();
int getOperator_account();
bool getIs_proper_fraction();
bool getIs_decimal();
int getExpnumber();
void init(int ExpNumber, int operator_number, int Accuracy, bool fraction, bool decimal,int min, int max, int operator_mode);
Setting();
void setOperate_pow(bool value);
void setRange(int min, int max);
};
node類:
二叉樹表達式結點,存放運算符或操做數,方法主要是結點運算等。
class node
{
private:
int operate; //運算符的類型 0表示不是運算符 1表示+ 2表示- 4表示* 8表示/ 16表示^
bool type; //1是操做符 0是操做數
//int numtype; //設置操做數類型,整數爲0,小數爲1, 真分數爲2
float up; //分子
long long down; //分母
public:
node(bool Type, int operate, double up, long long down);
node();
~node();
node* leftptr; //左指針
node* rightptr; //右指針
void setNode(bool type, Setting setting); //設置結點類型,並生成結點數據
void setNode(bool Type, double Up, long long Down, int Operate);
void setNodePow(); //設置乘方結點
void geneNode(Setting setting); //生成結點類型,並生成結點數據
bool getType(); //獲取結點類型
double getup(); //獲取分子
long long getdown(); //獲取分母
int getOperate(); //獲取操做符
bool operator_is_div(); //判斷是否爲除法
bool operator_is_pow(); //判斷是否爲乘方
bool operator==(node &num2); //判斷對象是否相等
node operator+(node &num2);
node operator-(node &num2);
node operator*(node &num2);
node operator/(node &num2);
node power(node &right);
bool judge_node(); //判斷結點是否非法 //也就是分母
std::string num2str(Setting setting); //將真分數轉化爲char型數組
int judge_priority(node node2); //判斷優先級
std::string transform_Operate(); //操做符轉換
void simplify(); //約分
};
generate類:
產生一系列表達式,檢查是否重複……
typedef struct Expression {
string expression;
node consequence;
}Expression;
class generate
{
private:
Setting setting;
vector<node*> TreeList; //用vector存放表達式指針的數組//或者用什麼其它的鏈表之類的
node* creExpression(node* Nptr, int &onum); //遞歸生成一棵表達式二叉樹
bool checkrepeat(node* Nptr, Expression &exp); //檢查是否重複
bool checkRepeatT(node* ptr, node* rootptr); //檢查樹是否重複
void addTree(node* rootptr); //把樹放到vector中
void addExpression(Expression expression); //把表達式放到vector
Expression getExpression(node* rootptr); //中序遍歷獲得表達式和值
node getValue(node left, node right, int Operate); //計算
public:
generate();
~generate();
bool set(int ExpNumber, int operator_number, int Accuracy, bool fraction, bool decimal,int min, int max, int operator_mode); //設置參數結構體
vector<Expression> ExpressionList; //表達式數組
void setRootnode(node* rootptr, Setting setting); //設置根結點參數
void expression(); //建立一系列表達式(一堆二叉樹,並用vector存放)
void show(); //顯示錶達式
void consequence(); //最終結果
void str_cat(Expression &dis, Expression src1, Expression src2); //表達式的連接
};
接下來是主要代碼:
隨機產生結點:
void node::setNode(bool type1, Setting setting) {
srand((unsigned)time(NULL));
if (type1 == OPERATOR) {
type = OPERATOR;
int op = setting.funcOperate();
int op1;
do {
op1 = (int)pow(2, rand() % 5);
} while (!(op1 & op));
operate = op1;
up = UP;
down = DOWN;
}
else if (type1 == NUMBER) {
type = NUMBER;
operate = OPERATE;
int num = setting.numbertype();
switch (num) {
case 0:
{
up = setting.getRange_min() + rand() % (setting.getRange_max() - setting.getRange_min() + 1);
down = 1;
}
//真分數
case 1:
{
up = setting.getRange_min() + rand() % ((setting.getRange_max() - setting.getRange_min() + 1));
down = setting.getRange_min() + rand() % ((setting.getRange_max() - setting.getRange_min() + 1));
}
case 2:
{
up = setting.getRange_min() + (rand() % ((setting.getRange_max() - setting.getRange_min())*setting.getAccuracy() + 1) / setting.getAccuracy());
down = 1;
}
}
}
}
void node::geneNode(Setting setting) {
srand((unsigned)time(NULL));
type = rand() % 2;
if (type == OPERATOR) {
int op = setting.funcOperate();
int op1;
do {
op1 = (int)pow(2, rand() % 5);
} while (!(op & op1));
operate = op1;
up = UP;
down = DOWN;
}
else if (type == NUMBER) {
operate = OPERATE;
int num = setting.numbertype();
switch (num) {
case 0:
{
up = setting.getRange_min() + rand() % (setting.getRange_max() - setting.getRange_min() + 1);
down = 1;
break;
}
//真分數
case 1:
{
up = setting.getRange_min() + rand() % ((setting.getRange_max() - setting.getRange_min() + 1));
down = setting.getRange_min() + rand() % (setting.getRange_max() - setting.getRange_min() + 1);
break;
}
case 2:
{
up = setting.getRange_min() + (double)(rand() % ((setting.getRange_max() - setting.getRange_min())*setting.getAccuracy() + 1)) / (double)setting.getAccuracy();
down = 1;
break;
}
}
}
}
駕駛員:林靜雯 領航員:李鑫
主要功能就是隨機產生結點,或者在必定要求下隨機產生結點
建樹:
node* generate::creExpression(node* Nptr, int & onum) {
int min = 0;
int max = 0;
if (!Nptr) return NULL;
//左結點
Nptr->leftptr = new node;
if ((*Nptr).operator_is_pow()) {
setting.setOperate_pow(false);
min = setting.getRange_min();
max = setting.getRange_max();
setting.setRange(setting.getRange_min(), setting.getRange_min()+10);
}
//操做符數目還未到達上限
if (onum <= setting.getOperator_account()) {
Nptr->leftptr->geneNode(setting); //生成左結點類型
//若是結點類型爲操做符
if (Nptr->leftptr->getType() == 1) {
onum++; //操做符數目加一
creExpression(Nptr->leftptr, onum); //遞歸生成左子樹
}
}
//操做符已達上限
else {
Nptr->leftptr->setNode(NUMBER, setting);
}
//右結點
Nptr->rightptr = new node();
//若是操做符是乘方
if ((*Nptr).operator_is_pow()) {
Nptr->rightptr->setNodePow();
}
//操做符數目未達上限
else if (onum <= setting.getOperator_account()) {
Nptr->rightptr->geneNode(setting); //生成右結點類型
//若是結點類型爲操做符
if (Nptr->rightptr->getType() == 1) {
onum++; //操做符數目加一
creExpression(Nptr->rightptr, onum); //遞歸生成右子樹
}
}
//操做符數目達到上限
else {
Nptr->rightptr->setNode(NUMBER, setting);
}
if ((*Nptr).operator_is_pow()) {
setting.setOperate_pow(true);
setting.setRange(min, max);
}
return Nptr;
}
駕駛員:林靜雯 領航人:李鑫
主要就是產生表達式==
遍歷樹來產生表達式和計算數值:
Expression generate::getExpression(node* rootptr) {
if (!rootptr) {
node num3;
Expression Express;
Express.consequence = num3;
Express.expression = {};
return Express;
}//雖然可能這種狀況不存在的
if (!rootptr->getType()) {
Expression Express;
Express.consequence = *rootptr;
Express.expression = Express.consequence.num2str(setting);
return Express;
}
Expression Express;
Express.consequence = (*rootptr);
Expression left = getExpression(rootptr->leftptr);
if (left.consequence.getup() > MAX_NUM || left.consequence.getdown() > MAX_NUM) {
left.consequence.setNode(NUMBER, UP, 0, OPERATE);
}
Expression right = getExpression(rootptr->rightptr);
if (right.consequence.getup() > MAX_NUM || right.consequence.getdown() > MAX_NUM) {
right.consequence.setNode(NUMBER, UP, 0, OPERATE);
}
//子樹結果出現非法
if (!left.consequence.judge_node()) {
return left;
}
if (!right.consequence.judge_node()) {
return right;
}
if ((*rootptr).operator_is_div() && right.consequence.getup()) {//除法
if (!setting.getIs_decimal() && !setting.getIs_proper_fraction()) {//僅支持整數
if (left.consequence.getup() / right.consequence.getup() != (long long)(left.consequence.getup() / right.consequence.getup())) {//不整除
long long makeup = ((long long)left.consequence.getup()) % ((long long)right.consequence.getup());
makeup = (long long)right.consequence.getup() - makeup;
node* new_operate = new node;
node* new_number = new node;
(*new_operate).setNode(OPERATOR, setting);
(*new_number).setNode(NUMBER, setting);
(*new_operate).setNode(OPERATOR, UP, DOWN, ADD);
(*new_number).setNode(NUMBER, makeup, DOWN, NUMBER);
(*new_operate).rightptr = new_number;
(*new_operate).leftptr = rootptr->leftptr;
rootptr->leftptr = new_operate;
string c_num = setting.load(makeup);
if (c_num == "wrong") {
Express.consequence.setNode(NUMBER, UP, 0, OPERATE);
return Express;
}
left.expression = left.expression + new_operate->transform_Operate() + c_num;
left.consequence.setNode(NUMBER, left.consequence.getup() + makeup, DOWN, ADD);
}
}
}
Express.consequence = getValue(left.consequence, right.consequence, rootptr->getOperate());
if (Express.consequence.getup() < 0) {
Express.consequence.setNode(NUMBER, -Express.consequence.getup(), Express.consequence.getdown(), Express.consequence.getOperate());
node* tem;
tem = rootptr->leftptr;
rootptr->leftptr = rootptr->rightptr;
rootptr->rightptr = tem;
str_cat(Express, right, left);
}
else {
str_cat(Express, left, right);
}
if (setting.numbertype() == 2) {
double tem = Express.consequence.getup();
tem /= Express.consequence.getdown();
Express.consequence.setNode(NUMBER, tem, DOWN, Express.consequence.getOperate());
}
else {
Express.consequence.simplify();
}
return Express;
}
駕駛員:李鑫 領航人:林靜雯
整除的處理因爲懼怕大機率出現的減法和除法混合的話,很容易出現沒法小規模修改的狀況讓出現a/0這樣小學生沒法處理的東西,因此沒法整除的時候由原來的減去餘數改爲加上餘數的補數,出現負數的時候直接交換左右子樹,變負爲正。
若是真的出現非法的式子:分數次冪,除數爲0,數字太大超過10位數,則強行將這棵樹廢掉(將這棵樹最後的返回結果的分母置爲0,即無心義)
關鍵的判斷重複:
bool generate::checkrepeat(node* rootptr, Expression &exp) {
for (Expression exp1 : ExpressionList) {
if (exp1.consequence == exp.consequence)
return true;
}
for (node* ptr : TreeList) {
if (checkRepeatT(ptr, rootptr)) return true;
}
return false;
}
bool generate::checkRepeatT(node* ptr, node* rootptr) {
//若是都是運算符
if (ptr == NULL && rootptr == NULL) return true;
if (ptr == NULL || rootptr == NULL) return false;
if (*ptr == *rootptr) {
return checkRepeatT(ptr->leftptr, rootptr->leftptr) && checkRepeatT(ptr->rightptr, rootptr->rightptr) || checkRepeatT(ptr->leftptr, rootptr->rightptr) && checkRepeatT(ptr->rightptr, rootptr->leftptr);
}
else return false;
}
駕駛員:林靜雯 領航員:李鑫
重載了==運算,外加利用string類的==運算,很方便的判斷出了重複
對外惟一接口的設定和若干入口檢驗:
bool generate::set(int ExpNumber, int operator_number, int Accuracy, bool fraction, bool decimal, int min, int max, int operator_mode) {
fstream setfile;
setfile.open("setting", ios::out);
//表達式個數
setfile << "ExpNumber:" << endl;
if (ExpNumber > MAX || ExpNumber < 0) {
ExpNumber = EXPNUMBER;
setfile << "false" << endl;
}
else setfile << "true" << endl;
setfile << ExpNumber << endl;
//操做符個數
setfile << "operator_number:" << endl;
if (operator_number > OPERATOR_NUMBER || operator_number < 0) {
operator_number = OPERATOR_NUMBER;
setfile << "false" << endl;
}
else setfile << "true" << endl;
setfile << operator_number << endl;
//小數位數
setfile << "Accuracy:" << endl;
if (Accuracy < 0 || Accuracy >3) {
setfile << "false" << endl;
Accuracy = ACCURACY;
setfile << Accuracy << endl;
}
else {
setfile << "true" << endl;
setfile << Accuracy << endl;
Accuracy = (int)pow(10, Accuracy);
}
//分數和小數的設置
if (fraction && decimal) {
setfile << "fration:" << endl;
setfile << "false" << endl;
fraction = false;
setfile << "no" << endl;
setfile << "decimal:" << endl;
setfile << "false" << endl;
decimal = false;
setfile << "no" << endl;
}
//分數
else {
setfile << "fraction:" << endl;
setfile << "true" << endl;
if (fraction) setfile << "yes" << endl;
else setfile << "no" << endl;
//小數
setfile << "decimal:" << endl;
setfile << "true" << endl;
if (decimal) setfile << "yes" << endl;
else setfile << "no" << endl;
}
//最小範圍數
setfile << "minnum:" << endl;
if (min > max || min>MAX || min<0) {
min = MIN;
setfile << "false" << endl;
}
else setfile << "true" << endl;
setfile << min << endl;
//範圍最大數
setfile << "maxnum:" << endl;
if (max > MAX || max <= 0) {
max = MAX;
setfile << "false" << endl;
}
else setfile << "true" << endl;
setfile << max << endl;
setfile << "operator_mode:" << endl;
//運算符格式
if (operator_mode < 0 || operator_mode >4) {
operator_mode = OPERATOR_MODE;
setfile << "flase" << endl;
setfile << "+-*" << endl;
}
else {
setfile << "true" << endl;
switch (operator_mode) {
case 0: setfile << "+" << endl;
break;
case 1: setfile << "+-" << endl;
break;
case 2:setfile << "+-*" << endl;
break;
case 3:setfile << "+-*/" << endl;
break;
case 4:setfile << "+-*/^" << endl;
break;
default:
break;
}
}
setfile << operator_mode << endl;
setfile.close();
setting.init(ExpNumber, operator_number, Accuracy, fraction, decimal, min, max, operator_mode);
return true;
}
駕駛員:林靜雯 領航員:李鑫
將數字方便的轉化成咱們想要的樣子:
string node::num2str(Setting setting)
{
string upc;
upc = setting.load(up);
if (upc == "wrong") {
down = 0;
return "wrong";
}
if (down == 1) {
return upc;
}
else {
string downc;
downc = setting.load(down);
if (downc == "wrong") {
down = 0;
return "wrong";
}
string express = upc + "//" + downc;
return express;
}
}
string Setting::load(double number)
{
string c;
number *= accuracy;
bool x = !(accuracy == 1);
number = (number - (long long)number > 0.5) ? ceil(number) : floor(number);
number /= accuracy;
c = to_string(number);
int a = (int)log10(accuracy);
if ((long long)number) {
try {
long b = (long)log10((long long)number);
b += 1 + x + a;
c.erase(b, c.size());
}
catch (exception &e) {
return"wrong";
}
return c;
}
else c.erase(1 + x + a, c.size());
return c;
}
駕駛員:李鑫 領航員:林靜雯
5、對接部分:
兩種對接方式:
第一種是輸出文件:
接口就是隻有一個設置函數:
extern "C++" _declspec(dllexport) bool set(int ExpNumber, int operator_number, int Accuracy, bool fraction, bool decimal, int min, int max, int operator_mode);
第二種是輸出內存:
須要結構體和一個函數:
struct Answer {
string* consequnce;
string* express;
};
extern "C++" _declspec(dllexport) Answer set(int ExpNumber, int operator_number, int Accuracy, bool fraction, bool decimal, int min, int max, int operator_mode);
兩種方法均測試成功
5、學習與進步
某林:
emmmm終於結束了。不過收穫仍是超級大。
第一是第一次寫C++/真正意義上的那種,雖然沒有用到繼承/重載/泛型等比較複雜的技巧,但終因而有點理解對象的設定和相關信息的封裝,好比說,要怎麼才能讓generate沒必要知道node裏到底發生了什麼,也就是所謂的避免類與類之間的語義耦合把,雖然仍是不能作到完徹底全的避免。但咱們至少作到了類之間的調用關係相對簡單這個點。不足的是我以爲類的劃分還能夠再細一點,由於有的類的方法太多了。
第二是如何比較快速的Debug,這也是此次的收穫,並且這要歸功於個人隊友李鑫2333以前雖然也知道條件斷點之類的,但都沒有實際用過,出了問題也不知道如何迅速排查,沒有跟進程序的思惟和思路,一遇到程序終止就手足無措,以前的Debug雖然本身也用,但沒有實際效率。看了隊友的調試過程通過了一週的學習,終於也能比較快地發現了程序的錯誤。
第三是更好理解了封裝和對接的含義。
封裝有利於修改程序,有利於代碼維護、有利於鼓起勇氣面對比較長的代碼,而API是合做的關鍵,是子系統與子系統之間對接的關鍵。如何不把本身的數據結構暴露給UI是咱們須要思考的問題。
總之是很爆炸的一週也是收穫不少的一週,感謝隊友!!!!!!
李鑫:我在大佬的一次次監督和嫌棄下漸漸領悟到了封裝的感受和封裝的意義
學到了名稱空間,雖然代價是幾乎白費了一個小時,問大佬只須要2分鐘,但仍是很開心的(呵呵呵,呵呵呵)
最後對接的時候,你們都在互相的磨合接口,這個問題就在很早我就在羣裏問過,因爲並不知道老師是否會有本身的安排,因此很差意思本身來規定接口,並且本身私下找人規定了接口感受瞬間二人做業就變成了四人做業,有點違背分組的意思,因此就放棄了。然而,事情並無我想象的那樣簡單。老師就是想要咱們本身規定接口,本身寫的東西找人對接固然是本身規定接口才好呀,不一樣人擅長的思惟啥啥啥的都不同,因此,在寫程序的時候,剛開始還覺得直接傳出去兩個數組指針,UI直接調用查看內存這多直接方便呀,而後發現不少UI組寫的是文件的讀寫,但只是咱們並無慌着該接口,而是想着這個自己很簡單,隨時均可以該。爲了方便UI組的對接工做,咱們仍是下了點點功夫思考UI組要幹什麼,怎麼用咱們的接口來作事情,怎麼才能方便他們作事情,因此將接口強行封裝成一個返回結構體的函數,對就只有一個函數,別接口的啥都沒有,根本不須要思考怎麼用選什麼用,只有一個只有用它UI組別無選擇。這個函數就及支持了文件讀取那種方式的UI組,也適用於內存讀取式的UI組。正是由於有了這樣的思考,纔在最後一天對接的時候很快就完成的對接的任務
技術上的一個進步就是:利用百度,學習新東西,看了幾遍,問了大佬的解釋以後終於知道什麼是dll,爲何要有dll,怎麼創建dll,怎麼連接dll。在ddl的緊逼下學習dll真爽
還有就是對隊友的絕對信任,以及two heads are真的真的better than one!