举一个具体的例子:
显而易见的是使用基数树来获取给定前缀的名称列表。但是,这并未考虑频率信息。因此,我不想仅将前5个结果作为第一个词汇结果,而是希望获得最常见的5个名称:
e.g。对于前缀dan
(5913, 'Daniel')
(889, 'Danny')
(820, 'Dana')
(272, 'Dan')
(60, 'Dane')
我错过了一个特里树算法吗?当然,理想的实现(如果存在的话)在我的脑海中是在python中。
更新:一般对Paddy3113提出的建议感到满意,不过我会说它在我提供2.6GB文件时会完全爆炸,这是我正在减少的文件之一。查看详细信息,输出提供了一些见解:
samz;Samzetta|Samzara|Samzie
samza;Samzara
samzar;Samzara
samzara;Samzara
samze;Samzetta
samzet;Samzetta
samzett;Samzetta
samzetta;Samzetta
samzi;Samzie
samzie;Samzie
# Format - PREFIX;"|".join(CHOICES).
我们在赏金方面还有几天,所以我仍在寻找杀手解决方案。因为它不仅仅是关于减少而且关于事物的查找方面。
答案 0 :(得分:4)
是的,我们可以使用trie。 trie节点的最常见名称是(1)该trie节点处的名称或(2)trie节点的子节点的最常用名称。这是一些可以使用的Python代码。
from collections import defaultdict
class trie:
__slots__ = ('children', 'freq', 'name', 'top5')
def __init__(self):
self.children = defaultdict(trie)
self.freq = 0
self.name = None
self.top5 = []
def __getitem__(self, suffix):
node = self
for letter in suffix:
node = node.children[letter]
return node
def computetop5(self):
candidates = []
for letter, child in self.children.items():
child.computetop5()
candidates.extend(child.top5)
if self.name is not None:
candidates.append((self.freq, self.name))
candidates.sort(reverse=True)
self.top5 = candidates[:5]
def insert(self, freq, name):
node = self[name]
node.freq += freq
node.name = name
root = trie()
with open('letter_s.txt') as f:
for line in f:
freq, name = line.split(None, 1)
root.insert(int(freq.strip()), name.strip())
root.computetop5()
print(root['St'].top5)
答案 1 :(得分:2)
在没有任何关于调优的想法的情况下,我首先假设我有一个名称及其频率列表,然后构造一个字典映射前缀到一组带有该前缀的名称,然后将每个集合转换为顶部的列表5个名字频率。
使用从按下的here派生的男生名单列表来创建text file,其中每一行都是整数出现频率,一些空格,然后是这样的名字:
8427 OLIVER
7031 JACK
6862 HARRY
5478 ALFIE
5410 CHARLIE
5307 THOMAS
5256 WILLIAM
5217 JOSHUA
4542 GEORGE
4351 JAMES
4330 DANIEL
4308 JACOB
...
以下代码构造字典:
from collections import defaultdict
MAX_SUGGEST = 5
def gen_autosuggest(name_freq_file_name):
with open(name_freq_file_name) as f:
name2freq = {}
for nf in f:
freq, name = nf.split()
if name not in name2freq:
name2freq[name] = int(freq)
pre2suggest = defaultdict(list)
for name, freq in sorted(name2freq.items(), key=lambda x: -x[1]):
# in decreasing order of popularity
for i, _ in enumerate(name, 1):
prefix = name[:i]
pre2suggest[prefix].append((name, name2freq[name]))
# set max suggestions
return {pre:namefs[:MAX_SUGGEST]
for pre, namefs in pre2suggest.items()}
if __name__ == '__main__':
pre2suggest = gen_autosuggest('2010boysnames_popularity_engwales2.txt')
如果你给dict你的前缀,那么它将返回你的建议(在这种情况下连同它们的频率,但如有必要可以丢弃它们:
>>> len(pre2suggest)
15303
>>> pre2suggest['OL']
[('OLIVER', 8427), ('OLLIE', 1130), ('OLLY', 556), ('OLIVIER', 175), ('OLIWIER', 103)]
>>> pre2suggest['OLI']
[('OLIVER', 8427), ('OLIVIER', 175), ('OLIWIER', 103), ('OLI', 23), ('OLIVER-JAMES', 16)]
>>>
不要尝试: - )
时间杀手
如果运行需要很长时间,那么您可以预先计算dict并将其保存到文件中,然后在需要时使用pickle模块加载预先计算的值:
>>> import pickle
>>>
>>> savename = 'pre2suggest.pcl'
>>> with open(savename, 'wb') as f:
pickle.dump(pre2suggest, f)
>>> # restore it
>>> with open(savename, 'rb') as f:
p2s = pickle.load(f)
>>> p2s == pre2suggest
True
>>>
答案 2 :(得分:0)
你可以基本上增加一个trie实现来将它的子项存储在流行顺序而不是按字母顺序排列,据说你还必须将流行度存储在trie的每个节点中。
答案 3 :(得分:0)
以下是关于如何执行此操作的想法:
构造字符串trie并在树中存储每个节点的整数。此节点指示使用该节点的名称数。因此,当该名称插入到trie中时,您将增加名称的所有节点。
然后,您可以通过贪婪地选择具有最高值的名称来确定名称。
正式它与任何字符串trie构造算法相同,但增加了整数的步骤。
答案 4 :(得分:0)
如果您想快速查找,唯一真正的解决方案是预先计算任何给定前缀的答案。在数据没有变化的情况下这很好,但是你需要一种方法来减少加载时间。
我建议使用DBM来存储预先计算的字典。这基本上是一个字典,其中的内容存储在磁盘上,并在您引用项目时查找。有关详细信息,请参阅http://docs.python.org/library/anydbm.html。唯一的缺点是值必须是字符串,因此您需要存储前5个条目的逗号分隔列表,比如说,并在查找时将其拆分。
这将比pickle更快的启动时间,因为不需要加载DB。它比使用sqlite简单得多。