第一次个人编程作业
1. Github项目地址:https://github.com/seeclong/031702329.(使用python评测工具Dubhe v2.1 final,如出现无法评测可以私聊)
2. 给出PSP表格
PSP2.1 | Personal Software Process Stages |
预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 30 | 40 |
· Estimate | · 估计这个任务 需要多少时间 |
10 | 20 |
Development | 开发 | 960 | 1250 |
· Analysis | · 需求分析 (包括学习新技术) |
120 | 240 |
· Design Spec | · 生成设计文档 | 30 | 40 |
· Design Review | · 设计复审 | 30 | 20 |
· Coding Standard | · 代码规范 (为目前的开发 制定合适的规范) |
30 | 30 |
· Design | · 具体设计 | 60 | 40 |
· Coding | · 具体编码 | 480 | 360 |
· Code Review | · 代码复审 | 30 | 40 |
· Test | · 测试(自我测试, 修改代码,提交修改) |
180 | 480 |
Reporting | 报告 | 170 | 270 |
· Test Repor | · 测试报告 | 90 | 120 |
· Size Measurement | · 计算工作量 | 20 | 30 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
60 | 120 |
合计 | **1160 ** | 1560 |
3.1 计算模块接口的设计思路
-
思考初步处理顺序
(1) 首先根据字符串首位的数字判断地址要分割的类型,将"n!"分割出去;
(2) 然后根据","将姓名从中字符串中分割出来;
(3) 接着根据电话号码是11位连续数字且首位为1可以用将正则表达式或者手写函数判断,讲电话号码分割出来,剩余的字符串就是接下来要分级的地址;
(4) 收集每一级地址关键字,通过关键字查找把地址划分成"直辖市/省"、"直辖市/市"、"区/县/县级市"’、"街道/镇/乡"、"详细地址"五级;
(5) 如果需要划分成七级,就把上方的详细地址继续划分出"路名"、"门牌号"、"详细地址";
(6) 如果补充缺失地址,就在事先本地存储的每一级地址资料中查询匹配,然后补全。 -
查找资料
(1) 最开始很快就用C++实现了上述(1)~(5)的功能,测试的效果也还算不错;
(2) 在实现缺失地址补全功能的时候,首先也想过直接在本地存储查找相应地址名称,但由于从区县级以后的数据量大幅提升,最后就没有尝试;
(3) 在查找解决方法时发现可以用百度/高德的Web服务api的地理/逆地理编码功能来实现:先将输入的地址编码成经纬度坐标,再用经纬度坐标逆编码出准确地址,把之前缺失的等级划分用调用api返回的相应等级地址补全;
(4) 找到了方法后就开始动手在线测试,经过一番比较,高德地图在绝大多数地址缺失的情况下的位置识别都比百度都更准确,所以我最终选用了高德的api,然后就是查阅它的技术文档,接着在直接的代码调用实现;
(5) 由于C++没有直接可用的http的get方法,且评测工具不让使用第三方依赖,实现起来更为复杂困难,尝试了一段时间最后以失败告终;
(6) 为了实现get请求调用api,改用python尝试,比较轻易地完成调用以及对地址的查询补充,接着就是把之前写的C++代码手动转换成python,再加上get请求,解析返回的参数,如果某个地址部分缺少即该等级字符串为空,就用返回的该等级字符串替换,就可以实现附加题的要求。
3.2 计算模块接口的实现
-
代码共有5个函数,分别为sub_tel、divide_address_5()、divide_address_7()、get_formatted_address(),它们之间是承接关系
(1) 函数divide_address_5()调用之前要先用sub_tel分割出手机号码
(2) divide_address_7()中包含了divide_address_5():当5级划分完毕后,第五级又可另外划分成更底层的三级
(3) get_formatted_address则是用在需要补全地址时,在divide_address_7划分七级地址后查找出为空字符串的缺失地址,再使用它通过调用api获取地址详细地址json对象的返回值,才可实现对空串补全的功能 -
以下是三个主要函数的流程图:
3.3 独到之处
- 可能就是代码比较长一点,且由于python代码是从C++复制过来转换的,所以也带着强烈的C++风格(
就是改得太累了所以也懒得重新再写一遍更简洁的写法) - 代码模块化做得还行,有分块解决不同的问题,并写出函数来调用,减少冗余代码
- 可读性我觉得也还可以,相信大部分(有耐心的)人都可以一行行轻松看下来,写的简单应该也算一个优点吧
4. 计算模块接口部分的性能改进
- 从上面可以轻易看出,get_formatted_address()函数占了整个程序运行时间的80%+,虽然程序运行的时间大都花在调取Web服务api上,但好在这种方法用来补全前四个等级地址的缺失效果还算不错,而且由于接口被封装得很好,使用它来实现我所需要的功能也并不复杂,但是该方法还是存在一定瓶颈或者说隐患,即高德地图的个人开发者的地理正/逆编码服务每日调用量最大6000(充钱才能变强),如果一旦超过这个数量,就需要另外采取方法来实现地址补全功能…
- 对于性能的改进,就是在模块化实现的过程中,对于每一个单独的模块不断优化,最终减少许多冗余判断和重复查询,以更简单的逻辑切割得到地址。其次在消耗最大的函数get_formatted_address()上我已经差不多写到最简了,它所耗费的时间就主要花在向服务器查询和返回的时间了,至于优化就是尽量少调用它吧(能不用就不用,不过比起存一堆巨大的数据然后在本地一个个查询匹配,时间上应该还是会占据一些优势,
毕竟这是人家的饭碗嘛)
def get_formatted_address(address):#消耗最大的函数代码展示
#根据百度地图api接口获取正地址编码也就是经纬度
url1='https://restapi.amap.com/v3/geocode/geo?address='+address+'&output=JSON&key=a22337e1181873a96bc9701887d1c349'
resp1=requests.get(url1)
resp1_json=resp1.json()
#根据经纬度获取结构化地址
location=resp1_json['geocodes'][0]['location']
url2='https://restapi.amap.com/v3/geocode/regeo?output=JSON&location='+location+'&key=a22337e1181873a96bc9701887d1c349&radius=5&extensions=all'
resp2=requests.get(url2)
resp2_json=resp2.json()
#提取结构化地址
formattted_address=resp2_json['regeocode']['addressComponent']
return formattted_address
5. 计算模块部分单元测试展示
class ALLTest(unittest.TestCase):
def test_sub_name(self): #用于测试分割姓名
name = sub_name('1!王五,云南省昭通市水富18694520738县云川路1号.') #测试数据1
self.assertEqual( name, '王五', msg='sub_name出现错误-1')
name = sub_name('2!楚涡握,湖18883549874北省随州市随县吴山镇唐王街联宏村委会.') #测试数据2
self.assertEqual( name, '楚涡握', msg='sub_name出现错误-2')
def test_sub_tel(self): #用于测试分割号码
tel = sub_tel('1!韶划奸,上海市普陀区长风新村街15717060981道光复西路1995号中山北路6-17号海鑫公寓.') #测试数据3
self.assertEqual( tel, '15717060981', msg='sub_tel出现错误-1')
tel = sub_tel('3!小美,北京市东15822153326城区交道口东大街1号北京市东城区人民法院.') #测试数据4
self.assertEqual( tel, '15822153326', msg='sub_tel出现错误-2')
def test_divide_address_5(self): #用于测试分割五级地址
truth[5] = ["福建省","龙岩市","新罗区","岩山镇","岩山供销社黄固村农资农家店"]
ans[5] = ["","","","",""]
ans[0],ans[1],ans[2],ans[3],ans[4] = divide_address_5('1!钭洋,福建龙岩市新罗区岩山镇岩山供销社黄固村13135601243农资农家店.') #测试数据5
self.assertEqual( ans, truth, msg='divide_address_5出现错误')
truth[5] = ['广东省', '东莞市', '', '凤岗镇', '凤平路13号']
ans[5] = ["","","","",""]
ans[0],ans[1],ans[2],ans[3],ans[4] = divide_address_5('1!小陈,广东省东莞市凤岗13965231525镇凤平路13号.') #测试数据6
self.assertEqual( ans, truth, msg='divide_address_5出现错误')
def test_divide_address_7(self): #用于测试七级地址
truth[7] = ['福建省', '福州市', '鼓楼区', '鼓西街道', '湖滨路', '110号', '湖滨大厦一层']
ans[7] = ["","","","","","",""]
ans[0],ans[1],ans[2],ans[3],ans[4],ans[5],ans[6]= divide_address_7('2!李四,福建省福州13756899511市鼓楼区鼓西街道湖滨路110号湖滨大厦一层.') #测试数据7
self.assertEqual( ans, truth, msg = 'divide_address_7出现错误-1')
truth[7] = ['福建省', '福州市', '鼓楼区', '', '五一北路', '123号', '福州鼓楼医院']
ans[7] = ["","","","","","",""]
ans[0],ans[1],ans[2],ans[3],ans[4],ans[5],ans[6]= divide_address_7('2!王五,福建省福州市鼓楼18960221533区五一北路123号福州鼓楼医院.') #测试数据8
self.assertEqual( ans, truth, msg = 'divide_address_7出现错误-2')
truth[7] = ["上海","上海市","普陀区","长风新村街道","光复西路1995号中山北路6-17号海鑫公寓"]
ans[7] = ["","","","","","",""]
ans[0],ans[1],ans[2],ans[3],ans[4],ans[5],ans[6]= divide_address_7('2!韶划奸,上海市普陀区长风新村街15717060981道光复西路1995号中山北路6-17号海鑫公寓.') #测试数据9
self.assertEqual( ans, truth, msg = 'divide_address_7出现错误-2')
truth[7] = ["湖北省","随州市","随县","吴山镇","唐王街","","联宏村委会"]
ans[7] = ["","","","","","",""]
ans[0],ans[1],ans[2],ans[3],ans[4],ans[5],ans[6]= divide_address_7('2!楚涡握,湖18883549874北省随州市随县吴山镇唐王街联宏村委会.') #测试数据10
self.assertEqual( ans, truth, msg = 'divide_address_7出现错误-2')
- 以下是代码覆盖率截图:
6. 计算模块部分异常处理说明
- 手机号位数出错,处理完手机号部分应返回空串并提示“请确认输入中包含正确的手机号码”
样例:2!李四,福建福州6899511市鼓楼区鼓西街道湖滨路110号湖滨大厦一层.
应返回答案:{'姓名': '李四', '手机': '', '地址': ['福建省', '福州市', '鼓楼区', '鼓西街道', '湖滨路', '110号', '湖滨大厦一层']}
- 姓名后缺少逗号,处理完姓名应返回空串并提示“请检查输入行的姓名后是否缺少逗号”
样例:2!李四福建福州13756899511市鼓楼区鼓西街道湖滨路110号湖滨大厦一层.
应返回答案:{'姓名': '', '手机': '13756899511', '地址': ['福建省', '福州市', '鼓楼区', '鼓西街道', '湖滨路', '110号', '湖滨大厦一层']}
- 没有输入姓名,应直接中断程序并提示“检查输入行的姓名是否错误”
样例:2,福建福州13756899511市鼓楼区鼓西街道湖滨路110号湖滨大厦一层.
- 没输入题目等级,应直接中断程序并提示“请输入题目等级号”
样例:李四,福建福州13756899511市鼓楼区鼓西街道湖滨路110号湖滨大厦一层.
代码示例:
try:
grade,all = sub_grade(all)
name,all = sub_name(all)
tel,all= sub_tel(all)
address = sub_address(all)
except ValueError:
print("ValueError !")
except KeyError:
print("KeyError !")
except IndexError:
print("IndexError !")
except Exception:
print("OtherError !")
else:
province,city,district,town,street =divide_address_5(address)
……