Removing duplicates from a list of lists
我在python中有一个列表:
1 | k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]] |
我想从中删除重复的元素。如果它不是一个普通的列表,我可以使用
如何以最有效的方式做到这一点?
以上列表的结果应为:
1 | k = [[5, 6, 2], [1, 2], [3], [4]] |
我不在乎维持秩序。
注意:这个问题很相似,但不完全符合我的需要。搜索过,但没有找到确切的副本。
标杆管理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | import itertools, time class Timer(object): def __init__(self, name=None): self.name = name def __enter__(self): self.tstart = time.time() def __exit__(self, type, value, traceback): if self.name: print '[%s]' % self.name, print 'Elapsed: %s' % (time.time() - self.tstart) k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [5, 2], [6], [8], [9]] * 5 N = 100000 print len(k) with Timer('set'): for i in xrange(N): kt = [tuple(i) for i in k] skt = set(kt) kk = [list(i) for i in skt] with Timer('sort'): for i in xrange(N): ks = sorted(k) dedup = [ks[i] for i in xrange(len(ks)) if i == 0 or ks[i] != ks[i-1]] with Timer('groupby'): for i in xrange(N): k = sorted(k) dedup = list(k for k, _ in itertools.groupby(k)) with Timer('loop in'): for i in xrange(N): new_k = [] for elem in k: if elem not in new_k: new_k.append(elem) |
"循环"(二次法)最快的所有短名单。对于长列表,它比GroupBy方法以外的所有人都快。这有道理吗?
对于短列表(代码中的列表),100000次迭代:
1 2 3 4 | [set] Elapsed: 1.3900001049 [sort] Elapsed: 0.891000032425 [groupby] Elapsed: 0.780999898911 [loop in] Elapsed: 0.578000068665 |
对于较长的列表(代码中的列表重复5次):
1 2 3 4 | [set] Elapsed: 3.68700003624 [sort] Elapsed: 3.43799996376 [groupby] Elapsed: 1.03099989891 [loop in] Elapsed: 1.85900020599 |
1 2 3 4 5 | >>> 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]] |
编辑:正如我在一篇评论中提到的,正常的优化工作集中在大投入(大O方法)上,因为它非常简单,可以提供很好的工作回报。但有时(本质上是因为在代码的深层内部循环中存在"可悲的关键瓶颈",这会推高性能极限),人们可能需要更详细地说明,提供概率分布,决定要优化的性能度量(可能是上限或90百分位比平均值更重要)或者中位数,取决于一个人的应用程序),在开始时执行可能的启发式检查,根据输入数据特征选择不同的算法,等等。
仔细测量"点"性能(特定输入的代码A与代码B)是这个极其昂贵的过程的一部分,标准库模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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)) |
请注意健全性检查(仅在执行
现在,我们可以在小示例列表上运行检查:
1 2 3 4 5 6 7 8 | $ 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 |
确认二次方方法具有足够小的常量,使其对具有很少重复值的小列表具有吸引力。没有重复的短列表:
1 2 3 4 5 6 7 8 | $ 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 |
二次型方法不错,但是排序和分组方法更好。等
如果(正如对性能的痴迷所暗示的那样)此操作处于推动边界应用程序的核心内部循环中,那么值得在其他具有代表性的输入样本上尝试相同的一组测试,可能会检测到一些简单的度量,这些度量可以启发性地让您选择一种或另一种方法(但度量必须快速,以确保URSE)。
同样值得考虑为
1 2 3 4 5 6 7 | >>> k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]] >>> k = sorted(k) >>> k [[1, 2], [1, 2], [3], [4], [4], [5, 6, 2]] >>> dedup = [k[i] for i in range(len(k)) if i == 0 or k[i] != k[i-1]] >>> dedup [[1, 2], [3], [4], [5, 6, 2]] |
我不知道它是否需要更快,但你不必使用元组和集合。
手动操作,创建一个新的
1 2 3 4 5 6 7 8 | k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]] new_k = [] for elem in k: if elem not in new_k: new_k.append(elem) k = new_k print k # prints [[1, 2], [4], [5, 6, 2], [3]] |
简单易懂,如果每个元素都有用的话,您可以保留第一次出现的顺序,但我猜这是二次复杂性,因为您要搜索每个元素的整个
即使你的"长"名单也很短。另外,您是否选择了与实际数据相匹配的数据?性能将随这些数据的实际外观而变化。例如,您有一个简短的列表反复重复,以形成一个较长的列表。这意味着二次解在基准中是线性的,但在现实中不是。
对于实际的大列表,设置代码是您最好的选择,它是线性的(尽管需要空间)。sort和groupby方法是O(n logn),loop-in方法显然是二次的,所以你知道当n变大时,这些方法是如何伸缩的。如果这是您正在分析的数据的实际大小,那么谁会在意呢?它很小。
顺便说一下,如果我没有形成一个中间的列表来制作这个集合,我会看到一个明显的加速,也就是说如果我替换
1 2 | kt = [tuple(i) for i in k] skt = set(kt) |
具有
1 | skt = set(tuple(i) for i in k) |
真正的解决方案可能取决于更多的信息:您确定列表确实是您需要的表示形式吗?
迄今为止,所有与
通过迭代列表并添加到一个"seen"
此
1 2 3 4 5 6 7 8 9 10 | from toolz import unique k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]] # lazy iterator res = map(list, unique(map(tuple, k))) print(list(res)) [[1, 2], [4], [5, 6, 2], [3]] |
注意,
tuple和的列表可用于删除重复项
1 2 3 | >>> [list(tupl) for tupl in {tuple(item) for item in k }] [[1, 2], [5, 6, 2], [3], [4]] >>> |
这应该有效。
1 2 3 4 5 6 7 8 9 | k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]] k_cleaned = [] for ele in k: if set(ele) not in [set(x) for x in k_cleaned]: k_cleaned.append(ele) print(k_cleaned) # output: [[1, 2], [4], [5, 6, 2], [3]] |
奇怪的是,上面的答案会删除"重复的",但是如果我也要删除重复的值呢??以下内容应该有用,并且不会在内存中创建新对象!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | def dictRemoveDuplicates(self): a=[[1,'somevalue1'],[1,'somevalue2'],[2,'somevalue1'],[3,'somevalue4'],[5,'somevalue5'],[5,'somevalue1'],[5,'somevalue1'],[5,'somevalue8'],[6,'somevalue9'],[6,'somevalue0'],[6,'somevalue1'],[7,'somevalue7']] print(a) temp = 0 position = -1 for pageNo, item in a: position+=1 if pageNo != temp: temp = pageNo continue else: a[position] = 0 a[position - 1] = 0 a = [x for x in a if x != 0] print(a) |
O/P是:
1 2 | [[1, 'somevalue1'], [1, 'somevalue2'], [2, 'somevalue1'], [3, 'somevalue4'], [5, 'somevalue5'], [5, 'somevalue1'], [5, 'somevalue1'], [5, 'somevalue8'], [6, 'somevalue9'], [6, 'somevalue0'], [6, 'somevalue1'], [7, 'somevalue7']] [[2, 'somevalue1'], [3, 'somevalue4'], [7, 'somevalue7']] |
创建一个以元组为键的字典,并打印键。
- 创建以元组为键、索引为值的字典
- 打印字典键列表
1 2 3 4 5 6 7 | k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]] dict_tuple = {tuple(item): index for index, item in enumerate(k)} print [list(itm) for itm in dict_tuple.keys()] # prints [[1, 2], [5, 6, 2], [3], [4]] |
另一个可能更通用、更简单的解决方案是创建一个由对象的字符串版本键控的字典,并在末尾获取values():
1 2 | >>> dict([(unicode(a),a) for a in [["A","A"], ["A","A"], ["A","B"]]]).values() [['A', 'B'], ['A', 'A']] |
关键是,这只适用于字符串表示形式足够好的唯一键(对于大多数本机对象都是这样)的对象。