-
-
Notifications
You must be signed in to change notification settings - Fork 7
/
parsing.py
283 lines (238 loc) · 10.7 KB
/
parsing.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
import re
import itertools
import operator
import pandas as pd
from elasticsearch import Elasticsearch
def inverdic(dic):
resdic = {}
for key, value in dic.items():
for index in value:
if type(value) == set or type(value) == list:
if index in resdic.keys():
if isinstance(resdic[index], set):
resdic[index].add(key)
elif isinstance(resdic[index], list):
resdic[index].append(key)
else:
resdic[index] = [resdic[index]]
resdic[index].append(key)
else:
resdic[index] = key
elif type(value) == dict:
resdic.update(inverdic(value))
return resdic
"""
Словарь, который используется для детекции номера дома, корпуса и т.д.
"""
sep_house_signs = {
'дом': {'д', 'дом'},
'владение': {'владение', 'вл'},
'корпус': {'к', 'корп', 'копр', 'кор', 'корпус'},
'строение': {'с', 'стр', 'строен', 'строение'},
'квартира': {'кв', 'квартира'},
'помещение': {'пом', 'помещение'},
'комната': {"ком", 'комн', 'комната'},
'кабинет': {"кабинет", "каб", "к-т", "каб-т"},
'офис': {'оф', 'офис'},
'литера': set("абвежз"),
'прочее': {'литер', 'литера', 'лит'},
'дробь': {'/', '-'}
}
house_signs_inv = inverdic(sep_house_signs)
def boost_keyword(dic):
replaces_lowered = {}
for key, value in replaces.items():
if isinstance(key, tuple):
new_key = "(" + ' OR '.join(key) + ')^' + str(1 / len(key))
replaces_lowered[new_key] = value
else:
replaces_lowered[key] = value
return replaces_lowered
'''
Этот словарь приводит все типы адресных объектов к стандартному виду (к тому что в ФИАС)
'''
replaces = {
'обл': {"область", "обл", "обл-ть"},
'респ': {"республика", 'респ'},
'край': {'край'},
'г': {'г', 'гор', 'город'},
('ао', 'а.окр'): {'автономный округ', "автономный", 'аокр', 'а.окр'},
('а.обл', 'аобл'): {'автономная область', 'авт.обл', 'аобл', 'а обл', 'аобл'},
('аллея', 'ал'): {'аллея', 'а', 'ал'},
'б-р': {'б-р', 'бульвар'},
'наб': {'наб', 'набережная'},
'пер': {'пер', 'переулок'},
('площадь', 'пл'): {'пл', "площадь"},
('проспект', 'пр-кт'): {"проспект", "пр", "пр-кт", "просп", 'пр-т'},
"пр-д": {"проезд", "пр-д", "прд"},
"ул": {"улица", "ул", "у", 'ул-ца'},
'р-н': {'район', "р", "р-н"},
'п': {'поселок', 'посёлок', "пос"},
'пгт': {'поселок городского типа', 'посёлок городского типа', 'пос. гор. типа', 'пос.гор.типа', 'пос гор типа'},
'г': {'г', 'гор', 'город'},
'с': {'с', 'село', 'сел'},
'д': {'д', 'дер', 'деревня', 'д-ня'},
'с/п': {"сельский поселок", "сельский посёлок", "сп", 'сельское поселение', 'сельпо', 'сп', 'сел.п.', }
}
replaces_inv = inverdic(boost_keyword(replaces))
def del_sp_char(string):
'''
Стоплист. Удаляет из строки символы переноса строки, но словарь можно дополнить при надобности
'''
for stopword in {r'\n', r'\r', '\\', '(', ')', ':'}:
string = string.replace(stopword, ' ')
return re.sub(r"[\d]+", ' \g<0> ', string)
def preprocess(string):
'''
Отделяет всё что можно друг от друга чтобы облегчить токенизацию
'''
string = del_sp_char(string)
# Превращает "2c3" в "2 c 3" и "2-3" в "2 - 3"
string = re.sub(r"[\d]+|[\W]+", ' \g<0> ', string)
string = string.replace(',', ', ') # то же самое, только с запятыми
string = re.sub(r'\,|\.|\-|\'|\"|\(|\)', '', string)
string = re.sub(r' +', ' ', string)
return string
def multiple_replace(dict, text, compiled=False):
'''
Преобразует словарь замен (dict) в паттерн замен для регулярок и тут же применяет его
'''
if not compiled:
regex = re.compile(r"\b(%s)\b" % "|".join(map(re.escape, dict.keys())))
else:
regex = compiled
# For each match, look-up corresponding value in dictionary
return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text)
def tokenize(string, comma=False):
'''
Токенизатор. Раздвигает слипшиеся буквы и цифры вроде 2с3 или корп1
Вход: строка
Выход: массив из слов (токены)
'''
# string = re.sub(r"[\d]+", ' \g<0> ', string).lower() — это уже сделано при препроцессинге
string = string.lower()
if not comma:
return re.findall(r'[\d]+|[\w]+', string)
if comma:
return re.findall(r'[\d]+|[\w]+|\,', string)
def tokens_to_string(tokens, string):
'''
Ищет токены в строке и возвращает их позицию начала
'''
pattern = r".?.?".join(tokens)
found = re.search(pattern, string.lower())
if found == None:
split = len(string)
else:
split = found.start()
return split
def extract_index(string, errors=False): # 100% works !!!
'''
Извлекает индекс из строки
Вход: строка
Выход: адрес без индекса, индекс
'''
index = re.findall(r'[^| |,][\d]{5}[ |$|, ]', string)
if len(index) > 1 and errors:
print("Два индекса в строке \"%s\" ?" % string)
if index != []:
index = index[0]
string = string.replace(index, '').strip()
index = index.replace(',', '')
else:
index = None
return string, index
def clarify_address(tokens, types):
'''
Разбивает строку с номером дома на ещё более точные части.
Возвращает номер дома, корпуса и строения
'''
# add missing "number" type
for i, token in enumerate(tokens):
if token.isdigit():
types[i] = 'число'
if i == 0:
types[i] = "дом"
elif types[i - 1] in sep_house_signs and types[i - 1] != 'литера' and types[i - 1] != 'дробь':
types[i] = types[i - 1]
elif i >= 2 and types[i - 1] == 'дробь':
types[i] = types[i - 2]
types[i - 1] = types[i - 2]
elif types[i] == 'литера' and i != 0:
tokens[i - 1] += tokens[i]
# write this info somewhere
dic = {}
for token, typ in zip(tokens, types):
if token not in house_signs_inv and typ != 'препинания':
if typ not in dic:
dic[typ] = token
# rename
dic['Дом'] = dic.get('дом', '')
if len(dic.get('корпус', '')) > len(dic.get('строение', '')):
dic['Корпус/строение'] = dic['корпус']
elif dic.get('строение', False):
dic['Корпус/строение'] = dic['строение']
return dic
def extract_house_tokens(tokens):
'''
находит последовательность номеров дома/корпуса/строения среди токенов и возвращает их
'''
a = lambda x: "число" if x.isdigit() and len(x) < 6 else "препинания" if x == ',' else "не распознано"
types = [house_signs_inv.get(x, a(x)) for x in tokens]
types_bin = [0 if x == 'не распознано' else 1 for x in types]
array = list((list(y) for (x, y) in itertools.groupby((enumerate(types_bin)), operator.itemgetter(1)) if x == 1))
if len(array) == 0:
return [], []
longest_seq = max(reversed(array), key=len)
return [tokens[i] for (i, _) in longest_seq], [types[i] for (i, _) in longest_seq]
def extract_house(string): # from 2.0
'''
Обёртка для процедуры извлечения номера дома/корпуса от оставшейся строки
Вход: адрес(строка)
Выход: адрес без номеров дома/корпуса, номера дома/корпуса строкой
'''
tokens = tokenize(string, comma=True)
house_tokens, house_types = extract_house_tokens(tokens)
split = tokens_to_string(house_tokens, string)
house = clarify_address(house_tokens, house_types)
address = string[:split]
# house = string[split:]
return address, house
stopwords = {
'российская': '',
'федерация': '',
'орел': 'орёл',
'мо': 'московская обл',
'большой': "(б OR большой)",
'большая': "(б OR большая)",
'малый': "(м OR малый)",
'малая': "(м OR малая)",
'средний': '(ср OR с OR средний)',
'средняя': '(ср OR с OR средняя)',
'нижний': '(н OR нижний)',
'б': "(б OR большая OR большой)",
'с': '(ср OR с OR средняя OR средний)',
'ср': '(ср OR с OR средняя OR средний)',
'м': "(м OR малый OR малая)",
'н': '(н OR нижний)',
'/': ''
}
def optimize_for_search(string):
'''
вводит небольшие изменения в строку поиска для более точного поиска
'''
string = string.replace('ё', 'е')
string = multiple_replace(stopwords, string.lower())
string = multiple_replace(replaces_inv, string)
# string = re.sub(r"[а-яА-Я]{4,}", '\g<0>~^2', string)
return string
housenum_replaces = {
'/': '\/'
}
def optimize_housenum(string):
'''
Пока что делает escape для "/"
'''
# creates escape characters for elasticsearch
string = multiple_replace(housenum_replaces, string)
return '"' + string + '"'