为什么单循环函数比双循环慢?

时间:2015-07-17 19:33:29

标签: python arrays algorithm performance

此问题源自另一个Stack Overflow问题 - How do I improve remove duplicate algorithm?

问题中发布的要求是 -

  

需要返回删除重复项的数组的长度,但我们最多可以留下2个重复项。

示例 - [1, 1, 1, 2, 2, 3],新数组将为[1, 1, 2, 2, 3]。所以新的长度是5。

OP给出的解决方案 -

def removeDuplicates(nums):
    if nums is None:
        return 0
    if len(nums) == 0:
        return 0
    if len(nums) == 1:
        return 1
    new_array = {}
    for num in nums:
        new_array[num] = new_array.get(num, 0) + 1
    new_length = 0
    for key in new_array:
        if new_array[key] > 2:
            new_length = new_length + 2
        else:
            new_length = new_length + new_array[key]
    return new_length

我尝试了一种解决方案,将循环量减少到一个循环。

def removeDuplicates1(nums):
    if nums is None:
        return 0
    if len(nums) == 0:
        return 0
    if len(nums) == 1:
        return 1
    new_array = {}
    length = 0
    for num in nums:
        n = new_array.get(num, 0)
        new_array[num] = n + 1
        if n <= 1:
            length += 1
    return length

之后,我试图将解决方案与原始解决方案进行对比,我认为我的解决方案应该在原始解决方案上提供至少一点改进,但​​timeit的结果表明原始解决方案始终是更好(即使数组包含所有独特元素)。采取的时间 -

In [3]: l = list(range(1000))

In [4]: %timeit removeDuplicates(l)
1000 loops, best of 3: 390 s per loop

In [5]: %timeit removeDuplicates1(l)
1000 loops, best of 3: 412 s per loop

In [6]: l1 = [1] * 1000

In [7]: %timeit removeDuplicates(l1)
1000 loops, best of 3: 224 s per loop

In [9]: %timeit removeDuplicates1(l1)
1000 loops, best of 3: 304 s per loop

有人可以建议为什么会这样吗?我忽略了一些明显的东西吗?

1 个答案:

答案 0 :(得分:3)

如果输入列表是列表(范围(x)),意味着没有重复,那么您的代码更快,但如果输入列表有大量重复项,那么您的代码会更慢。

我一直有时间与

collections.defaultdict - fastest
original proposal - next fastest (if duplicates)
your single loop proposal - slower, if there are duplicates
collections.counter - slowest

它们基本上都是一样的,所以它们总是在时间上接近。

defaultdict是最快的,因为原始提案基本上重复它,但defaultdict是python附带的核心库的一部分。我猜“不要重新发明轮子”适用。

但是为什么你的代码在使用单个循环时会变慢?考虑原始代码执行两个循环,因为有两个不同的东西要迭代。迭代原始数据列表一次,然后迭代唯一项(由于预期会有重复项,因此可能会更少)。

您的代码执行原始代码所执行的所有操作,但它会对原始数据列表中的每个元素执行此操作。可以把它想象成两个独立的循环,两个循环计数器。您仍然必须为原始列表中的所有元素执行第一个循环。但是第二个循环(你试图通过在原始循环中执行它来摆脱)现在必须为原始数据集中的每个项执行其代码。

通过更频繁地执行它而丢失一个循环所获得的结果,特别是对于原始数据中的重复项。