我正在用python编写此搜索引擎以获取食谱列表,并且每次搜索都应以一定的速度运行,最长不超过0.1秒。我一直在努力用我的代码来达到这种速度。我平均得到0.4。我想知道您是否对如何使此代码更快有任何想法。我已经尝试了很多事情,但是我知道循环是使循环变慢的循环。如果我可以使用大多数python来改善它,而不必添加那么多模块。
我已经在代码0.005平均的其他部分上加快了速度。但是在这一部分中,大量的食谱变得非常缓慢。
def countTokens(token):
token = str(token).lower()
#make digits an punctuations white spaces
tokens = token.translate(token.maketrans(digits + punctuation,\
" "*len(digits + punctuation)))
return tokens.split(" ")
def normalOrder(recipes, queries):
for r in recipes:
k = r.keys()
parts, scores = [[],[],[],[]], 0
parts[0] = countTokens(r["title"])
parts[1] = countTokens(r["categories"]) if "categories" in k else []
parts[2] = countTokens(r["ingredients"]) if "ingredients" in k else []
parts[3] = countTokens(r["directions"]) if "directions" in k else []
for q in queries:
scores += 8 * parts[0].count(q) + 4 * parts[1].count(q) + 2 * parts[2].count(q) + 1 * parts[3].count(q)
r["score"] = scores + r["rating"] if "rating" in k else 0
return recipes
在一点上下文上,仅当具有查询条件时,我才需要对上述四个描述符中查询的出现次数求和,这就是为什么我具有if的原因。
我注意到几点:
countTokens
时,您将再次生成相同的转换表(maketrans
调用)。我想这不会被优化,所以您可能会在那里失去性能。tokens.split(" ")
创建字符串中所有单词的列表,这非常昂贵,例如当字符串是100.000个单词时。您不需要。string.count()
,您可以计算大量的发生更少的开销。如果您应用了它,就不再需要string.count()
函数了,只需进行一点重构就可以了:
countTokens
这对您有用吗-它足够快吗?
编辑:在您的原始代码中,您将对def normalOrder(recipes, queries):
for recipe in recipes:
recipe["score"] = recipe.get("rating", 0)
for query in queries:
recipe["score"] += (
8 * recipe["title"].lower().count(query)
+ 4 * recipe["categories"].lower().count(query)
+ 2 * recipe["ingredients"].lower().count(query)
+ 1 * recipe["directions"].lower().count(query)
)
return recipes
和其他字符串的访问权包装在另一个recipe["title"]
调用中。我想它们已经是字符串了吗?如果不是,则需要在此处添加。
Edit2:您在评论中指出标点符号是一个问题。正如我在评论中说的,我认为您不必担心,因为如果查询词和配方文本都包含一个,则str()
调用仅关心标点符号,因此count调用仅会计数周围标点符号与查询内容相匹配的情况。看一下这些例子:
count
如果您希望这样做的行为有所不同,则可以执行与原始问题相同的操作:创建翻译表并应用它。请记住,将此翻译应用于配方文本(就像您在问题中所做的那样)没有多大意义,因为从那时起,任何包含标点符号的查询词都永远不会匹配。只需忽略所有包含标点符号的查询词,就可以轻松得多。您可能希望对查询词进行翻译,以便在有人输入“马铃薯”的情况下,发现所有出现的“马铃薯”。看起来像这样:
>>> "Some text, that...".count("text")
1
>>> "Some text, that...".count("text.")
0
>>> "Some text, that...".count("text,")
1
Edit3:在评论中,您指出要搜索[“ honey”,“ lemon”]以匹配“ honey-lemon”,但不希望“ butter”匹配“ butterfingers”。为此,您的初始方法可能是最好的解决方案,但是请记住,搜索单数形式的“马铃薯”将不再与复数形式(“马铃薯”)或任何其他派生形式匹配。
def normalOrder(recipes, queries):
translation_table = str.maketrans(digits + punctuation, " " * len(digits + punctuation))
for recipe in recipes:
recipe["score"] = recipe.get("rating", 0)
for query in queries:
replaced_query = query.translate(translation_table)
recipe["score"] += (
8 * recipe["title"].lower().count(replaced_query)
+ 4 * recipe["categories"].lower().count(replaced_query)
+ 2 * recipe["ingredients"].lower().count(replaced_query)
+ 1 * recipe["directions"].lower().count(replaced_query)
)
return recipes
如果您以相同的次数更频繁地调用此函数,则可以通过将def normalOrder(recipes, queries):
transtab = str.maketrans(digits + punctuation, " " * len(digits + punctuation))
for recipe in recipes:
recipe["score"] = recipe.get("rating", 0)
title_words = recipe["title"].lower().translate(transtab).split()
category_words = recipe["categories"].lower().translate(transtab).split()
ingredient_words = recipe["ingredients"].lower().translate(transtab).split()
direction_words = recipe["directions"].lower().translate(transtab).split()
for query in queries:
recipe["score"] += (
8 * title_words.count(query)
+ 4 * category_words.count(query)
+ 2 * ingredient_words.count(query)
+ 1 * direction_words.count(query)
)
return recipes
的结果存储在这些次数中来获得更高的性能,因此您不必在每次调用时都重新创建该列表。
取决于您的输入数据(平均要查询多少个查询?),也可能只经过一次.lower().translate().split()
结果并建立一个倒排索引(将一个单词映射到字符串中的计数) )。这也可以保留在函数调用之间,并且搜索速度更快,但是构建起来更昂贵:
split()
首先您可以使用from collections import defaultdict
def invertedIndexFromString(string, transtab):
words = string.lower().translate(transtab).split()
index = defaultdict(int)
for word in words:
index[word] += 1
def normalOrder(recipes, queries):
transtab = str.maketrans(digits + punctuation, " " * len(digits + punctuation))
for recipe in recipes:
recipe["score"] = recipe.get("rating", 0)
title_index = invertedIndexFromString(recipe["title"], transtab)
category_index = invertedIndexFromString(recipe["categories"], transtab)
ingredient_index = invertedIndexFromString(recipe["ingredients"], transtab)
direction_index = invertedIndexFromString(recipe["directions"], transtab)
for query in queries:
recipe["score"] += (
8 * title_index.get(query, 0)
+ 4 * category_index.get(query, 0)
+ 2 * ingredient_index.get(query, 0)
+ 1 * direction_index.get(query, 0)
)
return recipes
代替if条件。
get