从列表列表中删除重复项

我有一个Python列表:

k=[[1,2]、[4]、[5,6,2]、[1,2]、[3]、[4]]

我想从中删除重复的元素。如果它是一个正常的列表,而不是我可以使用的列表set。但不幸的是,这个列表是不可散列的,不能创建一组列表。只有一个元组。所以我可以将所有列表转换为元组,然后使用set并返回列表。但这并不快

如何以最有效的方式做到这一点

上述列表的结果应为:

k=[[5,6,2]、[1,2]、[3]、[4]]

我不在乎维护秩序

注意:这个问题类似,但不是我所需要的。搜索了这么多,但没有找到确切的副本


基准:

导入itertools,时间
类计时器(对象):
def uuu init uuuu(self,name=None):
self.name=名称
定义输入(自我):
self.tstart=time.time()
定义退出(自身、类型、值、回溯):
如果self.name:
打印'[%s]’%self.name,
打印“已用时间:%s%”(time.time()-self.tstart)
k=[[1,2]、[4]、[5,6,2]、[1,2]、[3]、[5,2]、[6]、[8]、[9]*5
N=100000
印刷透镜(k)
带计时器(’set’):
对于x范围内的i(N):
kt=[k中i的元组(i)]
skt=套(kt)
kk=[在skt中列出(i)代表i]
使用计时器(’sort’):
对于x范围内的i(N):
ks=已排序(k)
如果i==0或ks[i]!=ks[i-1],则xrange(len(ks))中i的重复数据消除=[ks[i]
使用计时器(’groupby’):
对于x范围内的i(N):
k=已排序(k)
重复数据消除=列表(k表示k,在itertools.groupby(k)中)
使用计时器(‘循环输入’):
对于x范围内的i(N):
new_k=[]
对于k中的元素:
如果elem不在纽约:
新附加(元素)

“循环”(二次法)是短列表中最快的方法。对于长列表,它比除groupby方法之外的所有人都快。这有意义吗

对于短列表(代码中的一个),100000次迭代:

[set]经过时间:1.3900001049
[排序]经过时间:0.891000032425
[groupby]运行时间:0.78099989911
[循环输入]经过时间:0.57800068665

对于较长的列表(代码中的列表重复了5次):

[set]经过时间:3.6870003624
[排序]经过时间:3.4399996376
[groupby]经过时间:1.0309998991
[循环输入]经过时间:1.85900020599
&gt&燃气轮机&燃气轮机;k=[[1,2],[4],[5,6,2],[1,2],[3],[4]]
&燃气轮机&燃气轮机&燃气轮机;进口itertools
&燃气轮机&燃气轮机&燃气轮机;k、 排序()
&燃气轮机&燃气轮机&燃气轮机;列表(k代表k,在itertools.groupby(k)中)
[[1, 2], [3], [4], [5, 6, 2]]

itertools通常为这类问题提供最快、最强大的解决方案,并且非常值得深入了解!)

编辑:正如我在评论中提到的,正常的优化工作主要集中在大的输入上(大O方法),因为它非常简单,可以提供良好的工作回报。但有时(本质上是针对深入的代码内部循环中的“悲惨的关键瓶颈”,它正在突破性能限制的边界),我们可能需要更详细地讨论,提供概率分布,决定要优化哪些性能度量(可能上界或第90个百分位数比平均值或中位数更重要,这取决于一个人的应用程序),在开始时执行可能的启发式检查,根据输入数据特征选择不同的算法,等等

仔细测量“点”性能(特定输入的代码A与代码B)是这个极其昂贵的过程的一部分,标准库模块timeit在这里有帮助。但是,在shell提示符下使用它更容易。例如,这里有一个简短的模块来展示解决此问题的一般方法,另存为nodup.py

导入itertools
k=[[1,2],[4],[5,6,2],[1,2],[3],[4]]
def doset(k,map=map,list=list,set=set,tuple=tuple):
返回映射(列表,集合(映射(元组,k)))
def dosort(k,排序=排序,xrange=xrange,len=len):
ks=已排序(k)
如果i==0或ks[i]!=ks[i-1],则为xrange(len(ks))中的i返回[ks[i]
def dogroupby(k,sorted=sorted,groupby=itertools.groupby,list=list):
ks=已排序(k)
返回[i代表i,在itertools.groupby(ks)中]
def donewk(k):
newk=[]
对于k中的i:
如果我不在纽克:
newk.append(一)
返回纽克
#检查所有函数是否计算相同的结果,并且不改变k
如果uuuu name uuuuuu=’\uuuuuuu main\uuuuuuu’:
savek=列表(k)
对于doset、dosort、dogroupby、donewk中的f:
resk=f(k)
断言k==savek
打印“%10s%s%”(f.\u\u名称\u,已排序(resk))

请注意健全性检查(在您只执行python nodup.py时执行)和基本提升技术(使每个函数都具有恒定的全局名称以实现速度),从而使事情处于平等的基础上

现在,我们可以对微小的示例列表进行检查:

$python-mtimeit-s'import nodup''nodup.doset(nodup.k)'
100000个循环,最好3个:每个循环11.7 usec
$python-mtimeit-s'import nodup''nodup.dosort(nodup.k)'
100000个循环,最好3个:每个循环9.68 usec
$python-mtimeit-s'import nodup''nodup.dogroupby(nodup.k)'
100000个循环,最好3个:每个循环8.74 usec
$python-mtimeit-s'import nodup''nodup.donewk(nodup.k)'
100000个循环,最好3个:每个循环4.44 usec

确认二次方法具有足够小的常数,使其对具有少量重复值的小列表具有吸引力。对于没有重复值的短列表:

$python-mtimeit-s'import nodup''nodup.donewk([[i]代表范围(12)])
10000个循环,最好3个:每个循环25.4 usec
$python-mtimeit-s'import nodup''nodup.dogroupby([[i]表示范围内的i(12)])
10000个循环,最好3个:每个循环23.7 usec
$python-mtimeit-s'import nodup''nodup.doset([[i]表示范围(12)]内的i)
10000个循环,最好为3个:每个循环31.3 usec
$python-mtimeit-s'import nodup''nodup.dosort([[i]表示范围(12)]内的i)
10000个循环,最好为3:25 usec/循环

二次法不错,但排序法和分组法更好

如果(正如对性能的痴迷所表明的那样)此操作是推动边界应用程序的核心内部循环,那么在其他具有代表性的输入样本上尝试相同的测试集是值得的,可能会检测到一些简单的度量,可以试探性地让您选择一种或另一种方法(当然,这项措施必须是快速的)

同样值得考虑的是,为k保留一个不同的表示形式——为什么它首先必须是一个列表列表而不是一组元组?如果重复删除任务很频繁,并且分析表明这是程序的性能瓶颈,那么始终保留一组元组并获得一个元组列表例如,仅在需要时和需要时才从中列出,总体上可能更快

发表评论