Fastest way to check if duplicates exist in a python list / numpy ndarray
我想确定我的列表(实际上是一个
注意:如果这不是一个副本,我会非常惊讶,但我已经尽力了,找不到了。最近的是这个问题和这个问题,这两个问题都要求返回唯一列表。
这是我想用的四种方法。
tl;dr:如果您期望很少(少于1/1000)个副本:
1 2 | def contains_duplicates(X): return len(np.unique(X)) != len(X) |
如果您希望经常(超过1/1000)重复:
1 2 3 4 5 6 7 | def contains_duplicates(X): seen = set() seen_add = seen.add for x in X: if (x in seen or seen_add(x)): return True return False |
第一种方法是提前退出这个答案,它希望返回唯一的值,第二种方法是应用于这个答案的相同思想。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | >>> import numpy as np >>> X = np.random.normal(0,1,[10000]) >>> def terhorst_early_exit(X): ...: elems = set() ...: for i in X: ...: if i in elems: ...: return True ...: elems.add(i) ...: return False >>> %timeit terhorst_early_exit(X) 100 loops, best of 3: 10.6 ms per loop >>> def peterbe_early_exit(X): ...: seen = set() ...: seen_add = seen.add ...: for x in X: ...: if (x in seen or seen_add(x)): ...: return True ...: return False >>> %timeit peterbe_early_exit(X) 100 loops, best of 3: 9.35 ms per loop >>> %timeit len(set(X)) != len(X) 100 loops, best of 3: 4.54 ms per loop >>> %timeit len(np.unique(X)) != len(X) 1000 loops, best of 3: 967 μs per loop |
如果您从普通的python列表开始,而不是从
1 2 3 4 5 6 7 8 9 | >>> X = X.tolist() >>> %timeit terhorst_early_exit(X) 100 loops, best of 3: 9.34 ms per loop >>> %timeit peterbe_early_exit(X) 100 loops, best of 3: 8.07 ms per loop >>> %timeit len(set(X)) != len(X) 100 loops, best of 3: 3.09 ms per loop >>> %timeit len(np.unique(X)) != len(X) 1000 loops, best of 3: 1.83 ms per loop |
编辑:如果我们对重复的数量有一个预先的期望呢?
上述比较是在假设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 30 31 32 33 34 35 36 37 38 39 40 | >>> X = np.random.normal(0, 1, [10000]) >>> for n_duplicates in [1, 10, 100]: >>> print("{} duplicates".format(n_duplicates)) >>> duplicate_idx = np.random.choice(len(X), n_duplicates, replace=False) >>> X[duplicate_idx] = 0 >>> print("terhost_early_exit") >>> %timeit terhorst_early_exit(X) >>> print("peterbe_early_exit") >>> %timeit peterbe_early_exit(X) >>> print("set length") >>> %timeit len(set(X)) != len(X) >>> print("numpy unique length") >>> %timeit len(np.unique(X)) != len(X) 1 duplicates terhost_early_exit 100 loops, best of 3: 12.3 ms per loop peterbe_early_exit 100 loops, best of 3: 9.55 ms per loop set length 100 loops, best of 3: 4.71 ms per loop numpy unique length 1000 loops, best of 3: 1.31 ms per loop 10 duplicates terhost_early_exit 1000 loops, best of 3: 1.81 ms per loop peterbe_early_exit 1000 loops, best of 3: 1.47 ms per loop set length 100 loops, best of 3: 5.44 ms per loop numpy unique length 1000 loops, best of 3: 1.37 ms per loop 100 duplicates terhost_early_exit 10000 loops, best of 3: 111 μs per loop peterbe_early_exit 10000 loops, best of 3: 99 μs per loop set length 100 loops, best of 3: 5.16 ms per loop numpy unique length 1000 loops, best of 3: 1.19 ms per loop |
因此,如果您期望很少的重复,那么
根据数组的大小和重复的可能性,答案会有所不同。
例如,如果您希望平均数组有大约3个重复,那么提前退出将使您的平均case时间(和空间)减少2/3rds;如果您希望1000个数组中只有1个有任何重复,那么它只会增加一点复杂性,而不会有任何改进。
同时,如果数组足够大,构建一个临时的数组大小的集合可能会很昂贵,那么在它前面放置一个概率测试,就像一个布卢姆滤波器一样,可能会大大加快速度,但如果不是这样,那又是浪费了精力。
最后,如果可能的话,你想呆在麻木的地方。循环遍历一组浮点数(或其他浮点数)并将每个浮点数装箱到一个python对象中所需的时间几乎与散列和检查值所需的时间相同,当然,将数据存储在python
所以,对于所有可能的场景,没有一个最佳解决方案。
为了让你知道写一个bloom过滤器有多简单,我在几分钟内就把它拼凑到了一起:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | from bitarray import bitarray # pip3 install bitarray def dupcheck(X): # Hardcoded values to give about 5% false positives for 10000 elements size = 62352 hashcount = 4 bits = bitarray(size) bits.setall(0) def check(x, hash=hash): # TODO: default-value bits, hashcount, size? for i in range(hashcount): if not bits[hash((x, i)) % size]: return False return True def add(x): for i in range(hashcount): bits[hash((x, i)) % size] = True seen = set() seen_add = seen.add for x in X: if check(x) or add(x): if x in seen or seen_add(x): return True return False |
它只使用12kb(62352位
当然,它几乎肯定会比使用