2018年软工第二次结对作业
软工结对作业之词频统计plusplus
引子
本次作业
队友 董钧昊
Github传送门
1. 分工明细
- 031602509 董钧昊:Java爬虫的设计实现,附加题的设计实现
- 031602523 刘宏岩:升级优化WordCount的功能,命令函参数的使用
2. PSP表格
psp2.1 | personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 10 | 10 |
Estimate | 估计这个任务需要多少时间 | 10 | 10 |
Development | 原型开发 | 600 | 900 |
Analysis | 需求分析(包括学习新技术) | 60 | 120 |
Design Spec | 生成设计文档 | 60 | 60 |
Design Review | 设计复审 | 20 | 150 |
Coding Standrd | 代码规范(为目前的开发制定合适的规范) | 15 | 15 |
Design | 具体设计 | 60 | 180 |
Coding | 具体编辑 | 180 | 180 |
Code Review | 代码复审 | 120 | 120 |
Test | 测试(自我测试,修改代码,提交修改) | 30 | 30 |
Reporting | 报告 | 210 | 300 |
Test Repor | 测试报告 | 30 | 45 |
Size Measurement | 计算工作量 | 10 | 15 |
Postmortem&Process Improvement Plan | 事后总结,并提出过程改进计划 | 20 | 15 |
- | 合计 | 1435 | 2150 |
3. 设计思路及实现流程
- 仔细阅读了作业文档后,总结了本次作业的两点要求:
- 爬取数据,并按照格式生成result.txt
- 升级第一次作业的WordCount,满足各种新增需求
- 有些功能在上次个人作业中已经说明,所以本次作业就不赘述了,要心疼一下我们两位可爱的助教小姐姐~
3.1 Java爬虫
- 最初为了方便起见,用python实现了一遍爬虫,但是考虑到测试问题,最终还是换用JAVA实现了一遍爬虫。
- 如下用流程图形式说明我的具体思路
- 通过分析下图所示的网页源代码,我们可以容易看出,每一份论文的标题URL都被保存在ptitle这个标签中,我们只需通过循环匹配每一个标签上述标签就可以根据获得每一篇论文的标题Title——标签内容。
- 而摘要Abstract的获取则可以通过上图所示中的URL,访问对应子页面。分析子页面的源代码,我们可以看出,摘要被保存在了div id="abstract" 这个标签中,只需匹配标签头尾即可获取标签内容——摘要内容
- 简单说一下本次爬虫的几个注意事项:
- 首先是爬取摘要时,我下意识认为标签尾部只需匹配到 /这个字符即匹配到标签尾,但实际上摘要部分是有可能出现类似于/、div这两种字符串的,所以仍需全文匹配标签尾部。
- 当然提到后向匹配的问题,C++11非boost库不支持后向零宽断言 (具体差异详见上一篇博客),而JAVA这边的库支持大多数正则表达式匹配语言(少量需要外部库支持);本次也可以利用后向零宽断言来实现匹配标签尾部的功能。
- !!!查阅了部分资料后发现,在JDK1.3及之前的JDK版本中并没有包含正则表达式的类,如果要在Java中使用正则表达式必须使用第三方提供的正则表达式库。从JDK1.4开始提供了支持正则表达式API,它们位于java.util.regex包中。所以使用JAVA实现爬虫最好采用JDK1.4版本以上
3.2 代码组织与内部实现设计(类图)
- 本次迭代的新版本代码仅在基础上修改完善了部分函数。
- 并在个人作业基础上优化了原型代码,去除旧代码冗余部分。
- 迭代更新大致内容如下所示
模块 | 备注 |
---|---|
work_2.h | 包含头文件、数据结构以及用到函数的声明 |
Count_chrs.cpp | 统计字符数模块(也包含行数的统计) |
Count_words.cpp | 统计单词、词组数模块(结果计入hashmap) |
Rank_words.cpp | 词频、词组字典序导出模块,用于实现hashmap |
WordCount.cpp | 主函数 |
3.3 命令行解析
- 命令行解析主要通过循环参数判定实现,使用一个循环体来判定传入参数首字符与次字符;同时加入非法检测模块,对不合法参数进行检测并提供错误提示。
- 值得一提的是,在Unix网络编程下,C++有一个getopt()函数可以快速有效地解析命令行参数,具体应用
for (v = 1; v < argc; v++){if (argv[v][0] == '-' && strlen(argv[v]) == 2){if (argv[v][1] == 'i') //输入参数判定{if (v + 1 < argc){strcpy_s(fnew.file_name, argv[v + 1]);fnew.i_flag = 1;v = v + 1;}}else if (argv[v][1] == 'o') //输出参数判定{if (v + 1 < argc){strcpy_s(fnew.out_file_name, argv[v + 1]);fnew.o_flag = 1;v = v + 1;}}else if (argv[v][1] == 'w'){if (v + 1 < argc){fnew.w_flag = 1;if (argv[v + 1][0] == '0') //权重参数判定fnew.weight = 0;else if (argv[v + 1][0] == '1')fnew.weight = 1;elsecout << "error for weight!" << endl;v = v + 1;}}else if (argv[v][1] == 'm'){if (v + 1 < argc){fnew.m_flag = 1;fnew.count_m = atoi(argv[v + 1]); //词组参数判定if (fnew.count_m == 0){cout << "error for m!" << endl;return 0;}v = v + 1;}}else if (argv[v][1] == 'n'){if (v + 1 < argc){fnew.n_flag = 1;fnew.count_n = atoi(argv[v + 1]);if (fnew.count_n == 0){cout << "error for n!" << endl; //排名参数判定return 0;}v = v + 1;}}}}if (fnew.n_flag == 0)fnew.count_n = 10;if (fnew.i_flag == 0 || fnew.o_flag == 0 || fnew.w_flag == 0) //三参数异常判定{cout << "parameters error!" << endl;return 0;}
- 本模块也可简化如下述流程图所示
3.4 w—权重统计
标题中的单词(词组)在统计时算出现十次,摘要中的单词(词组)在统计时算出现一次。
- 实现这个功能关键就是区分标题和摘要,我们的思路是:
- 在提取单词时,通过单词
Title:
和Abstract:
来检测,并设置一个开关istitle
。当检测到Title:
时,置istitle
为1,表示在此状态下提取出的单词(词组)都属于标题部分。否则置为0,表示在此状态下提取出的单词(词组)都属于摘要部分。这样就把单词(词组)用标题和摘要区分开啦! - 当然光有这些还不够,还要根据
w
后的参数来判断是否需要权重功能。敲重点!!! 当istitle
和w
后的参数同时为1时,才能把标题中的单词(词组)按照10计数。 否则不管是标题还是摘要中的单词(词组),都只能按照1计数。 因为我们采用了hashmap的方法统计频,所以只要在插入哈希表的函数上稍作改动即可,即增加一个开关
flag
,当需要计数10次时置flag=1
,插入结点的times
值置为10,否则flag=0
插入结点的times
值置为1。本模块的流程图:
- 贴上关键代码,关键处用注释解释:
//istitle开关部分if (temp_word == "title"&&temp_line[k] == ':'&&k <= temp_line.size())
{fn.count_chars = fn.count_chars - 7;count_words--; //title不算有效单词istitle = 1; //表示目前处于标题状态wordbuf.clear(); //标题和摘要状态切换时,清空队列因为词组不能跨越二者k += 2; //跳过“: ”continue; //获取下一个单词
}
if (temp_word == "abstract"&&temp_line[k] == ':'&&k <= temp_line.size())
{fn.count_chars = fn.count_chars - 10;count_words--; //title不算有效单词istitle = 0; //表示目前处于标题状态wordbuf.clear(); //标题和摘要状态切换时,清空队列因为词组不能跨越二者k += 2; //跳过“: ”continue; //获取下一个单词
}
//插入函数
void hash_insert(Wordnode **l, string word_str,int weight_flag)
{int value = hash_index(word_str); //计算哈希值Wordnode *p;//cout << value;for (p = l[value]; p != NULL; p = p->next) //查找节点并插入{if (word_str == p->word) //已有节点存在(重复单词){p->count += 1 + weight_flag * 9;return;}}p = new Wordnode; //未有节点存在(新单词)p->count = 1 + weight_flag * 9;p->word = word_str;p->next = l[value];l[value] = p;
}
//带权重的插入部分
if (istitle == 1 && fn.weight == 1)
{hash_insert(l, temp_comb,1); //插入10次
}
else
{hash_insert(l, temp_comb,0); //插入1次
}
本模块可单独分割进行测试,使得本模块整体耦合度较低,且本模块内部几乎无冗余代码,也体现了内聚度较高的性质,彻底体现了高内聚、低耦合的特点。
3.5 m—词组统计
- 本参数要求根据m后的参数,统计对应长度的词组词频。
- 我们总结了几个要点:
- 如何提取对应长度的词组
- 准确并完整的提取单词之间的分隔符
- 如何避免非法单词的干扰(数字,the、we等)
- 避免跨越标题和摘要提取单词组成词组
以下是我们的思路:
- 首先定义短单词、数字开头的单词、
Tiltle
和Abstract
为非法单词,其余为合法单词 。 - 在提取词组时,首先就是连续提取
m
个单词。我们采用了队列的思想。每当提取出一个单词时连同它后面的所有分隔符组成一个string
型变量进入队列。然后判断队列中的合法单词数目,如果等于m
,就创建一个临时的空的string
型变量,用它去连接队列中的前m
个合法单词。然后去掉连接后所得字符串后面的所有分隔符,最后插入哈希表,并弹出队首单词。这样我们就取得了完整的分隔符。 - 要去除非法单词的干扰,只需在检测到非法单词时清空队列即可。因为队列中的单词数目不足m且不能后后面进来的单词组合成词组。
要去除
Tiltle
和Abstract
的干扰,每当我们检测到这两个单词时采用continue
忽略,并清空队列,因为队列中的单词数目不足m
且不能后后面进来的单词组合成词组。所以我们要清空队列,防止跨越标题和摘要提取单词组成词组。本部分流程图:
- 贴出关键代码,关键处用注释解释:
//词组统计函数
if (temp_chr <= '9'&&temp_chr >= '0')
{k++; //数字开头判断while (k <= temp_line.size() - 1 && ((temp_line[k] <= '9'&&temp_line[k] >= '0') || (temp_line[k] <= 'z'&&temp_line[k] >= 'a'))){k++; }wordbuf.clear(); //数字开头,清空队列
}
else if (temp_chr <= 'z' && temp_chr >= 'a')
{temp_word = "";temp_word += temp_chr; //字符补足//cout<<temp_word<<endl;k += 1;if (temp_line.size() - 2 > k) //判断余下位数是否够装下一个单词{while (k < temp_line.size() && ((temp_line[k] <= 'z' && temp_line[k] >= 'a') || (temp_line[k] <= '9' && temp_line[k] >= '0'))){temp_word += temp_line[k];//cout << temp_word << endl;k += 1;}//wordbuf.clear();}//检测到合法单词if (3 < temp_word.size() && (temp_word[0] <= 'z'&&temp_word[0] >= 'a') && (temp_word[1] <= 'z'&&temp_word[1] >= 'a') && (temp_word[2] <= 'z'&&temp_word[2] >= 'a') && (temp_word[3] <= 'z'&&temp_word[3] >= 'a')){count_words++;if (temp_word == "title"&&temp_line[k] == ':'&&k <= temp_line.size()){fn.count_chars = fn.count_chars - 7;count_words--; //title不算有效单词istitle = 1; //表示目前处于标题状态wordbuf.clear(); //标题和摘要状态切换时,清空队列因为词组不能跨越二者k += 2; //跳过“: ”continue; //获取下一个单词}if (temp_word == "abstract"&&temp_line[k] == ':'&&k <= temp_line.size()){fn.count_chars = fn.count_chars - 10;count_words--; //title不算有效单词istitle = 0; //表示目前处于标题状态wordbuf.clear(); //标题和摘要状态切换时,清空队列因为词组不能跨越二者k += 2; //跳过“: ”continue; //获取下一个单词}if (fflag == 1) //只统计词组{int thischar = k;//连接单词后的分隔符while (isdivide(temp_line[thischar]) && thischar < temp_line.size()){temp_word = temp_word + temp_line[thischar];thischar++;}wordbuf.push_back(temp_word); //进入队列if (wordbuf.size() == len) //长度满足要求{for (int i = 0; i < len; i++){temp_comb += wordbuf[i];}wordbuf.erase(wordbuf.begin()); //第一个单词清空while (isdivide(temp_comb[temp_comb.length() - 1])) //去除最后的分隔符{temp_comb = temp_comb.substr(0, temp_comb.length() - 1);}if (istitle == 1 && fn.weight == 1){hash_insert(l, temp_comb,1);}else{hash_insert(l, temp_comb,0);}temp_comb = "";}}else //只统计单词{if (istitle == 1 && fn.weight == 1){hash_insert(l, temp_word,1);}else{hash_insert(l, temp_word,0);//cout << temp_word << endl;}temp_word = "";}}else //遇见短单词{wordbuf.clear(); //清空队列}
}
4. 性能分析与改进
- 使用爬取获得的
result.txt
,参数为WordCount.exe -i result.txt -o output.txt -m 3 -n 10 -w 1
,性能测试结果如下图:
- 可以看出消耗最大的函数为C_words()函数,因为该函数执行单词提取和词组拼接的操作,占用的大部分的CPU时间,总时间为6.072s。
- 对此我们提出来静态模块转换为动态模块,不仅是从算法层次上提出的,更是从模块化层次提出的。
- 具体展示模块如下示对比图体现
- 在之前我们通过静态判断词组及其之间的分割符,每次程序运行过程中均需花费大量的时间,维护该静态存储数据结构,且每次都需要遍历整个数据结构才能实现词组的筛选匹配
- 而改进之后,我们采用动态数据结构的形式,同时将模块中的静态化接口模块均转化成动态化程序应用模块,每次仅仅需要遍历部分数据结构,且采用二分法的遍历形式,极大程度的减少了再遍历的开销,使得代码能够以近乎单次完全遍历的结果。
- 改进后的效果如下图所示
- 改进后时间为5.232s,性能速度提升比为13%
5.单元测试
- 设定了10个单元测试用于检测代码的准确性,如下表所示:
单元测试的内容 | 测试的模块 | 输出结果 | 测试结果 |
---|---|---|---|
给定一个字符串 | 字符统计 | 字符数 | 通过 |
给定论文部分摘要A | 单词统计 | 字符数 | 通过 |
给定论文部分摘要B | 词频统计 | 排名前5词组 | 通过 |
非法参数 | 容错检测 | 错误提示 | 通过 |
输入文件异常 | 容错检测 | 错误提示 | 通过 |
输出文件异常 | 容错检测 | 错误提示 | 通过 |
result.txt | 带权重单词统计 | 排名前五单词 | 通过 |
result.txt | 长度为2、不带权词组统计 | 长度为2,排名前五词组 | 通过 |
result.txt | 长度为3、带权词组统计 | 长度为3,排名前五词组 | 修改代码后通过 |
result.txt | 长度为5、不带权词组统计 | 长度为5,排名前五词组 | 修改代码后通过 |
单元测试结果如下图所示:
- 这里提供8、9两次测试的代码
//第八次单元测试
TEST_METHOD(TestMethod8)
{// TODO: 在此输入测试代码File fnew; //控制文件模块Words wnew; //控制单词模块Wordnode *log_list[HASH_LENGTH] = { NULL }; //哈希散列指针数组vector<string> file_str;fnew.n_flag = 1;fnew.m_flag = 1;fnew.w_flag = 1;fnew.count_m = 2;fnew.count_n = 5;fnew.weight = 0;ifstream f;f.open("E:/result.txt", ios::in); //打开文件fnew.count_chars = C_chars(f, fnew, wnew, file_str); //计算字符数(行数)fnew.count_words = C_words(f, fnew, wnew, log_list, file_str); //计算单词数(插入哈希节点)//cout << "chars = " << fnew.count_chars << "," << "lines = " << fnew.count_lines<<","<<"words = "<<fnew.count_words<<endl;rank_word(log_list, wnew, fnew); //词频排名Assert::AreEqual(wnew.word_rank[1], string("this paper"));Assert::AreEqual(wnew.count_rank[1], 469);Assert::AreEqual(wnew.word_rank[2], string("show that"));Assert::AreEqual(wnew.count_rank[2], 340);Assert::AreEqual(wnew.word_rank[3], string("neural networks"));Assert::AreEqual(wnew.count_rank[3], 209);Assert::AreEqual(wnew.word_rank[4], string("neural network"));Assert::AreEqual(wnew.count_rank[4], 187);Assert::AreEqual(wnew.word_rank[5], string("convolutional neural"));Assert::AreEqual(wnew.count_rank[5], 186);}
//第九次单元测试
TEST_METHOD(TestMethod9)
{// TODO: 在此输入测试代码File fnew; //控制文件模块Words wnew; //控制单词模块Wordnode *log_list[HASH_LENGTH] = { NULL }; //哈希散列指针数组vector<string> file_str;fnew.n_flag = 1;fnew.m_flag = 1;fnew.w_flag = 1;fnew.count_m = 3;fnew.count_n = 5;fnew.weight = 1;ifstream f;f.open("E:/result.txt", ios::in); //打开文件fnew.count_chars = C_chars(f, fnew, wnew, file_str); //计算字符数(行数)fnew.count_words = C_words(f, fnew, wnew, log_list, file_str); //计算单词数(插入哈希节点)//cout << "chars = " << fnew.count_chars << "," << "lines = " << fnew.count_lines<<","<<"words = "<<fnew.count_words<<endl;rank_word(log_list, wnew, fnew); //词频排名Assert::AreEqual(wnew.word_rank[1], string("convolutional neural networks"));Assert::AreEqual(wnew.count_rank[1], 196);Assert::AreEqual(wnew.word_rank[2], string("generative adversarial networks"));Assert::AreEqual(wnew.count_rank[2], 178);Assert::AreEqual(wnew.word_rank[3], string("convolutional neural network"));Assert::AreEqual(wnew.count_rank[3], 159);Assert::AreEqual(wnew.word_rank[4], string("visual question answering"));Assert::AreEqual(wnew.count_rank[4], 156);Assert::AreEqual(wnew.word_rank[5], string("deep neural networks"));Assert::AreEqual(wnew.count_rank[5], 128);}
6. 代码覆盖率测试
- 使用
Run OpenCppCoverage
工具测试了本次作业程序的代码覆盖率,结果如下图所示:
- 可以看出覆盖率较低的模块为main函数和count_char函数,主要原因如下述所示:
- main函数中加入了许多异常检测代码段,比如文件打开失败检测等,导致覆盖率降低。
- count_char函数中,为了方便统计单词,我们把文档事先都转化为小写字母,由于爬取结果中大写字母数目很少,所以导致大小写转换的函数的使用率不高,代码覆盖率降低。
7.附加题设计
- 我们的附加题实现的功能主要是围绕CVPR的论文处理进行,我们可通过批量爬取论文PDF保存至本地、读取论文PDF转换文字、论文关键词、高频词检索并绘制词云。
- 为此我们通过Tkinter模块将上述功能整合在一起。
- 原先是想通过Web绘制较为好看的界面的,但是碍于时间限制,最终采用简洁的python模块进行界面UI设计实现。
- 在Windows界面下的效果如下图所示:
- !!!虽说我们结对的俩人是钢铁直男,但是Tkinter的美工界面的也太粗糙了吧,我们认为这也与Tkinter在Windows下的底层实现方式以及移植问题相关。
经过查阅文档后,在Tkinter的首页上,我们得到了答案——Tkinter在Windows上的界面是极其不好看的,所以我们改用Ubuntu实现我们的界面。
仍不死心
在Ubuntu系统上配置好了环境后,再次运行程序的结果如下图所示:
- 本质上还只是将边框切换成了Ubuntu风格的界面,本质上内容很是十分简陋的。既然如此,不妨来看看我们实现的功能吧。
彻底死心 PDF批量下载模块
通过爬虫批量获取论文PDF的URL,同时完成解析功能,并且以text的形式将其保存在本地对应文件夹中,方便后续读取PDF功能的实现。
- PDF转换文字功能
通过将PDF转换成文字功能,从图片扩展到文字,虽说尚不可及我们的初衷——OCR识别论文,但是从结果层面考量已经足够实现这项功能了。
同时这项功能并不只是为了后续词云,我们也可以就此依据Google基于python提供的翻译库,实现论文的翻译,减轻了大多数用户的阅读负担。
或许你常会遇到论文中无法理解的专业术语,而这些术语大多是简写,这时我们该怎么办呢?对此我们可基于此结合情感分析的手段实现文本处理,大致结果图如下图所示:
本次计划实现的情感分析模块也会在团队作业中更加完善化的实现。
- 基于背景图片的词云生成
效果对比图如下图所示:
我们可根据用户所需指定的背景图片,生成更加有趣、好看的词云图,而不是仅仅基于普通的方框模板的词云图;该项功能给用户提供了高自由度的自制定模板,从基于官方固定模板到用户充足发挥想象力。
私以为这类能提供给用户的高自由度功能在当今已经是少之又少,市场上更多的则是限制了用户的思维方式,而本“产品”反其道行之,尽我们的最大程度给用户带来良好体验。
8.Github的代码签入记录
- 匆忙间只迭代了一个版本,但该版本是经过多次测试、修缮完成的。
- 同时提交了 “爬虫”脚本及主程序
9.遇到的代码模块异常或结对困难及解决方法
- 本次遇到的困难及各自的应对方式大致如下表中所列。
异常及困难描述 | 尝试解决 | 是否解决 | 有何收获 |
---|---|---|---|
原型代码的选择 | 尝试测试了双方程序速度,并比对易读性 | 是 | 分析了下述多点如程序运行效率、测试结果准确性在内的诸多考量因素,同时考量了各自编码的优势及可行性来完成分工模块。 |
爬虫设计问题 | 原计划采用C++实现本次爬虫,但多次尝试均受阻于代码量极大且功能复杂的爬虫模块 | 是 | 解决问题不一定要按部就班地照标准模型进行,可以在既有模型的概念基础上,添加自身的思考与体会,打造出更适合团队实际应用的模型应用到本次问题中,若是担心应用的周期性及实用性,则可以选择其他模块开发。 |
钢铁直男的审美 | 尝试邀请美工“大触”,学习“生活与美”等类似界面UI教程 | 是 | 直男毁一生,美工“大触”都不屑跟我们合作,但是这一次我们的美工完成“从0到1”的改进,从流程图的绘制到改进对比图均体现出了我们审美的极大提升。 |
10. 学习进度条
第N周 | 新增代码(行) | 累计代码(行) | 本周学习耗时(小时) | 累计学习耗时(小时) | 重要成长 |
---|---|---|---|---|---|
1 | 300 | 300 | 18 | 18 | 原型设计,爬虫关于python的urllib库及request库学习 |
2 | 0 | 300 | 8 | 26 | 钢铁直男们的审美进步“一点点” |
3 | 500 | 800 | 12 | 38 | Java爬虫、Tkinter界面 |
11.评价队友
- 031602509 董钧昊:
合作了N多次的好基友,而且每次合作都能让我获益匪浅。这次的代码正是以他第一次个人作业的代码为基础,良好的封装和功能划分,让我很快的就看懂了他的思路,几乎只用了一天晚上就完成了附加功能的编码和debug过程,这就是我要向他学习的!
这次结对让最我吃惊的就是,由于我们两个人十一长假都在忙各自的事情,导致这次作业我们只用了两天时间来完成,最后可以在DDL之前实现所有的功能,要归功于我们的高执行力和高效率,非常享受这样的结对过程,希望以后还有机会可以再次合作!
12.总结
- 结对作业从原型到实现,随然时间跨度不长,但仍是一份值得细品其内涵的一份作业。
- 大多数人偏好 “单打独斗”,只愿一个人完成整个功能的实现,而忽视了这种做法存在的致命缺陷——主观意愿过强;而结对作业不仅提供了一个能从客观角度帮助你分析的队友,还提供了一种结对编程形式的模板——其中一人主攻编码模块,另一人则更客观提出编码意见促进编码实现。也算是一种不可多得的编程体验吧。
- 最后我还是想不单仅为我自己,更为我优秀的结对队友鼓下掌吧!
13.附件
- 附件链接
- 密码:aam9
14. 更新(10.17)
- 感谢柯老师帮我们找出了一个 “bug”
- 现已修正为通过采集的CVPR论文绘制出的词云,效果如下所示:
- 选自Misra_Learning_by_Asking_CVPR_2018_paper
转载于:https://www.cnblogs.com/031602523liu/p/9769491.html
2018年软工第二次结对作业相关推荐
- 软工第二次结对作业---部门录取方案实现
结对成员 031502344 郑世强 031502220 李自强 GitHub链接: DepartmanAndStudent 数据生成原理.input_data 使用随机数方法生成,满足以下规则 每个 ...
- [北航软工]第二次团队作业
[BUAA软工]第二次团队作业 Part 1 项目说明 1. 简介 项目名称:语音coding助手(暂定) 说明: 实现一个android app, 通过语音输入来写python代码,和android ...
- 2022软工第二次个人作业
2022软工第二次个人作业 第一部分 调研,评测 酷狗音乐 使用体验 登录界面 功能体验 播放栏 屏幕小图标功能 音乐版块功能 直播模块 听书模块 探索模块 搜索栏及听歌识曲 Bug 分析 窗口重置问 ...
- [BUAA软工第二次]个人作业-软件案例分析
项目 内容 这个作业属于哪个课程 课程社区的链接 这个作业的要求在哪里 作业要求的链接 我在这个课程的目标是 掌握软件工程开发线的基本流程 这个作业在哪个具体方面帮助我实现目标 了解软件团队成员职责. ...
- 计算式二级python_python实现自动生成小学四则运算题目(软工第二次项目作业)...
前言 软件工程 作业要求 作业目标 结对编程:代码实现.性能分析.异常处理说明.记录PSP表格 代码见: github 个人信息:朱育清 3118005437 信安二班 我的partner 个人信息: ...
- 软工实践第二次结对作业
1.结对成员: 031502340 易伟航 031502312 黄阳正 2.项目的Github链接: 第二次结对作业 3.数据和"数据生成"程序的原理以及我们所考虑的因素: 生成数 ...
- 2022秋软工实践 第一次结对编程作业
2022秋软工实践 第一次结对编程作业 需求分析 (1)Need,需求 (2)Approach,做法 (3)Benfit,好处 (4)Competitors,竞争 (5)Delivery,推广 UML ...
- 2021秋软工实践第一次结对编程作业
这个作业属于哪个课程 构建之法-2021秋-福州大学软件工程 这个作业要求在哪里 2021秋软工实践第一次结对编程作业 这个作业的目标 实现博饼软件原型并进行博客写作 学号 031902139 队友学 ...
- 软工网络15个人作业5--软件工程总结
一.请回望开学时的第一次作业,你对于软件工程课程的想象 1. 对比开篇博客你对课程目标和期待,"希望通过实践锻炼,增强计算机专业的能力和就业竞争力",对比目前的所学所练所得,在哪些 ...
- OUC2022秋季软工11组第一次作业
OUC2022秋季软工11组第一次作业 成员自我介绍&对问题的思考 第一位同学 一.自我介绍 姓名 潘子哲(赖皮小鳄鱼) 爱好 打篮球.玩游戏.踢足球 食堂推荐 感觉可以下口的就只有望海了,& ...
最新文章
- kdd cup 2019
- java 登录踢出_spring security 4 如何踢出用户?
- PHP的chunk_split() 函数把字符串分割为一连串更小的部分
- mysql慕课网笔记_mysql学习笔记
- numpy统计分布显示
- python数据录入和分析_hive+python数据分析入门
- 如何用python批量下载数据_Python实现批量下载文件
- Request,Request.Form,Request.QueryString
- 取代SharedPreferences的多进程解决方案
- mysql ssd优化测试_MySQL服务器SSD性能问题分析与测试
- Struts 标签 —— Bean 标签
- (八)理解商业模式画布(87)
- 型材机柜您了解多少?
- Java版本中最好用的网易云音乐、qq音乐api请求工具,你还在忙于如何使用java调音乐api?来看下这里的实现
- 虚拟机服务器系统蓝屏怎么办,虚拟机windows 2008 R2 系统蓝屏
- python drop用法,python数据处理--pandas的drop函数
- Spring Cloud 与 Dubbo 优缺点详解
- Wireshark 64位中文版(抓包工具)
- 渠道分析 之 渠道分析的价值 -3
- 归并排序及其优化(数组归并/链表归并,自顶向下/自底向上等)
热门文章
- 使用FFMpeg 提取MKV文件中的字幕
- 如何度量两幅图像的相似度--结构相似度 SSIM 原理及代码
- win10桌面管理文件收纳_隐藏在Windows 10里的小技巧
- PT站的分享精神,我所向往的PT分享模式(原创)
- 雷视一体机助力智慧高速路侧感知建设
- 从全面了解高级计量经济学体系+stata实操+实证研究前沿+论文+大数据机器学习+空间计量......._Stata高级计量经济学 陈强
- MIMO技术(一)分集与复用
- AWS学习(一)——AWS云技术基础
- 《237 Gbit ps unrolled hardware polar decoder》237 Gbit / s展开的硬件极化解码器
- Android自定义view之围棋动画(化繁为简)