Я заметил несколько моментов:
- Каждый раз, когда вы звоните
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: Вы указали в комментариях, что пунктуация является проблемой. Как я уже говорил в комментариях, я думаю, вам не нужно об этом беспокоиться, поскольку вызовы 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: В комментариях вы указали, что хотите, чтобы поиск ["honey", "lemon"] совпадал с "honey-lemon", ночто вы не хотите, чтобы «сливочное масло» соответствовало «сливочным пальцам». Для этого ваш первоначальный подход, вероятно, является лучшим решением, но имейте в виду, что поиск единственной формы «картошка» больше не будет соответствовать форме множественного числа («картошка») или любой другой производной форме.
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: я заменил defaultdict на Counter -не знал, что класс существует.