1、结对成员:
031502626 孙浩楷
031502643 朱晓健
2、项目Github链接:
3、最“好”数据:
- 链接:生成数据链接
- "数据生成"程序的原理
①Json::Value 是jsoncpp中最基本、最重要的类,用来表示各种类型的对象。首先创建一个大对象MAIN,其下分别有departments和students两个子对象。
②然后再分别对子对象的各“子子”对象进行赋值。(如departments中的department_no、member_limit、event_schedules和students中的student_no、free_time、applications_department、tags等)
// 部门
void data_dep(int n) {
id.clear();
Json::Value Item;
int tgn, i, j;
for (i = 0; i < n; ++i) {
memset(used, false, sizeof(used));
memset(flag, false, sizeof(flag));
// 生成常规活动时间
int cnt = rand() % 4 + 2;
for (j = 0; j < cnt; ++j) {
string tmp;
tmp = getTime();
Item["event_schedules"].append(tmp);
}
// 生成部门编号
Item["department_no"] = depId[i] = getDepId();
// 生成部门纳新人数
Item["member_limit"] = rand() % 5 + 10;
// 生成标签
tgn = rand() % 4 + 2;
for (j = 0; j < tgn; ++j) {
int inx;
do {
inx = rand() % 12;
} while (used[inx]);
used[inx] = true;
// 标记这个标签被部门使用过
vis[inx] = true;
Item["tags"].append(tag[inx]);
}
MAIN["departments"].append(Item);
Item.clear();
}
return;
}
具体的方式为通过包装函数生成,其中大部分采用rand()随机生成数据。(如星期、时间段等限定数据通过数组存储)
// 随机生成唯一的部门编号:D开头,后加3位数字,如D001
string getDepId() {
string DepId = "D";
int count = 0;
do {
if (count >= 1)// 防止生成重复DepId,重置
{
DepId = "D";
}
for (int i = 0; i < 3; i++) {
DepId += rand() % 10 + '0';
}
} while (id[DepId]);
id[DepId] = true;
return DepId;
}
并采用相关措施来防止随机生成的重复数据。(如上面的count计数器监测是否重复以重置)
③最终通过 Json::Writer 将 Json::Value 对象转化成字符串对象并用 std::ofstream out 输出到txt文件中。
Json::StyledWriter writer;
string str = writer.write(MAIN);
std::ofstream out;
out.open("input_data.txt");
out << str;
out.close();
- 考虑的因素
①作业对各项数据的要求:如20个部门、300个学生,标签两个以上、字符型等硬性限制。
②从实际出发考虑的数据特性:如“学生空闲时间段”本可随机生成0-24小时,但考虑实际情况,将其改成8-22
③防止重复,因为采用rand()随机生成相关数据,但有可能出现重复,在加入匹配程序调试后还发现一个问题。
string getDepId() {
string DepId = "D";
int count = 0;
do {
if (count >= 1)// 防止生成重复DepId,重置
{
DepId = "D";
}
for (int i = 0; i < 3; i++) {
DepId += rand() % 10 + '0';
}
} while (id[DepId]);
id[DepId] = true;
return DepId;
}
对比
string getDepId() {
string DepId = "D";
do {
for (int i = 0; i < 3; i++) {
DepId += rand() % 10 + '0';
}
} while (id[DepId]);
id[DepId] = true;
return DepId;
}
最早前为下面的版本,此时输入文件中出现了0315026262626这种情况,后面发现原因是当while()里的条件满足时,没有对已生成的字符串清空,导致继续往后加字符的情况。最后针对问题修改为上面的版本。
4、数据建模及匹配程序的思路及实现方式:
数据建模
Student类的student_no及Department类的department_no用string类型,而application_department及event_schedules因为各部门和每个同学的具体情况不同,采用动态string数组指针。Student类和Department类互为友元类,方便成员函数的数据访问。匹配程序思路
①首先根据学生的部门意愿顺序来匹配,只要该学生的一个空闲时间段和该部门一个常规活动时间段匹配,该学生就能加入到该部门中。
②然后,再对所有部门进行扫描,如果发现申请人数超过限制人数,则剔除兴趣标签不匹配的学生,直到部门人数等于限制人数为止,如果兴趣标签不匹配的学生都剔除完了申请人数仍然超过限制人数,则随机剔除。
③最后,对被剔除的学生再进行一次部门时间段匹配,以保证尽可能多的学生能分配到部门。实现方式
首先利用Json::Reader将txt的字符流转换成Json::Value
使用函数Student::StuAssignment和Department::DepAssignment将Json::Value数据读入到各自的类私有数据中。
按照匹配原则使用Student::TimeMatch进行学生空闲时间段和部门常规活动时间段的匹配,匹配成功则加入该部门,并删除新成员相应的free_time,防止和后续的意愿部门时间冲突。
扫描每个部门的人数,若超过限制人数,则使用Department::InterestFilter函数对部门成员进行剔除,优先剔除兴趣标签不匹配的,直到人数等于限制人数,对被剔除的学生归还相应的free_time。
对被剔除的学生使用Student::Redistribute函数调用Student::TimeMatch函数进行匹配。
将相关数据写入Json::Value,使用Json::StyledWriter转换成字符流并输出。
5、代码规范:
对较长有意义性的变量名采用驼峰式命名,对普通变量名采用纯小写命名。
string getStuId() {
string StuId = "03150";
int count = 0;
do
{
if (count >= 1)// 防止生成重复StuId,重置
{
StuId = "03150";
}
for (int i = 0; i < 4; i++) {
StuId += rand() % 10 + '0';
}
count++;
} while (id[StuId]);
id[StuId] = true;
return StuId;
}
6、结果评估:
以作业所给示例的input_data.txt为测试数据,我们的项目跑出的结果是126个unluncky_student,0个unlucky_department。
根据上述匹配原则,这个结果表示每个部门都招到了自己的新成员。
本次匹配原则并没有非常地严苛,只要学生空闲时间里的一个与部门活动时间段匹配即算匹配成功,因此unlucky_student相较于全匹配原则减少了很多。
对匹配结果基本满意。
最后一个对被踢出的学生按照其部门意愿再进行分配的原则能够最大程度的保证每个学生能分配到部门,也减少了unlucky_student的数量。
当然,如果空闲时间总是不和部门活动时间相匹配的话也只能成为UnluckyStudent了。
7、结对感受:
这次的合作我主要负责生成input_data部分,而晓健主要负责匹配部分程序的编写。
在一开始看到任务的时候我们两个都是一脸懵逼的。。什么是json?这种格式的数据怎么生成?为什么直接include“json.h”无法奏效?为什么弄个jsoncpp那么麻烦?。。。
不过在计算机这个专业混久了以后尤其是在软工实践这门课呆久了以后(赶紧改成呆。。不然让栋哥发现我们在混。。orz)也习惯了这种一看到作业生无可恋的感觉。
于是又是一轮轮的google、骂人、互相请教。。
在这次项目合作中,通过任务分配、代码规范统一、数据模型设计和jsoncpp等资料共享等经历,更加深刻地体会到了团队开发和个人开发的区别及意义所在--是压力,也是动力,更多的是一种互相扶持前进的友谊。
总的来说,这次项目的评测结果还是令人满意的。126个unluncky_student,0个unlucky_department,也算是较好地完成了部门、学生匹配要求。
在后续的作业和探索中,继续加油吧!