python creates a complement to a list that holds duplicate values
- 2020-12-19 21:06:47
- OfStack
Given list a = [1,2,2,3], the sublist b = [1,2] finds a list that completes b by sorting (a)== sorting (b complement). In the above example, the complement would be a list of [2,3].
It's tempting to use list parsing:
complement = [x for x in a if x not in b]
Or Settings:
complement = list(set(a) - set(b))
However, both approaches will return
complement = [3].
One obvious approach is:
complement = a[:]
for element in b:
complement.remove(element)
However, the feeling was very unsatisfactory, and not very good. Did I miss a wise idiom?
As noted below, is there a more efficient way to have performance is O(n ^ 2)?
Only more declarative and therefore Pythonic approach would come to mind, and to mention that the performance of b(and a) is to use some sort of subtraction counter:
from collections import Counter
class DecrementCounter(Counter):
def decrement(self,x):
if self[x]:
self[x] -= 1
return True
return False
Now we can use list parsing:
b_count = DecrementCounter(b)
complement = [x for x in a if not b_count.decrement(x)]
Here we track the counts in b, for each element we look at whether it is part 1 of b_count. If this is the case, we reduce the counter and ignore the element. Otherwise we add it to the completion. Note that this only works if we are confident that such a supplement exists.
After the complement is constructed, it can be checked whether the complement exists:
not bool(+b_count)
If this is False, then such a complement cannot be constructed (for example, a = [1] and b = [1,3]). So full implementation might be:
b_count = DecrementCounter(b)
complement = [x for x in a if not b_count.decrement(x)]
if +b_count:
raise ValueError('complement cannot be constructed')
If the dictionary lookup runs in O(1) (usually, but only in rare cases, O(n)), then the algorithm runs in O(| a | | b |) (therefore, the list). The deletion method is usually run in O(| a |×| b |).
conclusion