首页 > 技术文章 > 第一次个人编程作业技术助教总结

rtxux 2019-09-17 17:02 原文

GitHub仓库

PSP

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 30 30
· Estimate · 估计这个任务需要多少时间 30 30
Development 开发 870 1110
· Analysis · 需求分析 (包括学习新技术) 60 60
· Design Spec · 生成设计文档 60 40
· Design Review · 设计复审 20 10
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 10 10
· Design · 具体设计 240 540
· Coding · 具体编码 240 210
· Code Review · 代码复审 60 60
· Test · 测试(自我测试,修改代码,提交修改) 180 180
Reporting 报告 200 240
· Test Report · 测试报告 40 40
· Size Measurement · 计算工作量 40 40
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 120 180
· 合计 1100 1380

Disclaimer

本项目所有代码都可以当成软件工程的反面教材,完全是怎么爽怎么来,没有测试,没有组织,没有性能考虑,故请勿模仿。
数据库的village表没有上传,因其过大,push被GitHub reject.

需求分析

先上题目

题目:损坏的地址簿
描述如下:
公司给实习生小王安排了一个任务,要求将一份客户地址规范化处理,地址簿原先是这样的:
王先生,13756899511,福建福州闽侯上街镇福州大学.陈先生,15063321552,福建省福州市鼓楼区鼓西街道湖滨路233号.
这样的地址不够规范,公司要求小王将地址规范化为如下的字典格式,并存储为json文件:
[{"姓名":"王先生","手机":"13756899511","地址":["福建省","福州市","闽侯县","上街镇","福州大学"]},
{"姓名":"陈先生","手机":"15063321552","地址":["福建省","福州市","鼓楼区","鼓西街道","湖滨路233号"]}]
可是小王不仅没完成任务,还把原始文件弄乱了,手机号码被混杂在地址数据中,请帮小王恢复文件并完成他的任务。
文件现状:
李四,福建省福州13756899511市鼓楼区鼓西街道湖滨路110号湖滨大厦一层.
张三,福建福州闽13599622362侯县上街镇福州大学10#111.
王五,福建省福州市鼓楼18960221533区五一北路123号福州鼓楼医院.
小美,北京市东15822153326城区交道口东大街1号北京市东城区人民法院.
小陈,广东省东莞市凤岗13965231525镇凤平路13号.
请把文件格式化为如下形式:
[{"姓名"':"李四","手机":"13756899511","地址":["福建省","福州市","鼓楼区","鼓西街道", "湖滨路110号湖滨大厦一层"]},
{"姓名":"张三","手机":"13599622362","地址":["福建省","福州市","闽侯县","上街镇", "福州大学10#111"]},
{"姓名":"王五","手机":"18960221533","地址":["福建省","福州市","鼓楼区","","五一北路123号福州鼓楼医院"]},
{"姓名":"小美","手机":"15822153326","地址":["北京","北京市","东城区","","交道口东大街1号北京市东城区人民法院"]},
{"姓名":"小陈","手机":"13965231525","地址":["广东省","东莞市","","凤岗镇","凤平路13号"]}]
请注意:
1.直辖市与一般城市的匹配区别,如样例中的小美。
2.手机号码一定不会和地址中的数字相邻,即不会出现福州市西二环路11380502116922号或者福州大学30#31395521336233这样的情况。
3.地址最终格式应为["直辖市/省","直辖市/市","区/县/县级市","街道/镇/乡","详细地址"]
4.该难度题目不会出现违背题目规则的异常样例。
当你把该任务做好之后,产品经理突发奇想,让你进一步匹配详细地址,进而区分出详细地址中的街道、道路名、门牌号等信息,文件则因变成:
[{"姓名":"李四","手机":"13756899511","地址":["福建省","福州市","鼓楼区","鼓西街道","湖滨路","110号","湖滨大厦一层"]},
{"姓名":"张三","手机":"13599622362","地址":["福建省","福州市","闽侯县","上街镇","","","福州大学10#111"]},
{"姓名":"王五","手机":"18960221533","地址":["福建省","福州市","鼓楼区","","五一北路","123号","福州鼓楼医院"]},
{"姓名":"小美","手机":"15822153326","地址":["北京","北京市","东城区","","交道口东大街","1号","北京市东城区人民法院"]},
{"姓名":"小陈","手机":"13965231525","地址":["广东省","东莞市","","凤岗镇","凤平路","13号",""]}]
请注意:该难度题目中,并非所有的路都叫XX路,也可能叫XX街,XX巷,需自行搜集数据。
产品经理还有个大胆的想法,但是已经被大部分程序员打爆了,没人愿意做,如果你觉得你做得来,可以试一试。地址簿中存在 福建福州市公园路15号 这样的地址,希望能利用地图数据自动补全缺省信息,将其格式化为["福建省","福州市","仓山区","公园路","15号"]或者更完善的形式。(附加题)

从题目分析,该题要求同学编程从文本中提取姓名、电话、地址并以结构化形式输出。题目有三个难度:第一个难度,将文本地址分割为["直辖市/省","直辖市/市","区/县/县级市","街道/镇/乡","详细地址"]的五级地址;第二个难度,将文本地址分割为["直辖市/省","直辖市/市","区/县/县级市","街道/镇/乡","路名","门牌号","详细地址"]的七级地址;第三个难度即附加题,将文本地址分割为上述七级地址并补全缺失的部分。

为了评测的方便和编程的可行性,我在每个输入条目前加了x!的难度标记,用以标识这条输入的难度和输出格式。另外,为了附加题的可操作性,规定只补全前四级地址。

样例详见这里

前期准备

实现思路

为了实现数据的自动生成,我决定从已结构化的数据出发,通过各种混淆,生成用于输入的文本,并使用结构化的数据直接构造答案。

调研

首先需要获取结构化的数据,通过各种搜索找到了china_regionschina-divisions两个GitHub仓库。从第一个仓库获知可以从国家统计局爬取五级行政区划数据;第二个仓库则提示了一些菜鸟的API和示例代码,其中智能地址补全功能可用于获取最后一级地址,但需要一个引子(detail_address参数)。

最后我决定从国家统计局爬取五级行政区划数据存入数据库,再从菜鸟利用前四级行政区划作为限定,第五级区划作为引子,获取最后一级地址存入数据库。从数据库选取若干条信息构成结构化数据,经人工检查和纠错后可用。

数据库设计

垃圾MySQL毁我青春,PostgreSQL万岁
数据库分为province, city, country, town, village, guest_address, entry,分别存储省级行政区、地级行政区、县级行政区、乡镇级行政区、社区级、从菜鸟爬来的地址、题目数据条目。除第一个表外,每个表均存在对上一个表的外键引用。

行政区划爬取和预处理

直接利用china_regions中的代码从国家统计局爬取五级行政区划,并转换为SQL文件,这些在仓库的readme中均有说明,这里不再赘述。
值得注意的是,爬取后的数据中直辖市的省级区划名称与题目要求不同,且存在市辖区、省直辖县之类的数据,为了不增添太多难度,对这部分数据进行修正和剔除。

最后一级地址的爬取

根据china-divisions菜鸟API文档,我们可以使用前四级行政区拼接构成divisionAddress,并使用第五级区划名称作为引子填充detailAddress,通过菜鸟的这个API,获取guess_address作为最后一级详细地址。然后这些数据可用于和前四级地址拼接混淆,构成第一级难度数据,也可再分割出道路,门牌号等,构造第二、三级难度数据。
需要注意的是,有一些镇级区划在菜鸟中不能正确返回数据,表现为返回对象的town字段与传过去的不同,这可能是由于该行政区过于偏僻。因此我们需要对返回对象进行过滤,丢弃town字段不同的对象。

姓名生成

从GitHub上找了个百家姓和常用名的文件(仓库地址忘了),随机拼接组成姓名。所以你们会看到很多颇有喜感的姓名233333

手机号生成

从一组手机号码前缀中随机选取一个,加上8位随机数字,组成手机号。

def get_phone():
    prelist = ["130", "131", "132", "133", "134", "135", "136", "137", "138", "139",
               "147", "150", "151", "152", "153", "155", "156", "157", "158", "159",
               "186", "187", "188", "189"]
    return random.choice(prelist) + "".join(random.choice("0123456789") for i in range(8))

数据生成

第一级难度数据生成

第一级难度数据生成较为简单,只需要拼接地址加混淆即可。从guest_address表中随机抽取数据,根据这里提到的规则进行随机缺失处理,最后上面提到的规则进行后缀省略处理,即完成地址的生成。
手机号插入使用以下算法:
1. 随机选取插入点。
2. 检查插入点是否会与数字相邻,若是,跳到1。
3. 插入手机号
最后进行"%s,%s." % (name, composed_string)的格式化,生成最终的文本。
需要注意的是,这样生成的数据中存在一些奇怪的数据,如乡厂合一的地方,需要人工过一遍数据库,对一些特别诡异的数据进行剔除。

第二级难度数据生成(标注)

这部分数据有两条路子。

路子1

guest_address中随机选取数据,解析出路名、门牌号、详细地址后存储,但这种方法跟做题一样了,还有可能出错,需要人工校对,因此需要第二条路子作为辅助。

路子2

第二条路子是从guest_address人工选取看得爽的数据,标注出路名、门牌号、详细地址后入库。没错,就是低级的手标数据法

最后跟第一级难度一样进行混淆和格式化,生成文本。
最后还需要对路子1生成的数据进行校对。

第三级难度数据生成

这部分偷了个懒,主要从第二级难度的数据中抽取,在生成文本时对前四级地址进行随机缺失处理,然后入库校对。

数据补充

除了以上生成的数据外,我还从guest_address中发掘了一些有趣的数据加入数据集中。(手动滑稽)

文本混淆

合成前混淆

主要是随机缺失处理,将以一定概率删除县级或乡镇级行政区,若县级行政区后缀为县,则以一定概率删除地级行政区

合成时混淆

若省/市级行政区后缀为"省"/"市",则以一定概率删除后缀。另外删去直辖市的省级行政区文本。

存在问题和踩的坑及改进思路

entry表没有引用上一级

刚开始生成时,entry表没有没有存储到guest_address的引用,导致在缺失处理后,需要重新填回缺失的部分困难。

改进思路

还好我对detail_address字段还没有怎么动过,迅速ALTER TABLE并一个UPDATE entry JOIN guest_address,重建外键约束。并修改对应代码保存对guest_address的引用。

数据分布不均匀

在校对生成后的数据时发现,直辖市数据量较大,四个直辖市数据加起来占到总量的1/4。我在从菜鸟爬取时是每个省抽几个市,每个市抽几个县,以此类推,从菜鸟爬取。可能是由于直辖市行政区划比较细密,再加上菜鸟数据也向直辖市倾斜,在爬取其他省份偏僻地区返回的无效数据较多。

改进思路

不对爬取的乡镇级行政区进行计数,而对爬取的有效地址进行计数。
其实我觉得这个问题也不是大问题,而且改进方法也存在问题,中国的行政区划分布本来就不均匀,简单的计数方法搞不定,也不需要搞这个。

手工标注数据效率低下

其实还是自己菜,写不出有效的分割算法(在有数据的情况下还这样,真是太菜了)。还是得有时间去学学这方面的知识,这次作业结束后,也去看看同学们的优秀代码,试着从中学到一点东西。

随机缺失后的数据存在问题

在对行政区进行随机缺失处理后,校对数据时发现了一些问题。数据中存在详细地址开头和上级行政区重复的现象,如"XX镇人民政府",这种数据若乡镇级不缺失,则合成地址为"XX镇XX镇人民政府",否则合成地址为"XX镇人民政府",这种情况生成的答案必然导致匹配问题。

改进思路

在进行随机缺失处理时对下级地址进行检查,并做相应处理。由于我发现这个问题时已从数据库删去对应级别地址且还没重建外键,因此我是一票SQL语句揪出可疑条目,手工处理的。

数据来源过于单一

本次数据生成所用的最后一级地址数据主要来自菜鸟,菜鸟的数据量虽然大,但是还是有一些数据质量很差,可以参考上面一条问题。另外,菜鸟最多分到五级地址,对第二级难度数据的结构化生成不是很友好。这次我前期调研有些过于容易满足,还是要多找几个数据源,保证数据的多样化,也增强冗余性。另外还可以尝试多数据源的交叉验证,提高数据质量。

推荐阅读