对乡村地名进行模糊匹配

想法是这样的,根据一个随手输入的乡村地名,匹配出其严格的五级行政地址。例如输入的”无极县东侯坊乡南池阳村助农点“,便要匹配出”河北省-石家庄市-无极县-东侯坊乡-南池阳村“。后面的这个五级的行政地址是已知存在数据库里的。

大概的思路是首先进行分词,如上面的分成”无极县“,”东侯坊乡“,”南池阳村“,然后再匹配看有没有包含这几个词的五级行政地名。

第一部分:分词

1.构建分词的地名字典

要分词首先要有字典。这里我们已经明确都是行政地名,所以构建这样的地名字典还是很简单的。还是上面的例子”河北省-石家庄市-无极县-东侯坊乡-南池阳村“,拆分成”河北省“,”石家庄市“,”无极县“,”东侯坊乡“,”南池阳村“便就可存入地名字典了。这个地名字典属于是静态且可重复利用的,这部分我便就在数据库端处理了。

创建地名字典表namedict

CREATE TABLE `namedict` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(50) NULL DEFAULT NULL COLLATE ‘utf8_bin‘,
    PRIMARY KEY (`id`)
)

之后往表里插入记录

insert into `namedict` (name)
select distinct(substring_index(substring_index(village_name,‘-‘,1),‘-‘,-1)) from new_village    -- 河北省
union all
select distinct(substring_index(substring_index(village_name,‘-‘,2),‘-‘,-1)) from new_village    -- 石家庄市
union all
select distinct(substring_index(substring_index(village_name,‘-‘,3),‘-‘,-1)) from new_village    -- 无极县
union all
select distinct(substring_index(substring_index(village_name,‘-‘,4),‘-‘,-1)) from new_village    -- 东侯坊乡
union all
select distinct(substring_index(substring_index(village_name,‘-‘,5),‘-‘,-1)) from new_village    -- 南池阳村

统计了一下地名字典表namedict里共计33万多条的记录。

2.分词判断

有了地名字典就可以进行分词判断了。比如”无极县“,拿这个词到字典里匹配一下是否存在,就知道是否有这个地名了。而”无极县东“在字典里是不存在的,那就可以知道这个地名是不存在的,就不能这样分词了。这里面要注意匹配时判断的效率问题,要避免全扫描,不能每次匹配判断都把33万条记录都扫描一遍,因为一个名字是要经过多次的分词判断才能够得到结果的。也正是这个原因,我把地名字典的所有条目都加载到python的变量里的,这样能够避免多次地和数据库交互。

a.首先先进行hash判断

把33万条记录存放到python的字典变量dict里,那么通过一次hash判断就知道这个地名是否存在。

b.然后再进行范围扫描判断

我们都知道”石家庄市“和”石家庄“是一个意思,但是在dict里面存”石家庄市“而用”石家庄“去匹配是匹配不到的,这里就需要用到范围扫描。

1)把33万个名字再存放到一个list里面,并以一定的规则从小到大排序,如[a1,a2,a3,a4,...,an],且(a1<a2<a3<a4<...an)。

2)if x<a1 or x>an,那就表示x不在list的范围里,也就表示x不在名字字典的范围里,那就不用考虑了。

3)if not (x<a1 or x>an),那么可以先对排序的列表进行分组,每1000元素个作为一个分组。因为是递增排序的,从最右边(最大的)的分组开始,对每个分组的第一个元素(每个分组中最小的元素)进行比较。如果小于等于了这个分组的第一个元素,就表示了这个x在这个分组的范围之内。再以这个分组的第一个元素为开始,向后查询。如果x<=list[i],那么i就是这个范围的起点;如果x>list[j],那么j就是这个范围的终点。这种方式其实就是参考了数据库的索引范围查找算法(B+Tree index range scan),虽说也不算高效,但也比全扫描要好很多了。

3.分词

分词以两个字为起点开始分词,以“无极县东侯坊乡南池阳村助农点”为例:

“无极”是一个词,“无极县”是一个词,“无极县东”不是一个词,那把“无极县”提取出来后面的“东侯坊乡南池阳村助农点”继续。直到拆分成“无极县”,“东侯坊乡”,“南池阳村”。

二.根据分词对五级村名进行匹配

这部分其实也很简单,只要一条SQL语句就能搞定最后的匹配了:

mysql> select village_name from new_village where village_name like ‘%无极县%东侯坊乡%南池阳村%‘;
+------------------------------------------------------------+
| village_name                                               |
+------------------------------------------------------------+
| 河北省-石家庄市-无极县-东侯坊乡-南池阳村                   |
+------------------------------------------------------------+
1 row in set (0.62 sec)

这样的语句是不能走索引的只能全表扫描,不过好在全表也就66万多条记录,总共就几十M的大小,完全可以缓存在数据库的内存里的。

不过即便如此也还是有可优化的空间的。首先把所有的66万个五级行政村名都放到python的list里面,其后可以采取类似数据库全文索引的方法。全文索引的难点在于分词以及stopword的鉴定,而我们这里的条目天然就是已分词好的:”河北省-石家庄市-无极县-东侯坊乡-南池阳村“,也不存在stopword。这里我们需要做的只是记录下每个词所在列表的位置。还是上面那个例子:

fullindex["无极县"]=set([58414, 58415, 58416, 58417, 58418, 58419, 58420, 58421, 58422, 58423,...])

fullindex["东侯坊乡"]=set([58445, 58446, 58447, 58448, 58449, 58450, 58451, 58452, ...])

fullindex["南池阳村"]=set([58460])

再对这几个集合做交集操作得到set([58460]),那list[58460]就是我们要找的“河北省-石家庄市-无极县-东侯坊乡-南池阳村”。(这里得到的是最好的情况有且仅有一个,也有可能会有多个例如只写了个“河北”,那会把河北省所有的村都匹配出来;也有可能一个也没有,那就是地名写的不对,一个也匹配不出来。)

三.运行情况:

1.占用内存

因为对于数据库的操作就是开始的时候加载了两份数据:地名字典(33万)和五级行政村名(66万),大部分的操作都是通过代码实现的,所以最后统计了下比较重要的几个python变量所占用的内存:

存储地名字典的列表:2678096

存储地名字典的dict:12583184

存储五级村名的列表:5429648

存储分词位置的dict:12583184

总共看下来程序运行大概会占用33M左右的内存。

2.花费时间

整个程序运行一次要47秒左右,其中从数据库加载数据的时间比较长,大概要42秒,程序进行分词再对村名进行匹配大概用了5秒左右。如果把程序改成服务端的,那只要在启动的时候加载一次数据即可。要是在分词匹配村名之前能够确认省份的话,那这一动作就能降到1秒左右,就基本能够满足线上操作的要求了。

最后大致的代码如下:

#!/usr/local/bin/python
# -*- coding: utf8 -*-

‘‘‘
Created on 2016年6月12日

@author: PaoloLiu
‘‘‘

from mysqlhelper import *
import logging,re,sys,itertools,time

def logger():
    logging.basicConfig(level=logging.DEBUG,
                    format=‘%(asctime)s [%(levelname)s] [%(filename)s] [%(threadName)s] [line:%(lineno)d] %(message)s‘,
                    datefmt=‘%Y-%m-%d %H:%M:%S‘)

def get_list_dict(mysqldb):

    results=mysqldb.executequery("select name from namedict")

    list_dict=[]

    for row in results:
        list_dict.append(row[0].decode("utf8"))

    list_dict.sort(cmp=None, key=None, reverse=False)

    return list_dict

def get_dic(list_dict):
    dic={}

    for item in list_dict:
        dic[item]=1

    return dic

def index_range(list,item):

#     list要求是已排序的

    length=len(list)

    result={}

    if item<list[0] or item>list[len(list)-1]:
        result["error"]=True

    else:

        for i in range(length/1000,-1,-1):
            if item>=list[i*1000]:
                branch=i*1000
#                 logging.debug("branch="+str(branch))
                break

        for i in range(branch,length):
            if list[i]>=item:
                result["begin"]=i
                result["end"]=i
#                 logging.debug("result[\"begin\"]="+str(result["begin"]))
                break

        for i in range(result["begin"],length):
            if item<list[i]:
                result["end"]=i
#                 logging.debug("result[\"end\"]="+str(result["end"]))

    return result

def isexist(name,dic,list_dict):
    pass

    result=[]

    if dic.get(name):
        result.append(name)
    else:
        pass

        range_result=index_range(list_dict,name)

        if range_result.get("error"):
            pass
        else:
            for i in range(range_result["begin"],range_result["end"]+1):
#                 logging.debug("name="+name)
#                 logging.debug("listname="+list_dict[i][0])
                if re.match(name+".*",list_dict[i]):
#                     logging.debug("name="+name)
#                     logging.debug("listname="+list_dict[i][0])
                    result.append(list_dict[i])

    return result

def div_word(name,dic,list_dict):
    divlist=[]

    while len(name)>=2:
        match_flag=0
#         logging.debug("name:"+name)
        for i in range(2,len(name)+1):
            result=[]
            result=isexist(name[0:i],dic,list_dict)
#             logging.debug("test name:"+name[0:i])
            if len(result)>0:
                pass
                match_flag=1
                previous=result
            else:
                if match_flag==1:
#                     logging.debug("div name1:"+name[0:i-1])
                    divlist.append(previous)
#                     logging.debug(previous)
                    name=name[i-1:]
#                     logging.debug("new name:"+name)
                    break

            if i==len(name) and len(result)>0:
#                 logging.debug("div name2:"+name[0:i])
                divlist.append(result)
                name=name[i:]
            elif i==len(name):
                name=name[1:]

    return divlist

def get_fullname_list(mysqldb):
    strsql="select village_name from new_village;"

    results=mysqldb.executequery(strsql)

    fullname_list=[]

    for row in results:
        fullname_list.append(row[0].decode("utf8"))

    return fullname_list

def get_fullindex(fullname_list):
    fullindex={}

    for i in range (0,len(fullname_list)):
        split_list=fullname_list[i].split("-")
        for item in split_list:
            if fullindex.get(item):
                fullindex[item].add(i)
            else:
                fullindex[item]=set()
                fullindex[item].add(i)

    return fullindex

def main():

    begin=time.time()

    mysqldb=mysqlhelper("dbaadmin","123456","172.16.2.7","3306","spider")
    mysqldb.connect()

    list_dict=get_list_dict(mysqldb)
    dic=get_dic(list_dict)

    fullname_list=get_fullname_list(mysqldb)
    fullindex=get_fullindex(fullname_list)

    load_data_time=round((time.time()-begin),2)
    logging.info("加载数据耗时"+str(load_data_time))
    logging.info("==============================================")

    name=u"无极县东侯坊乡南池阳村助农点"
    divlist=div_word(name, dic, list_dict)   

    for x in itertools.product(*divlist):
        result=fullindex[x[0]]
#         logging.debug(x[0])
#         logging.debug(fullindex[x[0]])

        for i in range(1,len(x)):
#             logging.info(x[i])
#             logging.info(fullindex[x[i]])
            result=result&fullindex[x[i]]

        if len(result)>0:
            for item in result:
#                 logging.debug(item)
                logging.info("match name:"+fullname_list[item])

    match_time=round((time.time()-begin-load_data_time),2)
    logging.info("名称匹配耗时"+str(match_time))

    logging.info("==============================================")
    logging.info("list_dict getsizeof:"+str(sys.getsizeof(list_dict)))
    logging.info("dic getsizeof:"+str(sys.getsizeof(dic)))
    logging.info("fullname_list getsizeof:"+str(sys.getsizeof(fullname_list)))
    logging.info("fullindex getsizeof:"+str(sys.getsizeof(fullindex)))

    mysqldb.close()
if __name__ == ‘__main__‘:
    pass

    logger()

    main()
时间: 2024-08-25 04:02:17

对乡村地名进行模糊匹配的相关文章

利用SQL模糊匹配来验证字段是否是日期格式

最近需要验证数据仓库某个字段是否转化成某种日期格式,比如时间戳格式 '2016-05-03 23:21:35.0', 但是DB2不支持REGEXP_LIKE(匹配)函数,所以需要重新想其他办法. 最后使用了最常规的like来模糊匹配,虽然比不上正则匹配那么精准,但也够用了. 思路: 一个下划线代表一个字符,那'2016-05-03 23:21:35.0'可以表示成'____-__-__-__.__.__.______'. 当然这种办法比较笨,不能识别是数字还是字母还是字符,当然更好的办法是编写U

关键字模糊匹配

关键词模糊匹配,如候选词集合为{‘我爱北京天安门’,‘北京西站’,‘上海外滩’},输入‘北京’,要匹配出{‘我爱北京天安门’,‘北京西站’} 想到了如下几种方法: 1. 正则法 将所有关键词集合存入数组或字典中,然后用关键字进行正则匹配. 效率略慢,400万候选词的话,约用时4s 2.reids法 有两种子方法 keys命令模糊匹配 keys *北京* 官方不推荐这种做法 sscan命令模糊匹配 SSCAN myset 0 MATCH *北京* COUNT 4000000 400万候选词的话,约

Excel 中使用SQL 语句查询数据(七)-----用LIKE 运算符进行模糊匹配查询

这篇博文要和大家分享的是用LIKE 运算符进行模糊匹配查询下图数据源商品代号包含数字的数据. 我们用Microsoft query连接数据源,步骤请参考本系列第一篇博文.语句如下图 其中 LIKE '%[0-9]%' 执行结果如下 然后将结果导入excel  的sheet中

js模糊匹配

<div> <input type="text" placeholder="请输入..." id="input"/><button>搜索</button> <ul id="inputInfo"></ul> </div> let search = [] let list = '' var inputArr = [] document.getEle

selenium模糊匹配控件

起因:在查找一些控件时,可能控件的一些属性是变化的,那在匹配时需要进行模糊匹配,模糊匹配,使用xpath 定位方式有种: contains(属性名,字符串):使用文本匹配,功能很强大 starts-with(属性名,字符串):根据开头进行模糊匹配 ends-with(属性名,字符串):根据结尾内容进行匹配 matchs(属性名,字符串):根据正则进行匹配 案例: 如图,点击底部的一个收藏,弹出OK按钮,需要点击这个Ok,就能正常执行下一步 <span type="1">OK

selenium2 python 学习笔记--xpath模糊匹配

xpath模糊匹配,类似find_by_partial_link,如下图: contains(属性名,字符串),starts-with(属性名,字符串),ends-with(属性名,字符串),matchs(属性名,字符串)

mybatis模糊匹配和正则

模糊匹配 <select id="findByName" parameterType="string" resultType="Student">        select * from student where student.name like "%"#{name}"%";   <select> 正则 <select id="findByName"

Python下用List对员工信息表进行模糊匹配

#需求 用户可以模糊查询员工信息 显示匹配了多少条,匹配字符需要高亮度显示 #脚本内容 #!/usr/bin/env python #_*_ coding:utf-8 _*_ while True:         info = 'info.txt'         f = file(info)         search=raw_input('Please Engter You Search Info: ')         for line in f.readlines():       

c2java 动态规划之模糊匹配

字符串匹配 精确: indexOf(String str); -- strstr(), O(mn). lastIndexOf(String str); -- continue 的别样用法. matches(String regex); -- Regex.compile()/match(). 模糊: java package? Spell Checker -- 两个字符串的相似程度 Fuzzy Finder -- 子列匹配 上面两个问题都可以用这个概念"编辑距离"来有效解决. 所谓这个距