小学生数学算式自动出题器
最近软工老师要我们在课堂上实现一个自动生成给小学生做数学题的程序,记录一下~
有下列一些要求
- 随机出现四个运算符
- 每次出现1-2运算符,计算式子a op b op c
- 按照优先级的运算顺序不能出现负数,非整数,大于100 的数,要不他们可能遭不住
分析:
只有两个运算符,直接可以暴力先计算a op b,再按照后面op的优先级决定:继续计算op c还是a op(b op c)
/*
Author:Janspiry
Number:
Title:
Tags:
*/
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
char sig[5]="-+/*";
int a,b,c,op1,op2;
int calc(int a,int b,int op){
switch(sig[op]){
case '/':
if(b==0||a%b) return inf;//特判除法
return a/b;
case '*':
return a*b;
case '-':
// if(a<b) return inf;
return a-b;
default: return a+b;
}
}
void init(){//随机函数
a=rand()%101;
b=rand()%101;
c=rand()%101;
op1=rand()%4;
op2=rand()%4;
}
bool valid(int num){//判断合法
return num>=0&&num<=100;
}
int main()
{
#ifdef __DEBUG__
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
srand(time(0));
int ans1,ans2,ks=1;
while(1){
init();
ans1=calc(a,b,op1);
ans2=calc(b,c,op2);
if((op2>>1)>(op1>>1)){//比较优先级
if(!valid(ans2)||!valid(calc(a,ans2,op1)))continue;
}else{
if(!valid(ans1)||!valid(calc(ans1,c,op2)))continue;
}
printf("P%d %d%c%d%c%d\n",ks++,a,sig[op1],b,sig[op2],c);
if(ks==300){//输出300组
break;
}
}
return 0;
}
稍微复杂一点的思路,毕竟要是出现3个以上运算符那就gg
/*
Author:Janspiry
Number:
Title:
Tags:
*/
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <ctime>
using namespace std;
#define ll long long
#define ull unsigned long long
#define pi acos(-1)
const int inf=0x3f3f3f3f;
char sig[5]="+-*/";
char ans[20],tmp[20];
int a,b,c,op1,op2;
int calc(char *s){
int a,b,n=strlen(s);
for(int i=n;i>=0;i--){//按照优先级递归求值
if(s[i]=='+'){
s[i]='\0';
return calc(s)+calc(s+i+1);
}else if(s[i]=='-'){
s[i]='\0';
a=calc(s);
b=calc(s+i+1);
if(a>100||b>100||a<b){//判断减法合法性
return inf;
}
return a-b;
}
}
for(int i=n-1;i>=0;i--){
if(s[i]=='*'){
s[i]='\0';
a=calc(s);
b=calc(s+i+1);
if(a>100||b>100){
return inf;
}
return calc(s)*calc(s+i+1);
}else if(s[i]=='/'){
s[i]='\0';
a=calc(s);
b=calc(s+i+1);
if(a>100||b>100||a==0||b==0||a%b){//判断除法合法性
return inf;
}
return a/b;
}
}
return atoi(s);
}
void init(){
a=rand()%100+1;
b=rand()%100+1;
c=rand()%100+1;
op1=rand()%4;
op2=rand()%4;
}
bool valid(char *s){
int ans=calc(s);
return ans>=0&&ans<=100;
}
void multiop(){//是否允许单算法符
while(op1==op2){
op2=rand()%4;
}
}
int main()
{
#ifdef __DEBUG__
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
#endif
srand((unsigned int)time(0));
int ks=1;
while(1){
init();
multiop();
sprintf(ans,"%d%c%d%c%d",a,sig[op1],b,sig[op2],c);
strcpy(tmp,ans);
if(valid(ans)){
printf("P%d: %s\n",ks++,tmp);
if(ks==300){
break;
}
}
}
//printf("Time elapsed: %.3lf s\n",1.0*clock()/CLOCKS_PER_SEC);
return 0;
}
效果图
更新
- 增加可视化界面与一些选择操作
- 增加括号运算符
- 优化小学生视角效果
思路:
一个比较神奇的做法: 我们从随机的答案入手。假设要求是定义范围0-100,需要三个运算符,加减乘除都可以。步骤大致如下:
0.随机得到要分解的答案98
1.随机一个运算符比如*,然后随机一个数字2,那么98=249。
2.随机分解49或者2(用vector实现),假设选择的数的49,重复步骤1,分解得到了77,现在vector里面还有7,7,2
3.重复步骤2,得到了7=1+6
4.最后的答案就是(1+6)*7*2,括号需要我们在进行递归生成字符串的同时的判断优先级加上去。
流程就是不断的随机数和随机运算符进行数的分解,可以用树来完成对儿子节点的记录。
头文件
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<time.h>
#include<string>
#include<string.h>
#include<string.h>
using namespace std;
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
public:
QString ans;
private:
Ui::Widget *ui;
private slots:
void on_print_clicked();
void on_copy_clicked();
private:
char sig[5]="+-*/";//运算符
bool vis[5];//哪些运算符可用
int fac[505];//除法因子
int rangel,ranger;//运算范围
int len;//选择操作符的个数
int col,col_cnt;//打印栏数要求
int ks;//打印组数
int unit_len;//单元长度
bool display_ans;//是否显示结果
struct node{
int op,lnum,rnum;//分解表达式用,返回a op b
};
struct Tree{
int sum,op,lc,rc;//节点代表的数,分解用的操作符,左右儿子节点编号
}tree[505<<2];
vector<int>vec;//随机节点用
private:
void init();//获得设置,按设置随机
int create_op();//随机运算符
node create_formula(int sum);//分解表达式
void rand_exp();//随机表达式
string get_exp(int now);//递归得到表达式
void Print(int root);//按格式打印
};
#endif // WIDGET_H
.c文件
#include "widget.h"
#include "ui_widget.h"
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<time.h>
#include<string>
#include<string.h>
#include<string>
#include <QClipboard>
using namespace std;
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::init(){
rangel=ui->rangel->value();//最小范围
ranger=ui->ranger->value();//最大范围
if(rangel>ranger){
swap(rangel,ranger);
}
col=ui->col->value();//栏数
ks=ui->ks->value();//组数
len=ui->len->value();//运算符个数
col_cnt=0;
// memset(vis,0,sizeof(vis));
//运算符的合法性
vis[0]=ui->add->checkState();
vis[1]=ui->minus->checkState();
vis[2]=ui->mult->checkState();
vis[3]=ui->div->checkState();
unit_len=12*len;//控制格式用
display_ans=ui->display_ans->checkState();//是否输出结果
ans="";//最后的字符串
}
int Widget::create_op(){
int op=rand()%4;
while(!vis[op]){//判断运算符合法性并生成
op=rand()%4;
}
return op;
}
Widget::node Widget::create_formula(int sum){//进行数的分解
int op=create_op();
int lnum,rnum,tot=0;
switch(sig[op]){
case '+':
lnum=rand()%(sum+1);
rnum=sum-lnum;
break;
case '-':
lnum=rand()%(ranger-rangel+1)+rangel;
rnum=sum-lnum;
while(rnum<rangel||rnum>ranger||lnum<sum){
lnum=rand()%(ranger-rangel+1)+rangel;
rnum=lnum-sum;
}
break;
case '*':
for(int i=rangel;i*i<=ranger;i++)if(i&&sum%i==0){
fac[tot++]=i;
}
lnum=fac[rand()%tot];
rnum=sum/lnum;
break;
default:
if(sum==0){
lnum=0;
rnum=max(rand()%(ranger-rangel+1)+rangel,1);
}else{
rnum=max(1,rand()%(ranger/sum+1));
lnum=sum*rnum;
while(lnum<rangel||lnum>ranger){
rnum=max(1,rand()%(ranger/sum+1));
lnum=sum*rnum;
}
}
break;
}
return {op,lnum,rnum};
};
void Widget::rand_exp(){
int cnt=0,tot=1,idx=1;
int sum=rand()%(ranger-rangel+1)+rangel;
node nod;
vec.clear();
while(cnt<len){//利用vector进行随机的节点选择,进行数的分解
cnt++;
nod=create_formula(sum);
tree[idx]={sum,nod.op,tot+1,tot+2};
tree[++tot]={nod.lnum,0,0,0};
vec.push_back(tot);
tree[++tot]={nod.rnum,0,0,0};
vec.push_back(tot);
int r=rand()%vec.size();
idx=vec[r];
sum=tree[idx].sum;
vec.erase(vec.begin()+r);
}
}
string Widget::get_exp(int now){//打印
// string s;
int lc=tree[now].lc,rc=tree[now].rc,op=tree[now].op;
if(tree[now].lc&&tree[now].rc){
string l=get_exp(lc);//递归得到表达式
string r=get_exp(rc);
if(tree[lc].lc&&op/2>tree[lc].op/2){//优先级高加括号
l="("+l+")";
}
if(tree[rc].lc&&(op/2>tree[rc].op/2||op==1||op==3)){
r="("+r+")";
}
return l+sig[op]+r;//返回l op r
}else{
return to_string(tree[now].sum);//没有运算符返回数字
}
}
void Widget::Print(int root){
string s=get_exp(root);//递归得到表达式
int slen=s.length();
int tmpc=0;
for(int i=0;i<slen;i++){
if(s[i]=='*'){
tmpc+=4;
// ans=ans+" * ";
ans=ans+" × ";
}else if(s[i]=='/'){
tmpc+=4;
// ans=ans+" / ";
ans=ans+" ÷ ";
}else if(s[i]=='+'||s[i]=='-'){
tmpc+=3;
ans=ans+" "+s[i]+" ";
}else{
tmpc++;
ans=ans+s[i];
}
}
if(display_ans){//选择输出结果进行的操作
QString tmp=QString::number(tree[root].sum);
ans=ans+" = "+tmp;
tmpc+=tmp.length();
}
if(++col_cnt>=col){//栏数控制
col_cnt=0;
ans=ans+"\r\n";
}else{
while(tmpc++<unit_len){
ans=ans+" ";
}
}
}
void Widget::on_print_clicked(){
srand(time(0));
int ans_cnt=0;
init();
while(1){//多组随机
rand_exp();
Print(1);
if(++ans_cnt>=ks){
break;
}
}
ui->display_area->clear();
ui->display_area->setText(QString(ans));//设置输出
};
void Widget::on_copy_clicked(){//复制调用剪切板
QClipboard* clipboard = QApplication::clipboard();
clipboard->setText(ans);
if(clipboard->supportsSelection()==0){
ui->display_area->selectAll();
}
};
效果图
问题
存在的一个问题是括号不能自己选择,算法还要设计一下。
11.3更新
将多组数据输入txt文件中,测试算法性能
CPU:Intel(R) Core(TM) i5-7200U CPU @ 2.50GHz 2.71GHz
内存:16.0GB
操作系统:64位Win10
编程语言:C++
文件类型:txt
CPU耗时(单位:s)
版本 \ 情况 | 1万 | 10万 |
---|---|---|
Debug版本 | 0.785 | 8.002 |
Release版本 | 0.164 | 1.724 |