项目地址
https://github.com/1061413241/AddressBook
PSP
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 40 | 30 |
· Estimate | · 估计这个任务需要多少时间 | 40 | 30 |
Development | 开发 | 1125 | 1845 |
· Analysis | · 需求分析 (包括学习新技术) | 300 | 600 |
· Design Spec | · 生成设计文档 | 30 | 50 |
· Design Review | · 设计复审 | 5 | 5 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 30 |
· Design | · 具体设计 | 30 | 30 |
· Coding | · 具体编码 | 600 | 1000 |
· Code Review | · 代码复审 | 20 | 25 |
· Test | · 测试(自我测试,修改代码,提交修改) | 120 | 105 |
Reporting | 报告 | 50 | 75 |
· Test Repor | · 测试报告 | 20 | 30 |
· Size Measurement | · 计算工作量 | 10 | 15 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 20 | 30 |
· 合计 | 1215 | 1950 |
表格内容不算精确,但也大致符合设计的整个流程。实际耗时比预估耗时多了几乎一倍,这是没有计划到的。最开始认为无非是学一些新东西,经历了一个又一个坑后,才知道一坑更比一坑深。
解题思路
C++
1、不妨试试暴力:
刚看到本次作业,最先想到的就是用C++实现了。C++算是学的比较扎实的语言,也用来做过一些小项目,再加上题目要求用vs2017。巧了,可谓是天时地利人和。
- 数据中位置不固定的手机号是一个影响因素,先取出手机号。
- 取出姓名和多余的字符,得到纯地址串。
- 根据关键字取出各级地址,此时出现了不确定的因素:
- 省市级关键字可能缺失,匹配前两级关键字时会出现问题
- 可以导入省市级行政区数据进行匹配
一度要按照这个想法去做了,但是我转念一想,我既然能导入行政区数据,为什么不把整个词典导入呢,这个思路很有趣。于是抱着试一试的心态,果然一下就找到了GitHub上一个项目“cppjieba”,而且还是C++版本的,这下就省事很多了。字符串匹配也不用自己写了,项目直接都提供好了。
2、可恶的cppjieba:
附上项目地址:https://github.com/yanyiwu/cppjieba
直接用cppjieba的方法,实现对地址的分词。根据分词的结果把缺少的信息完善。然后开始按照项目说明去做:
git clone --depth=10 --branch=master git://github.com/yanyiwu/cppjieba.git
cd cppjieba
mkdir build
cd build
cmake ..
make
前四步轻松完成,但最后两步才是真正的重点。
-
cmake、make
之前一直用的是IDE自带的编译工具,对cmake也没什么了解。发现git调用不了这个命令后,就去下载了一个,添加环境变量,cmake完美执行。接着安装make,但是make命令始终报错。查了很多材料,
“CMake是一个跨平台的安装(编译)工具,可以用简单的语句来描述所有平台的安装(编译过程)。他能够输出各种各样的makefile或者project文件。”
关键就在这,我执行完cmake后并不会输出makefile或者项目文件,这样的话make也找不到对应的makefile来执行。应该是cmake的版本不合适,重装3.15.3版本
这次可以生成相应的makefile文件,执行make也可以输出项目文件。但是调入vs里会报一大堆错误。研究了两天,接近放弃。
-
Ubuntu
放弃之前,想起来网上很多关于cmake和make的资料都是在Linux系统上的,于是去虚拟机上试了试。前四步照旧轻松完成,后面系统提示未安装,apt-get,做完所有步骤。输入:
./demo
终端上显示:
[demo] Cut With HMM 他/来到/了/网易/杭研/大厦
我无言以对。
3、小结:
暴力匹配可能不是最好的方法,但肯定是可行的。
cppjieba确实能实现分词功能,而且对于地址的划分准确率还可以,但对于某些地址的划分过于详细,加大了程序实现的复杂度。加上还是没能解决用VS打开的问题,之后有时间要仔细研究一下待解决的问题。
Java
两天时间的浪费,我对C++心生不爽。转战Java。
但依然不屈服于暴力解决,想用点有意思的功能。很早就开始学习Java,但战线拖得太长,一直都只是掌握了皮毛。想为之后的团队项目做些准备,决定学习调用API来解决。
1、华为云API:
对如何使用API毫无头绪,最先找到的就是华为云的地址分词,看完了四十几页的开发文档,0元买了API,发现还是不会用,“先创建APP再调用接口”什么的我是真的搞不懂啊,本以为就是用url使用key的事情,还要创建什么APP,最后不了了之。
2、百度AI:
http://ai.baidu.com/docs#/NLP-Cpp-SDK/top
在b站上看了如何调用API的视频,发现up主用的是百度AI。看起来还算简单,虽然和我要实现的功能没什么关系,但决定先按照教学去练练手。相比华为,百度的开发文档更简单易懂,按照步骤一步一步做,最后实现了第一次调用API,算是对调用API的过程有了一些了解。
3、高德地图API:
https://lbs.amap.com/api/webservice/guide/api/georegeo
学会了调用API后,就开始找合适的接口。高德地图的接口功能非常丰富,可以根据地址返回省、市、县等各级行政区的名称。这样就可以把暴力匹配前几级地址优化到只需调用一个接口来实现。但是,最坑的事情来了,高德地图返回的json数据,格式化校验会出错,导致程序疯狂报错。
绝望.jpg ......
4、百度地图API:
http://lbsyun.baidu.com/index.php?title=webapi/guide/webservice-geocoding-abroad
相比高德地图,百度地图不会返回各级行政区的信息,只能获取到位置的经纬度。只能二次调用接口实现逆地址解析,根据经纬度返回各级行政区信息。这就是最终的实现方案。
设计与实现
上面已经分析了该如何实现地址划分的功能。
主要类和方法:
类名 | 方法名 | 功能 |
---|---|---|
Main | main() | 实现文件流操作,调用Resolution.addressResolution() |
———— | ——————————————— | ———————————————— |
AddressInfo | getName(String) | 获取姓名 |
setName(String) | 设置姓名 | |
getProvince(String) | 获取省份 | |
setProvince(String) | 设置省份 | |
...... | 实现对各个信息的操作 | |
———— | ——————————————— | ———————————————— |
AddressUtils | GetAddressData(String address) | 调用API得到经、纬度json数据 |
GetAddress(String address) | 解析json数据中的经、纬度 | |
GetAddressData2(double lng,double lat) | 调用API得到各级地址json数据 | |
GetAddress2(String addressInfo) | 解析json数据中的各级地址 | |
addressResolution(String address,char flag) | 解析后2级(4级)地址 | |
———— | ——————————————— | ———————————————— |
Resolution | addressResolution(String address,char flag) | 解析手机号和姓名,初始化操作,调用其他类和方法 |
正则表达式解析后2级(4级)地址:
//5级的后2级
(?<town>[^区]+?区|.+?镇|.+?街道|.+?乡)?(?<village>.*)
//7级的后4级
(?<town>[^区]+区|.+?镇|.+?街道|.+?乡)?(?<village1>.+?街|.+?路|.+?巷)?(?<village2>[d]+?号|[d]+.?道)?(?<village3>.*)
关键代码说明
1、解析json数据中的经纬度:
String addressInfobyJson=GetAddressData(address);
JSONObject dataOfJson = JSONObject.fromObject(addressInfobyJson);
dataOfJson=dataOfJson.getJSONObject("result");
dataOfJson=dataOfJson.getJSONObject("location");
addressInfo.setLng(dataOfJson.getDouble("lng"));//经度
addressInfo.setLat(dataOfJson.getDouble("lat"));//纬度
2、解析json数据中的各级地址:
JSONObject dataOfJson = JSONObject.fromObject(addressInfobyJson);
dataOfJson=dataOfJson.getJSONObject("result");
dataOfJson=dataOfJson.getJSONObject("addressComponent");
addressInfo.setProvince(dataOfJson.getString("province"));//省份
addressInfo.setCity(dataOfJson.getString("city"));//市级
addressInfo.setCounty(dataOfJson.getString("district"));//县级
解析json数据最重要的就是理解json的层次结构,有些复杂的json数据会嵌套好几层数据,一不留神就是“null object”。需要不断转换object进行解析。
推荐一个json校验格式化工具:http://www.bejson.com/ 层次分明,一目了然。
性能分析
耗时主要集中在三个部分:
- 读取数据耗时,通过学习不同的读写数据方法,模拟大规模数据进行测试比对,找到更优的方法。
- 匹配数据字符串耗时,应通过简化匹配方式,减少字符串处理过程中的分支数来达到优化。
- 两次调用API耗时,网络请求过程中消耗时间。
计算模块部分单元测试
单元测试是软件开发中必不可少的一环,但是在平常开发中往往因为项目周期紧,工作量大而被选择忽略,这样往往导致软件问题层出不穷。线上出现的不少问题其实在有单元测试的情况下就可以及时发现和处理,因此培养自己在日常开发中写单元测试的能力是很有必要的。
在单元测试中,利用eclipse自带的Junit,其目的是检测每个单一功能的准确性,即你的方法在输入数据之后能够达到所要的结果,在此对部分重要函数进行单元测试。
addressResolution方法(解析地址):
@Test
void testAddressResolution() {
Resolution res=new Resolution();
String address="李四,福建省福州13756899511市鼓楼区鼓西街道湖滨路110号湖滨大厦一层.";
System.out.println("期待的结果:"+"{"姓名":"李四","手机":"13756899511","地址":["福建省","福州市","鼓楼区","鼓西街道","湖滨路","110号","湖滨大厦一层"]}");
System.out.println("实际程序运行的结果:"+res.addressResolution(address, '2').toString());
}
GetAddress方法(获取经纬度):
@Test
void testGetAddress() {
AddressUtils addr=new AddressUtils();
String address="福建省福州市鼓楼区鼓西街道湖滨路110号湖滨大厦一层";
System.out.println("期待的结果:"+"lng=119.29937,lat=26.097462");
addr.GetAddress(address);
double lng=addr.addressInfo.getLng();
double lat=addr.addressInfo.getLat();
System.out.println("实际程序运行的结果:lng="+lng+",lat="+lat);
}
GetAddress2方法(获取行政区信息):
@Test
void testGetAddress2() {
AddressUtils addr=new AddressUtils();
String info=addr.GetAddressData2(119.29937, 26.097462);
addr.addressInfo=AddressUtils.GetAddress2(info);
System.out.println("期待的结果:"+"province=福建省,city=福州市,county=鼓楼区");
String province =addr.addressInfo.getProvince();
String city=addr.addressInfo.getCity();
String county=addr.addressInfo.getCounty();
System.out.println("实际程序运行的结果:province="+province+",city="+city+",county="+county);
}
单元测试结果表明,所有函数都经过了检验。
测试覆盖率
异常处理
主要是针对文本信息缺失情况进行处理。
1、缺少姓名或姓名输入格式不规范:
3!,北京市东15822153326城区交道口东大街1号北京市东城区人民法院.
2、缺少手机号:
3!小美,北京市东城区交道口东大街1号北京市东城区人民法院.
3、缺少地址信息或地址信息不符合题目要求:
3!小美,15822153326东城区人民法院.
4、地址信息中包含“#”号,影响调用API:
//取出#号
String txt1=txt;
int jingIndex=txt1.lastIndexOf("#");
if(jingIndex!=-1)
txt1=txt1.substring(0, jingIndex)+txt1.substring(jingIndex+1);
总结与收获
- 一个人做项目真的是费劲,能力不够,经验不够,疯狂踩坑,自学能力及查找资料解决问题能力太差。
- 前期计划不明确,导致学习方向太杂,要学的东西太多,短时间内无法消化、无法深入掌握。要学会及时止损,时间不足的情况下有所权衡,不能想着全都掌握好,要抓住重点,最大可能的节约时间。
- 掌握的测试方法太少,debug能力不强,和大佬还有很大差距orz
- Typora编辑的图片怎么传啊,为什么复制到博客园全没了......