当该列表包含特殊类别时,如何测试短语是否在大型(650k)短语列表中?
例如,我想测试短语["he","had","the","nerve"]是否在列表中。 它是,但在["he","had","!DETERMINER","nerve"]下,其中"!DETERMINER"是包含多个选项(a, an, the)的wordclass的名称。 我有大约350个单词类,其中一些是相当冗长的,所以我认为枚举列表中有一个(或多个)单词类的每个项目是不可行的。
我想使用一组这些短语而不是慢慢地通过列表,但我不知道如何处理wordclasses的可变性。 速度非常重要,因为我需要每次进行数十万次这样的比较。
与pjwerneck的建议类似,您可以使用树(或更具体地说是trie)将列表存储在部分中,但扩展它以专门处理类别。
# phrase_trie.py
from collections import defaultdict
CATEGORIES = {"!DETERMINER": set(["a","an","the"]),
"!VERB": set(["walked","talked","had"])}
def get_category(word):
for name,words in CATEGORIES.items():
if word in words:
return name
return None
class PhraseTrie(object):
def __init__(self):
self.children = defaultdict(PhraseTrie)
self.categories = defaultdict(PhraseTrie)
def insert(self, phrase):
if not phrase: # nothing to insert
return
this=phrase[0]
rest=phrase[1:]
if this in CATEGORIES: # it's a category name
self.categories[this].insert(rest)
else:
self.children[this].insert(rest)
def contains(self, phrase):
if not phrase:
return True # the empty phrase is in everything
this=phrase[0]
rest=phrase[1:]
test = False
# the `if not test` are because if the phrase satisfies one of the
# previous tests we don't need to bother searching more
# allow search for ["!DETERMINER","cat"]
if this in self.categories:
test = self.categories[this].contains(rest)
# the word is literally contained
if not test and this in self.children:
test = self.children[this].contains(rest)
if not test:
# check for the word being in a category class like"a" in
#"!DETERMINER"
cat = get_category(this)
if cat in self.categories:
test = self.categories[cat].contains(rest)
return test
def __str__(self):
return '(%s,%s)' % (dict(self.children), dict(self.categories))
def __repr__(self):
return str(self)
if __name__ == '__main__':
words = PhraseTrie()
words.insert(["he","had","!DETERMINER","nerve"])
words.insert(["he","had","the","evren"])
words.insert(["she","!VERB","the","nerve"])
words.insert(["no","categories","here"])
for phrase in ("he had the nerve",
"he had the evren",
"she had the nerve",
"no categories here",
"he didn't have the nerve",
"she had the nerve more"):
print '%25s =>' % phrase, words.contains(phrase.split())
运行python phrase_trie.py:
he had the nerve => True
he had the evren => True
she had the nerve => True
no categories here => True
he didn't have the nerve => False
she had the nerve more => False
关于代码的一些观点:
defaultdict的使用是为了避免在调用insert之前检查该子特里是否存在;它会在需要时自动创建和初始化。
如果要对get_category进行大量调用,则可能需要为速度构建反向查找字典。 (或者,甚至更好,记住对get_category的调用,以便常见的单词具有快速查找,但是你不会浪费内存存储你永远不会查找的单词。)
该代码假定每个单词只在一个类别中。 (如果没有,唯一的变化是get_category返回一个列表和PhraseTrie的相关部分循环遍历此列表。)
如果速度很重要并且你必须处理文字,而不是存储短语和文字列表,你应该考虑将它存储为一个单词树,这样孩子的深度就是它在短语中的位置。这样,您可以简单地查询每个级别,并在您向上移动时缩小搜索范围。一旦找不到单词,就知道短语没有列出。
这是一个非常天真的实现,就像你的一个例子。您甚至可以将它实现为这样的嵌套dicts,但如果您的数据很大且是动态的,那么您可能应该使用数据库:
tree = {'he':
{'had':
{'the': {'nerve': {}, 'power': {}, 'gift': {}},
'a': {'car': {}, 'bike': {}},
'an': {'airplane': {}, 'awful': {'smell': {}}}
}
}
}
def find_phrase(phrase, root):
if not phrase:
return True
try:
next = root[phrase[0]]
return find_phrase(phrase[1:], next)
except KeyError:
return False
return False
assert find_phrase(['he', 'had', 'the', 'nerve'], tree) is True
assert find_phrase(['he', 'had', 'the', 'power'], tree) is True
assert find_phrase(['he', 'had', 'the', 'car'], tree) is False
assert find_phrase(['he', 'had', 'an', 'awful', 'smell'], tree) is True
assert find_phrase(['he', 'had', 'an', 'awful', 'wife'], tree) is False
所以基本上列举了所有可能的选择?如果我这样做至少那么我可以把它全部设为一套,并且很容易测试会员资格。我只是害怕它会炸掉列表中的短语数量......
它没有列举所有可能的选项作为成员资格的集合和测试。您将短语数据建模为树而不是列表,因此如果第一个单词存在,则首先在根节点之间进行搜索。如果是,则只搜索其子项的第二个单词,依此类推。如果你找到所有的单词,你就有了匹配。如果您无法在任何级别找到单词,则可以退出搜索。
是的,但是根节点可以是一个单词类,如果在wordclass中有20个选项,并且15个已批准的短语以该单词类开头,那么你需要在树中建模20 * 15个短语,对吗?这似乎与枚举可能的选项非常类似(但我对树木知之甚少,也许我不理解某些东西)
列举案例将构建每一个可能的短语。这不是我对树的建议。使用树,您可以创建单词之间的关系,这样您就可以知道下一个单词可以是哪个单词,因此只要找不到一个单词就停止搜索。我在答案中添加了一个示例实现,所以也许这个想法会让你更清楚。在这样的实现中,查找短语是否存在仅提前n O(1)个查找,其中n是该短语中的单词数。不能比那更好。
首先,制作两个dict:
partOfSpeech = {'a':'!DETERMINER', 'an':'!DETERMINER', 'the':'!DETERMINER'}
words = {'!DETERMINER': set(['a', 'an', 'the'])}
这应该 - 至少 - 让你加快速度。让我们看看这会给你带来多少加速,如果还不够,请发表评论,我会尽力做得更好(或者来自SO社区的其他人甚至可以改进我的解决方案或完全提供更好的解决方案)。
我认为速度的主要问题不在于单词类是在列表还是在字典中,而是批准的短语是在列表还是集合中。现在它看到了!确定它可以很快地测试"它"是否在其中。问题是650k批准的短语都在一个列表(令牌列表)中,并且尝试查找其中一个(可能带有wordclasses)是否与所有候选令牌匹配真的很慢。那有意义吗?
对于您的第一点,set s和dict是基于散列的实现。因此,与列表相比,它们提供O(1)查找时间,其中查找为O(n)。但我明白你的观点。在我匆忙发布可能无用的东西之前,让我先想一想
您的主要问题是您的大型列表(包含所有短语的列表)未以任何方式排序。如果您可以将其作为"预处理步骤"的一部分放在更"有用"的数据结构中,那么您将获得更好的运气。现在,您所能做的就是浏览整个列表 - >慢。如果你可以把这个列表变成一棵树(或者更好,一个特里),那么你将获得更加令人印象深刻的加速