>>> k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]]
>>> import itertools
>>> k.sort()
>>> list(k for k,_ in itertools.groupby(k))
[[1, 2], [3], [4], [5, 6, 2]]
itertools经常为这类问题提供最快,最强大的解决方案,非常值得熟悉! – )
编辑:正如我在评论中提到的,正常的优化工作主要集中在大型投入(大O方法)上,因为它更容易提供良好的工作回报.但有时候(主要是针对深层内部代码循环中的“悲剧性关键瓶颈”,正在推动性能限制的界限),可能需要更详细地介绍概率分布,决定优化哪些性能指标(可能是上限或第90个百分位比平均值或中位数更重要,具体取决于一个人的应用程序),在开始时执行可能的启发式检查,根据输入数据特征选择不同的算法,等等.
仔细测量“点”性能(代码A与特定输入的代码B)是这个极其昂贵的过程的一部分,标准库模块时间在这里有所帮助.但是,在shell提示符下使用它会更容易.例如,这是一个简短的模块,用于展示此问题的一般方法,将其另存为nodup.py:
import itertools
k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]]
def doset(k, map=map, list=list, set=set, tuple=tuple):
return map(list, set(map(tuple, k)))
def dosort(k, sorted=sorted, xrange=xrange, len=len):
ks = sorted(k)
return [ks[i] for i in xrange(len(ks)) if i == 0 or ks[i] != ks[i-1]]
def dogroupby(k, sorted=sorted, groupby=itertools.groupby, list=list):
ks = sorted(k)
return [i for i, _ in itertools.groupby(ks)]
def donewk(k):
newk = []
for i in k:
if i not in newk:
newk.append(i)
return newk
# sanity check that all functions compute the same result and don't alter k
if __name__ == '__main__':
savek = list(k)
for f in doset, dosort, dogroupby, donewk:
resk = f(k)
assert k == savek
print '%10s %s' % (f.__name__, sorted(resk))
注意完整性检查(当你执行python nodup.py时执行)和基本的提升技术(为每个函数提供局部的常量全局名称以提高速度)以使事情平等.
现在我们可以在微小的示例列表上运行检查:
$python -mtimeit -s'import nodup' 'nodup.doset(nodup.k)'
100000 loops, best of 3: 11.7 usec per loop
$python -mtimeit -s'import nodup' 'nodup.dosort(nodup.k)'
100000 loops, best of 3: 9.68 usec per loop
$python -mtimeit -s'import nodup' 'nodup.dogroupby(nodup.k)'
100000 loops, best of 3: 8.74 usec per loop
$python -mtimeit -s'import nodup' 'nodup.donewk(nodup.k)'
100000 loops, best of 3: 4.44 usec per loop
确认二次方法具有足够小的常数,使其对具有较少重复值的小列表具有吸引力.使用没有重复的短列表:
$python -mtimeit -s'import nodup' 'nodup.donewk([[i] for i in range(12)])'
10000 loops, best of 3: 25.4 usec per loop
$python -mtimeit -s'import nodup' 'nodup.dogroupby([[i] for i in range(12)])'
10000 loops, best of 3: 23.7 usec per loop
$python -mtimeit -s'import nodup' 'nodup.doset([[i] for i in range(12)])'
10000 loops, best of 3: 31.3 usec per loop
$python -mtimeit -s'import nodup' 'nodup.dosort([[i] for i in range(12)])'
10000 loops, best of 3: 25 usec per loop
二次方法也不错,但sort和groupby方法更好.等等
如果(正如对性能的痴迷所表明的那样)这个操作位于推动边界应用程序的核心内部循环中,那么值得尝试对其他代表性输入样本进行相同的测试,可能会检测到一些可能启发式地让你发现的简单测量选择一种或另一种方法(当然,措施必须快速).
考虑保持k的不同表示也是值得的 – 为什么它必须是列表而不是一组元组?如果重复删除任务是频繁的,并且分析显示它是程序的性能瓶颈,那么始终保留一组元组并仅在需要时从中获取列表列表,例如,总体上可能更快.