Your method:
def dedup_reference(a, b):
for el in b:
idx = (el == a).argmax()
if a[idx] == el:
a = np.delete(a, idx)
return a
Scan method with sorting of inputs required:
def dedup_scan(arr, sel):
arr.sort()
sel.sort()
mask = np.ones_like(arr, dtype=np.bool)
sel_idx = 0
for i, x in enumerate(arr):
if sel_idx == sel.size:
break
if x == sel[sel_idx]:
mask[i] = False
sel_idx += 1
return arr[mask]
np.unique counting method:
def dedup_unique(arr, sel):
d_arr = dict(zip(*np.unique(arr, return_counts=True)))
d_sel = dict(zip(*np.unique(sel, return_counts=True)))
d = {k: v - d_sel.get(k, 0) for k, v in d_arr.items()}
res = np.empty(sum(d.values()), dtype=arr.dtype)
idx = 0
for k, count in d.items():
res[idx:idx+count] = k
idx += count
return res
You could perhaps accomplish the same as above through some clever use of the numpy set functions (e.g. np.in1d), but I don't think it's any faster than just using dictionaries.
Here is one lazy attempt at benchmarking (updated to include @Divakar's diff_v2 and diff_v3 methods, too):
>>> def timeit_ab(f, n=10):
... cmd = f"{f}(a.copy(), b.copy())"
... t = timeit(cmd, globals=globals(), number=n) / n
... print("{:.4f} {}".format(t, f))
>>> array_copy = lambda x, y: None
>>> funcs = [
... 'array_copy',
... 'dedup_reference',
... 'dedup_scan',
... 'dedup_unique',
... 'diff_v2',
... 'diff_v3',
... ]
>>> def run_test(maxval, an, bn):
... global a, b
... a = np.random.randint(maxval, size=an)
... b = np.random.choice(a, size=bn, replace=False)
... for f in funcs:
... timeit_ab(f)
>>> run_test(10**1, 10000, 5000)
0.0000 array_copy
0.0617 dedup_reference
0.0035 dedup_scan
0.0004 dedup_unique (*)
0.0020 diff_v2
0.0009 diff_v3
>>> run_test(10**2, 10000, 5000)
0.0000 array_copy
0.0643 dedup_reference
0.0037 dedup_scan
0.0007 dedup_unique (*)
0.0023 diff_v2
0.0013 diff_v3
>>> run_test(10**3, 10000, 5000)
0.0000 array_copy
0.0641 dedup_reference
0.0041 dedup_scan
0.0022 dedup_unique
0.0027 diff_v2
0.0016 diff_v3 (*)
>>> run_test(10**4, 10000, 5000)
0.0000 array_copy
0.0635 dedup_reference
0.0041 dedup_scan
0.0082 dedup_unique
0.0029 diff_v2
0.0015 diff_v3 (*)
>>> run_test(10**5, 10000, 5000)
0.0000 array_copy
0.0635 dedup_reference
0.0041 dedup_scan
0.0118 dedup_unique
0.0031 diff_v2
0.0016 diff_v3 (*)
>>> run_test(10**6, 10000, 5000)
0.0000 array_copy
0.0627 dedup_reference
0.0043 dedup_scan
0.0126 dedup_unique
0.0032 diff_v2
0.0016 diff_v3 (*)
Takeaways:
dedup_reference slows down significantly as the number of duplicates increases.
dedup_unique is fastest if the range of values is small. diff_v3 is pretty fast and does not depend on the range of values.
- Array copy times are negligible.
- Dictionaries are pretty cool.
The performance characteristics strongly depend on both the amount of data (not tested), and also the statistical distributions of the data. I recommend testing the methods with your own data and picking the fastest. Note that the various solutions produce different outputs, and make different assumptions about the inputs.