我正在尝试编写一个自动填充用户输入的程序,该程序可能是以下之一:机场的三个字母IATA代码,一个城市的名字,某个给定语言中的城市名称,机场名称,国家/地区名称,一个州的名字。
机场数据全部位于Airport
类的实例中,该类具有.match()
方法,确定是否有任何相关属性以用户输入开头。
以下是所有相关代码:
class Location(object):
def __init__(self, code, location_type):
self.code = code # Country/city/state/airport codes. Format varies.
self.type = location_type
self.name = self.get_name() # String containing name of location.
if self.type == 'city':
self.localizations = self.get_localizations()
# The above is a dictionary, keys are locales (ex. 'fr-FR'), values
# are the translated city names in the specified locale.
def match(self, pattern, match_code=False, locales=[]):
if match_code: # Fires if we only match for the 3 letter IATA code
return pattern.match(self.code)
if not self.name: # Some instances don't have names
return None
if locales and self.localizations: # Fires if there's languages given
for locale in locales:
match = pattern.match(self.localizations.get(locale, ''))
if match:
return locale
return None
return pattern.match(self.name)
class Airport(Location):
def __init__(self, airport, city=None, state=None, country=None):
self.code = airport
self.type = 'airport'
self.name = self.get_name()
self.city = Location(city, 'city')
self.state = Location(state, 'state')
self.country = Location(country, 'country')
matches = []
pattern = re.compile('^' + keyword, re.I) # Keyword is the user's input
for airport in airports: # airports is a list of Airport instances
if airport.match(pattern, match_code=True):
matches.append(airport.create_match('airport', 100))
elif (airport.city.match(pattern)
or airport.city.match(pattern, locales=locales)):
if airport.city.match(pattern):
matches.append(airport.create_match('locality', 70))
else:
locale = airport.city.match(pattern, locales=locales)
matches.append(airport.create_match('localised_locality', 70,
locale=locale))
elif airport.match(pattern):
matches.append(airport.create_match('airport', 50))
elif airport.country.match(pattern):
matches.append(airport.create_match('country', 30))
elif airport.state.match(pattern):
matches.append(airport.create_match('state', 30))
根据我的测试,Airport.match()
方法几乎一直占用。目前有9451个Airport
个实例,我的电脑上搜索大约需要50毫秒。
我的程序是在启动时创建所有这些实例的程序,从XML文件加载它们,因此我可以根据需要修改源数据。
答案 0 :(得分:1)
我认为你正在倒退。那是什么意思?好吧,在我看来,你要匹配的东西列表是静态的(相对),而你的用户将一次输入一个字符的数据。您应该做的是将您可能自动完成的所有内容放入已排序的数组中,然后每次用户键入另一个字符时,找到数组中与用户输入的前缀匹配的第一项。
你可以通过记住你到达的最后一个地方进行优化,例如如果用户键入'S',当你得到下一个字符时,你开始搜索数组中的第一个'S';如果他们输入'SF',那么你从'SF'开始搜索,依此类推。
以下是基于您上面所写内容的示例:
import bisect
# Construct the search array
search_array = [(l.code.lower(), l) for l in locations] + [(l.name.lower(), l) for l in locations] + [(a.city.lower(), a) for a in airports] + [(a.state.lower(), a) for a in airports] + [(a.country.lower(), a) for a in airports]
search_array.sort()
# Now, assume the user enters 'S'; we do
new_entry = bisect.bisect_left(search_array, ('S'.lower(), None))
if new_entry < len(search_array):
found = search_array[new_entry]
if found[0].startswith('S'.lower()):
entry = new_entry
autocompletion = found[0]
# Let's say they now enter 'F'; we do
new_entry = bisect.bisect_left(search_array, ('SF'.lower(), None), entry)
if new_entry < len(search_array):
found = search_array[new_entry]
if found[0].startswith('SF'.lower()):
entry = new_entry
autocompletion = found[0]
等等。显然,这只是Stack Overflow中的一个例子。
答案 1 :(得分:-1)
我想出了一个解决方案,但由于它很难看而且最终只使用了一点点,因此以下内容仅供参考:
初始化数据时,为每个实例分配一个名为Location.match_name
的附加属性。这只是self.name.lower()
,但是通过预先分配它,我们消除了使用不区分大小写的正则表达式匹配的需要,甚至在我们匹配时将self.name
转换为小写。相反,我们会将keyword.lower()
与Location.match_name[:len(keyword)]
进行比较。 (不使用str.startswith()
,因为它由于某种原因而具有较差的性能。)
然而,如果我没记错的话,这只能带来大约5%或10%的加速,而且我决定不牺牲代码的可读性和初始化时间。