【问题标题】:Python: how can this code be made to run fasterPython:如何使这段代码运行得更快
【发布时间】:2017-11-18 06:04:51
【问题描述】:

我是 Python 新手,我正在通过 Codewars 慢慢学习。我知道这可能违反规则,但我有一个效率问题。

给你一个整数列表

ls = [100, 76, 56, 44, 89, 73, 68, 56, 64, 123, 2333, 144, 50, 132, 123, 34, 89]

你必须写一个函数choose_best_sum(t, k, ls)

这样你就可以从 ls 中找到 k 个整数的组合,使得这些 k 个整数的总和接近或等于 t。

我的最终解决方案通过了测试,但在更详细的测试中失败可能是因为效率。我试图更多地了解效率。这是我的代码

import itertools

def choose_best_sum(t, k, ls):
    if sum(sorted(ls)[:k]) > t or len(ls) < k:
       return None
    else:
       combos = itertools.permutations(ls, k)
       return max([[sum(i)] for i in set(combos) if sum(i) <= t])[0]

有人可以强调瓶颈在哪里(我假设在 permutations 调用中)以及如何使这个函数更快?

编辑:

上面介绍的解决方案给出了

0.458 秒内调用 1806730 个函数

 ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    1    0.000    0.000    0.457    0.457 <string>:1(<module>)
    1    0.000    0.000    0.457    0.457 exercises.py:14(choose_best_sum)
742561    0.174    0.000    0.305    0.000 exercises.py:19(<genexpr>)
321601    0.121    0.000    0.425    0.000 exercises.py:20(<genexpr>)
    1    0.000    0.000    0.458    0.458 {built-in method builtins.exec}
    1    0.000    0.000    0.000    0.000 {built-in method builtins.len}
    1    0.032    0.032    0.457    0.457 {built-in method builtins.max}
    1    0.000    0.000    0.000    0.000 {built-in method builtins.sorted}
742561    0.131    0.000    0.131    0.000 {built-in method builtins.sum}
    1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

使用帮助我得到的最终解决方案是:

def choose_best_sum(t, k, ls):
   ls = [i for i in ls if i < t and i < (t - sum(sorted(ls)[:k-1]))]
   if sum(sorted(ls)[:k]) > t or len(ls) < k:
      return None
   else:
      return max(s for s in (sum(i) for i in itertools.combinations(ls, k)) if s <= t)

排序者:标准名称

7090 次函数调用在 0.002 秒内

 ncalls  tottime  percall  cumtime  percall filename:lineno(function)
    1    0.000    0.000    0.003    0.003 <string>:1(<module>)
 2681    0.001    0.000    0.003    0.000 exercises.py:10(<genexpr>)
    1    0.000    0.000    0.003    0.003 exercises.py:5(choose_best_sum)
    1    0.000    0.000    0.000    0.000 exercises.py:6(<listcomp>)
    1    0.000    0.000    0.003    0.003 {built-in method builtins.exec}
    1    0.000    0.000    0.000    0.000 {built-in method builtins.len}
    1    0.000    0.000    0.003    0.003 {built-in method builtins.max}
   17    0.000    0.000    0.000    0.000 {built-in method builtins.sorted}
 4385    0.001    0.000    0.001    0.000 {built-in method builtins.sum}
    1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

【问题讨论】:

标签: python algorithm performance processing-efficiency


【解决方案1】:

你的表达有几个明显的缺陷

max([[sum(i)] for i in set(combos) if sum(i) <= t])[0]
  1. 您无缘无故地运行了两次sum(i)

  2. 您正在将结果打包到列表中 ([sum(i)]),然后将其解包 ([0])

  3. 您无缘无故地将combos 转换为集合

尝试替换为

sums = [sum(c) for c in combos]
return max(s for s in sums if s <= t)

编辑:好的,关于更好算法的一些想法:

天啊!首先,使用itertools.combinations 而不是itertools.permutations。你只是拿总和;项目的顺序没有区别。如果您在 ie k = 4 上运行,combinations 将返回 4! == 在相同输入数据上的条目比 permutations 少 24 倍。

其次,我们希望在一开始就从ls 中丢弃尽可能多的项目。显然我们可以抛出任何 > t 的值;但我们可以得到比这更紧密的界限。如果我们将 (k - 1) 个最小值相加,则最大允许值必须是

(如果我们正在寻找一个精确的和,我们可以反向运行这个技巧 - 添加 (k - 1) 个最大值会给我们一个最小允许值 - 我们可以重复应用这两个规则来丢弃更多的可能性. 但这不适用于这里。)

第三,我们可以查看 (k - 1) 个值的所有组合,然后使用bisect.bisect_left 直接跳转到可能的最佳第 k 个值。有点复杂,因为您必须仔细检查第 k 个值是否尚未被选为 (k - 1) 值之一 - 您不能直接使用内置的 @987654334 执行此操作@ 函数,但您可以使用 itertools.combinations code 的修改副本(即测试 bisect_left 返回的索引是否高于当前使用的最后一个索引)。

这些加在一起应该可以将您的代码速度提高 len(ls) * k * k!... 祝你好运!

编辑 2:

让这成为过度优化危险的教训:-)

from bisect import bisect_right

def choose_best_sum(t, k, ls):
    """
    Find the highest sum of `k` values from `ls` such that sum <= `t`
    """
    # enough values passed?
    n = len(ls)
    if n < k:
        return None

    # remove unusable values from consideration
    ls = sorted(ls)
    max_valid_value = t - sum(ls[:k - 1])
    first_invalid_index = bisect_right(ls, max_valid_value)
    if first_invalid_index < n:
        ls = ls[:first_invalid_index]
        # enough valid values remaining?
        n = first_invalid_index   # == len(ls)
        if n < k:
            return None

    # can we still exceed t?
    highest_sum = sum(ls[-k:])
    if highest_sum <= t:
        return highest_sum

    # we have reduced the problem as much as possible
    #   and have not found a trivial solution;
    # we will now brute-force search combinations of (k - 1) values
    #   and binary-search for the best kth value
    best_found = 0
    # n = len(ls)      # already set above
    r = k - 1
    # itertools.combinations code copied from
    #   https://docs.python.org/3/library/itertools.html#itertools.combinations
    indices = list(range(r))
    # Inserted code - evaluate instead of yielding combo
    prefix_sum = sum(ls[i] for i in indices)          #
    kth_index = bisect_right(ls, t - prefix_sum) - 1  # location of largest possible kth value
    if kth_index > indices[-1]:                       # valid with rest of combination?
        total = prefix_sum + ls[kth_index]            #
        if total > best_found:                        #
            if total == t:                            #
                return t                              #
            else:                                     #
                best_found = total                    #
    x = n - r - 1    # set back by one to leave room for the kth item
    while True:
        for i in reversed(range(r)):
            if indices[i] != i + x:
                break
        else:
            return
        indices[i] += 1
        for j in range(i+1, r):
            indices[j] = indices[j-1] + 1
        # Inserted code - evaluate instead of yielding combo
        prefix_sum = sum(ls[i] for i in indices)          #
        kth_index = bisect_right(ls, t - prefix_sum) - 1  # location of largest possible kth value
        if kth_index > indices[-1]:                       # valid with rest of combination?
            total = prefix_sum + ls[kth_index]            #
            if total > best_found:                        #
                if total == t:                            #
                    return t                              #
                else:                                     #
                    best_found = total                    #
        else:
            # short-circuit! skip ahead to next level of combinations
            indices[r - 1] = n - 2

    # highest sum found is < t
    return best_found

【讨论】:

  • 这是一个很大的帮助。根据您的解决方案,我可以清楚地看到我进行的过多调用。
  • 虽然答案中的提示是正确的,但我很确定上面提到的详细测试仍然会失败。 @GhostRider 尝试提出一种更有效的算法,而不是尝试所有可能的 k 长度排列。
  • 是的,详细的测试仍然失败。我仍然会将它标记为正确,因为它突出了我的代码中的许多缺陷,这些缺陷很容易纠正并且可能有用。我怀疑这部分代码有一种优雅的数学方法。我会继续努力。谢谢
  • @GhostRider:添加了更多想法。玩得开心:-)
  • 太棒了。只是改变组合就产生了巨大的变化。当我整理好所有内容后,我将发布每个修改和最终结果的 cProfile 结果。
猜你喜欢
  • 2015-08-22
  • 2021-11-03
  • 2020-04-26
  • 1970-01-01
  • 1970-01-01
  • 2017-07-11
  • 2021-10-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多