有什么方法可以使这段代码更快?

问题描述 投票:2回答:2

我正在用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的原因。

python python-3.x performance search time
2个回答
2
投票

我注意到几点:

  • 每次调用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()

0
投票

首先您可以使用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
© www.soinside.com 2019 - 2024. All rights reserved.