Python列表减法操作*尊重重复*

时间:2015-05-24 04:53:39

标签: python python-2.x

我希望从另一个列表中减去列表但是要尊重重复:

>>> a = ['a', 'b', 'c','c', 'c', 'c', 'd', 'e', 'e']
>>> b = ['a', 'c', 'e', 'f','c']
>>> a - b
['b', 'c','c', 'd', 'e']

元素顺序无关紧要。

答案here有一个问题,但忽略了重复。解决方案会给出:

>>> a - b
['b', 'd']

一个解决方案考虑重复,但它会改变原始列表中的一个:

[i for i in a if not i in b or b.remove(i)]

我写了这个解决方案:

a_sub_b = list(a)
b_sub_a = list(b)
for e in a:
    if e in b_sub_a:
        a_sub_b.remove(e)
        b_sub_a.remove(e)

print a_sub_b # a - b 
print b_sub_a # b - a

这对我有用,但有更好的解决方案,更简单或更有效吗?

3 个答案:

答案 0 :(得分:4)

如果订单无关紧要,请使用collections.Counter

c = list((Counter(a) - Counter(b)).elements())

Counter(a) - Counter(b)构建一个计数器,其中元素x的计数等于xa显示的次数减去x的次数出现在b中。 elements()创建一个迭代器,使每个元素的次数等于其计数,list将其转换为列表。整件事需要O(len(a)+len(b))次。

请注意,根据您的工作情况,最好不要使用列表,只需将abc表示为计数器。< / p>

答案 1 :(得分:2)

这将为b的每个元素搜索a的每个元素。对于匹配的每个元素,它还会在每个列表上执行线性remove。因此,您的算法需要二次时间 - O(max(N, M)^2)其中Na的长度,Mb的长度。

如果您只是将b复制到set而不是list,则可以解决问题。现在,您只需对a中的每个元素执行常量时间集查找,并执行常量时间集删除而不是列表删除。但是,您仍然遇到线性时间和从a副本中删除错误的问题。而且您无法将a复制到set,因为这会丢失重复项。

最重要的是,a_sub_b.remove(e)删除e 匹配的元素。这与您刚刚查找的元素的元素不一定相同。它将成为一个相等的元素,如果身份根本不重要,那很好......但如果确实如此,那么remove可能做错了。

无论如何,性能已经足以成为不使用remove的理由了。一旦解决了上述问题,这就是使算法成为二次而非线性的唯一方法。

解决此问题的最简单方法是建立一个新列表,而不是复制列表并从中删除。

解决这两个问题,你有O(2N+M)时间,这是线性的。

所以,把两者放在一起:

b_set = set(b)
new_a = []
for element in a:
    if a in b_set:
        b_set.remove(element)
    else:
        new_a.append(element)

然而,这仍然可能有问题。你还没有很清楚地陈述事情,所以很难确定,但b可能包含重复项,如果是这样,那是否意味着应该从{{1}中删除重复的元素}} 多次?如果是这样,您需要一个多集,而不是a。在Python中执行此操作的最简单方法是使用set

Counter

另一方面,如果from collections import Counter b_counts = Counter(b) new_a = [] for element in a: if b_counts[element]: b_counts[element] -= 1 else: new_a.append(element) a的顺序都不重要,那么这只会减少到多重差异,这样会更容易:

b

但实际上,如果两者的顺序毫无意义,那么您可能应该首先使用new_a = list((Counter(a) - Counter(b)).elements()) 或其他多重表示,而不是列表......

答案 2 :(得分:1)

以下仅使用标准库

a = ['a', 'b', 'b', 'c', 'c', 'c', 'c', 'd', 'd', 'd', 'e', 'e']
b = ['a', 'c', 'e', 'f','c']
a_set = set(a)
b_set = set(b)

only_in_a = list(a_set - b_set)
diff_list = list()

for _o in only_in_a:
    tmp = a.count(_o) * _o
    diff_list.extend(tmp)

for _b in b_set:
    tmp = (a.count(_b) - b.count(_b)) * _b
    diff_list.extend(tmp)

print diff_list

并给出:

['b', 'b', 'd', 'd', 'd', 'c', 'c', 'e']

正如所料。