【问题标题】:How to find unpaired elements in an array fast? [closed]如何快速找到数组中未配对的元素? [关闭]
【发布时间】:2019-07-15 17:49:14
【问题描述】:

我正在练习 Codility 的一些问题。然而,每次我运行这些问题时,我的性能(运行时)得分都非常低(25%)。你能帮我知道如何改进我的代码以获得更好的分数吗?

问题是:

写一个函数:

def solution(A)

给定一个由满足上述条件的 N 个整数组成的数组 A,返回未配对元素的值。

例如,给定数组 A 使得:

  A[0] = 9  A[1] = 3  A[2] = 9
  A[3] = 3  A[4] = 9  A[5] = 7
  A[6] = 9

该函数应返回 7,如上例所述。

我的代码是:

def solution(A):
# write your code in Python 3.6
    lis=[i for i in A if A.count(i) ==1]
    return lis[0]

输出:

中等2 “中等随机测试 n=100,003 ✘TIMEOUT ERROR 被杀。达到硬限制:6.000 秒"

【问题讨论】:

  • 这可能是Code Review堆栈站点的一个好问题
  • @G.Anderson 谢谢。我不知道。
  • @AndrejKesely 感谢也尝试过这种方法。但是对于这两种方法,codility 给出了相同的分数。感谢您的帮助。
  • 优化相关问题适用于 Stack Overflow,无需移至 Code Review。问题中描述的问题相对较小且集中,因此我不确定为什么要应用“过于宽泛”的密切原因。另外,根据答案,可以通过了解正确的python函数来解决问题,这正是Stack Overflow的目的。投票支持重新开放。

标签: python python-3.x optimization


【解决方案1】:

这是因为list.count每次都会搜索整个列表,也就是O(N) * N,或者N**2。您可以使用collections.Counter 来计算一个项目出现一次或一次通过的次数,并且查找是 O(1),因为它是一个字典:

from collections import Counter

def solution(A):
    c = Counter(A)
    # this will iterate over all the key/value pairs
    # which is at worst N elements long
    return [k for k, v in c.items() if v==1]

显示速度增加:

python -m timeit -s "from random import randint; A = [randint(0,500) for i in range(10000)]" "x = [a for a in A if A.count(a)==1]"
10 loops, best of 3: 957 msec per loop


python -m timeit -s "from random import randint; from collections import Counter; A = [randint(0,500) for i in range(10000)]; c = Counter(A)" "x = [s for s, v in c.items() if v==1]"
10000 loops, best of 3: 20.1 usec per loop

即使我每次都创建一个随机列表,Counter 实现在 20 次试验中的平均最佳运行时间是 20.2us,而list.count 实现是 962.1ms。因此,即使每次运行 timeit 并不完全是苹果对苹果,我认为平均值显示了它自己

【讨论】:

  • 谢谢它有效。早些时候他们不允许我导入 numpy,所以我认为不允许导入额外的 LIB。无论如何,这行得通。非常感谢:)
  • @AkhilT Counter 只是一个专门用于计数的字典。您可以使用普通字典实现相同的算法
  • @juanpa.arrivillaga 感谢您提供的信息。这很有帮助。
  • 次要优化:通过将[k for k in c if c[k]==1] 更改为[k for k, v in c.items() if v==1],避免嵌套查找和dict 查找开销。对于小输入可能会稍微慢一些,但对于较大的输入应该可以更好地扩展。另外,除非练习需要,否则不要将导入放在函数本身中(至少在 CPython 上,重新import 模块非常昂贵,即使模块已经加载和缓存,这要归功于令人难以置信的复杂的模块查找机制)。
  • @ShadowRanger 是的,我把它放在一起比较快,我会添加它作为编辑
【解决方案2】:

itertools.groupby() 版本的性能大约是collections.Counter 版本的 3 倍:

import collections
from itertools import groupby
import timeit

l = [9, 3, 9, 3, 9, 7, 9]

def fn1(lst):
    return [v for v, g in groupby(sorted(lst)) if len([*g]) == 1]

def fn2(lst):
    k = collections.Counter(lst)
    return [i for i in k if k[i] == 1]

print(timeit.timeit(lambda: fn1(l), number=100_000, globals=globals()) )
print(timeit.timeit(lambda: fn2(l), number=100_000, globals=globals()) )

打印:

0.11646193999331445
0.33489679799822625

【讨论】:

  • 有点慢,但sum(1 for _ in g) 不会将整个列表读入内存,而不是len([*g])。
  • 我怀疑这里的性能提升是小输入的产物;对于三个独特的项目,sorted 的 O(n log n) 复杂性几乎不重要,groupby 的实现效率低下并不重要,而 Counter 的大致 O(n) 计数复杂性没有机会发光.根据我的经验,groupby 比人们想象的要慢很多(有时即使您的输入已经排序,也会输给Counter,因此不需要调用sorted)。
  • @ShadowRanger Let'er rip... 虽然该解决方案似乎是最慢的(至少从 repl.it 上的测试来看)
  • @ShadowRanger 在我的机器(AMD 2400G)上,groupby() 的版本更快,直到输入数组的长度达到 85-90。
【解决方案3】:

尝试以下方法:

import collections 

k = collections.Counter(A)
return [ i for i in k if k[i] == 1]

【讨论】:

    猜你喜欢
    • 2014-07-20
    • 2012-02-25
    • 2011-02-08
    • 2012-03-22
    • 2021-04-03
    • 2017-07-14
    • 1970-01-01
    • 2016-09-25
    • 2014-01-05
    相关资源
    最近更新 更多