从列表列表中删除重复项

我有一个 Python 中的列表列表:

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

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

怎样才能以最有效的方式做到这一点?

上述清单的结果应该是:

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

我不在乎维护秩序。

注意: 这个问题是相似的,但不完全是我需要的。搜索所以,但没有找到确切的重复。


基准:

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次迭代:

[set] Elapsed: 1.3900001049
[sort] Elapsed: 0.891000032425
[groupby] Elapsed: 0.780999898911
[loop in] Elapsed: 0.578000068665

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

[set] Elapsed: 3.68700003624
[sort] Elapsed: 3.43799996376
[groupby] Elapsed: 1.03099989891
[loop in] Elapsed: 1.85900020599
161502 次浏览
>>> 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]]

我不知道它是否一定要更快,但是您不必使用元组和集。

手动执行,创建一个新的 k列表并添加到目前为止还没有找到的条目:

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]]

理解起来很简单,并且保持每个元素第一次出现的顺序应该是有用的,但是我猜它在复杂性上是二次的,因为您正在为每个元素搜索整个 new_k

>>> 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 经常提供最快和最强大的解决这类问题的办法,是 好吧值得亲密熟悉!-)

编辑 : 正如我在评论中提到的,正常的优化工作集中在大输入(big-O 方法)上,因为它非常容易,可以提供良好的回报。但是有时候(基本上是因为在代码的深层内部循环中存在“可悲的关键瓶颈”,这些瓶颈正在推动性能极限的边界) ,人们可能需要进行更多的细节,提供概率分布,决定优化哪些性能指标(可能上限或第90百分位数比平均值或中位数更重要,这取决于应用程序) ,在开始时执行可能的启发式检查,根据输入数据特征选择不同的算法,等等。

仔细测量“点”性能(代码 A 对特定输入的代码 B)是这个极其昂贵的过程的一部分,标准库模块 timeit在这里有所帮助。但是,在 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

二次方法不错,但是排序和分组方法更好。等等。

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

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

甚至你的“长”名单也很短。还有,你选择它们来匹配实际数据了吗?性能将随着这些数据的实际外观而变化。例如,您有一个重复一遍又一遍的短列表,以便创建一个更长的列表。这意味着二次解在基准测试中是线性的,但在现实中不是。

对于实际上很大的列表,集合代码是最好的选择ーー它是线性的(尽管占用空间很大)。Sort 和 groupby 方法是 O (n logn)方法中的循环明显是二次的所以你知道当 n 变得非常大时这些方法是如何扩展的。如果这是您正在分析的数据的真实大小,那么谁在乎呢?很小。

顺便说一句,如果我没有形成一个中间列表来进行设置,也就是说,如果我替换

kt = [tuple(i) for i in k]
skt = set(kt)

skt = set(tuple(i) for i in k)

真正的解决方案可能取决于更多的信息: 您确定列表列表真的是您所需要的表示形式吗?

另一个可能更通用、更简单的解决方案是创建一个由对象的字符串版本键控的 dictionary,并在末尾获取值() :

>>> dict([(unicode(a),a) for a in [["A", "A"], ["A", "A"], ["A", "B"]]]).values()
[['A', 'B'], ['A', 'A']]

问题是,这只适用于字符串表示为足够好的唯一键的对象(对于大多数本机对象来说都是这样)。

元组和{}的列表可用于删除重复项

>>> [list(tupl) for tupl in {tuple(item) for item in k }]
[[1, 2], [5, 6, 2], [3], [4]]
>>>

创建一个以 tuple 为键的字典,并打印键。

  • 创建以元组作为键和索引作为值的字典
  • 字典键列表

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]]

到目前为止,所有与 set相关的解决方案都需要在迭代之前创建一个完整的 set

通过迭代列表列表并添加到“看到的”set,可以使这种方法变得懒惰,同时保持顺序。然后只产生一个列表,如果它不是在这个跟踪器 set中找到。

这个 unique_everseen配方可以在 itertools 医生中找到,也可以在第三方 toolz库中找到:

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转换是必要的,因为列表不是散列的。

这个应该可以。

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]]

奇怪的是,上面的答案删除了“重复”,但是如果我想删除重复的值呢? 以下内容应该是有用的,并且不会在内存中创建新对象!

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, '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']]
k=[[1, 2], [4], [5, 6, 2], [1, 2], [3], [5, 2], [3], [8], [9]]
kl=[]
kl.extend(x for x in k if x not in kl)
k=list(kl)
print(k)

指纹,

[[1, 2], [4], [5, 6, 2], [3], [5, 2], [8], [9]]

一点点的背景知识,我刚开始学习蟒蛇和理解力。

k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]]
dedup = [elem.split('.') for elem in set(['.'.join(str(int_elem) for int_elem in _list) for _list in k])]
a_list = [
[1,2],
[1,2],
[2,3],
[3,4]
]


print (list(map(list,set(map(tuple,a_list)))))

产出: [[1, 2], [3, 4], [2, 3]]

最简单的解决方案是将一个列表转换为一个元组列表,然后应用 dict.fromkeys()方法,然后将其转换回该列表。

例如:

你有 k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]]

转换为元组列表 k = list(map(tuple, k))

这会给你 [(1, 2), (4,), (5, 6, 2), (1, 2), (3,), (4,)]

然后执行以下操作: unique = list(dict.fromkeys(k))

你会得到 [(1, 2), (4,), (5, 6, 2), (3,)]

仅此而已。

如果抱怨的不是“不快”本身,而是你提出的解决方案中“不够简洁”的部分,那么在 Python 3.5 + 中,借助于 拆箱操作员拆箱操作员和简洁的元组表示法,你可以使链式数据结构转换非常简短(当然,这仍然是 O (n ^ 2) ,但是解包还是比直接转换快一些) :

输入:

k = [[1, 2], [4], [5, 6, 2], [1, 2], [3], [4]]
k = [*map(list, {*map(tuple, k)})]


# If you prefer comprehensions to map()
# k = [[*t] for t in {(*l,) for l in k}]


# Order-preserving alternative:
# k = [*map(list, dict.fromkeys(map(tuple, k)))]


print(k)

产出:

[[1, 2], [4], [5, 6, 2], [3]]