對鄉村地名進行模糊匹配


想法是這樣的,根據一個隨手輸入的鄉村地名,匹配出其嚴格的五級行政地址。例如輸入的”無極縣東侯坊鄉南池陽村助農點“,便要匹配出”河北省-石家庄市-無極縣-東侯坊鄉-南池陽村“。后面的這個五級的行政地址是已知存在數據庫里的。

 

大概的思路是首先進行分詞,如上面的分成”無極縣“,”東侯坊鄉“,”南池陽村“,然后再匹配看有沒有包含這幾個詞的五級行政地名。

 

第一部分:分詞

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()

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM