鉴于列表a = [1, 2, 2, 3]
及其子列表b = [1, 2]
,找到一个以sorted(a) == sorted(b + complement)
的方式补充b的列表。在上面的示例中,complement
将是[2, 3]
的列表。
使用列表理解是很诱人的:
complement = [x for x in a if x not in b]
或设置:
complement = list(set(a) - set(b))
但是,这两种方式都会返回complement = [3]
。
明显的做法是:
complement = a[:]
for element in b:
complement.remove(element)
但这感觉非常不满意而且不是非常 Pythonic 。我错过了一个明显的习语还是这样?
正如下面指出的那样,性能是O(n^2)
还有更有效的方法吗?
答案 0 :(得分:16)
唯一更多的声明性以及 Pythonic 方式突然出现在我的脑海中,而提高了大型b
的性能(和a
)是使用某种减量计数器:
from collections import Counter
class DecrementCounter(Counter):
def decrement(self,x):
if self[x]:
self[x] -= 1
return True
return False
现在我们可以使用列表理解:
b_count = DecrementCounter(b)
complement = [x for x in a if not b_count.decrement(x)]
这里我们跟踪b
中的计数,a
中的每个元素,我们看它是否属于b_count
。如果确实如此,我们减少计数器并忽略该元素。否则我们将其添加到complement
。请注意,只有当我们确定存在<{1}} 时,这才有效。
后构建了complement
,您可以检查补码是否存在:
complement
如果这是not bool(+b_count)
,那么无法构建此补码(例如False
和a=[1]
)。因此,完整的实施可能是:
b=[1,3]
如果字典查找在 O(1)(它通常只在极少数情况下是 O(n))中运行,则此算法在< em> O(| a | + | b |)(所以列表大小的总和)。而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')
方法通常在 O(| a |×| b |)中运行。
答案 1 :(得分:5)
为了降低已经有效的方法的复杂性,您可以使用collections.Counter
(这是一个快速查找的专用字典)来计算两个列表中的项目。
然后通过减去值来更新计数,最后通过仅保留计数为&gt;的项来过滤列表。 0并使用itertools.chain
from collections import Counter
import itertools
a = [1, 2, 2, 2, 3]
b = [1, 2]
print(list(itertools.chain.from_iterable(x*[k] for k,x in (Counter(a)-Counter(b)).items() if x > 0)))
结果:
[2, 2, 3]
答案 2 :(得分:2)
O(n log n)
a = [1, 2, 2, 3]
b = [1, 2]
a.sort()
b.sort()
L = []
i = j = 0
while i < len(a) and j < len(b):
if a[i] < b[j]:
L.append(a[i])
i += 1
elif a[i] > b[j]:
L.append(b[j])
j += 1
else:
i += 1
j += 1
while i < len(a):
L.append(a[i])
i += 1
while j < len(b):
L.append(b[j])
j += 1
print(L)
答案 3 :(得分:2)
如果补语中元素的顺序无关紧要,那么只需要collections.Counter:
from collections import Counter
a = [1, 2, 3, 2]
b = [1, 2]
complement = list((Counter(a) - Counter(b)).elements()) # complement = [2, 3]
如果补码中的项目顺序应与原始列表中的顺序相同,则使用以下内容:
from collections import Counter, defaultdict
from itertools import count
a = [1,2,3,2]
b = [2,1]
c = Counter(b)
d = defaultdict(count)
complement = [x for x in a if next(d[x]) >= c[x]] # complement = [3, 2]
答案 4 :(得分:1)
def add_duplicate_position(items):
element_counter = {}
for item in items:
element_counter[item] = element_counter.setdefault(item,-1) + 1
yield element_counter[item], item
assert list(add_duplicate_position([1, 2, 2, 3])) == [(0, 1), (0, 2), (1, 2), (0, 3)]
def create_complementary_list_with_duplicates(a,b):
a = list(add_duplicate_position(a))
b = set(add_duplicate_position(b))
return [item for _,item in [x for x in a if x not in b]]
a = [1, 2, 2, 3]
b = [1, 2]
assert create_complementary_list_with_duplicates(a,b) == [2, 3]