我注意到几点:
- 每次调用
countTokens,都会再次生成相同的转换表(maketrans 调用)。我想这不会被优化掉,所以你可能会在那里失去性能。
-
tokens.split(" ") 创建字符串中所有单词的列表,这相当昂贵,例如当字符串为 100.000 个单词时。你不需要那个。
- 总体而言,您似乎只是想计算一个单词在字符串中包含的频率。使用
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"] 和其他字符串的访问包装在另一个str() 调用中。我猜他们已经是字符串了?如果不是,您需要在此处添加。
Edit2:您在 cmets 中指出标点符号是一个问题。正如我在 cmets 中所说,我认为您不必担心,因为 count 调用只会关心标点符号,如果查询词和配方文本都包含一个,那么 count 调用只会计算周围标点符号与查询内容匹配的出现次数。看看这些例子:
>>> "Some text, that...".count("text")
1
>>> "Some text, that...".count("text.")
0
>>> "Some text, that...".count("text,")
1
如果您希望它的行为有所不同,您可以像在原始问题中那样做一些事情:创建一个翻译表并应用它。
请记住,将此翻译应用于食谱文本(就像您在问题中所做的那样)没有多大意义,从那时起,任何包含标点符号的查询词都不会匹配。这可以通过忽略所有包含标点符号的查询词来更容易地完成。
您可能希望对查询词进行翻译,这样如果有人输入“potato”,您就会发现所有出现的“potato”。这看起来像这样:
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
Edit3:在 cmets 中,您声明要搜索 ["honey", "lemon"] 以匹配 "honey-lemon",但不希望 "butter" 匹配 "butterfingers"。为此,您最初的方法可能是最好的解决方案,但请记住,搜索单数形式“potato”将不再匹配复数形式(“potatoes”)或任何其他派生形式。
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 Counter
transtab = str.maketrans(digits + punctuation, " " * len(digits + punctuation))
def counterFromString(string):
words = string.lower().translate(transtab).split()
return Counter(words)
def normalOrder(recipes, queries):
for recipe in recipes:
recipe["score"] = recipe.get("rating", 0)
title_counter = counterFromString(recipe["title"])
category_counter = counterFromString(recipe["categories"])
ingredient_counter = counterFromString(recipe["ingredients"])
direction_counter = counterFromString(recipe["directions"])
for query in queries:
recipe["score"] += (
8 * title_counter[query]
+ 4 * category_counter[query]
+ 2 * ingredient_counter[query]
+ 1 * direction_counter[query]
)
return recipes
Edit4:我已经用 Counter 替换了 defaultdict——我不知道该类存在。