Python列表减法运算

时间:2010-08-06 23:43:12

标签: python list

我想做类似的事情:

>>> x = [1,2,3,4,5,6,7,8,9,0]  
>>> x  
[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]  
>>> y = [1,3,5,7,9]  
>>> y  
[1, 3, 5, 7, 9]  
>>> y - x   # (should return [2,4,6,8,0])

但python列表不支持此功能 这样做的最佳方式是什么?

13 个答案:

答案 0 :(得分:259)

使用列表理解:

[item for item in x if item not in y]

如果您想使用-中缀语法,您可以这样做:

class MyList(list):
    def __init__(self, *args):
        super(MyList, self).__init__(args)

    def __sub__(self, other):
        return self.__class__(*[item for item in self if item not in other])

然后您可以像:

一样使用它
x = MyList(1, 2, 3, 4)
y = MyList(2, 5, 2)
z = x - y   

但是如果你不是绝对需要列表属性(例如,排序),只需使用集合作为其他答案推荐。

答案 1 :(得分:201)

使用set difference

>>> z = list(set(x) - set(y))
>>> z
[0, 8, 2, 4, 6]

或者您可能只有x和y设置,因此您无需进行任何转换。

答案 2 :(得分:34)

这是一个“set subtraction”操作。使用设置数据结构。

在Python 2.7中:

x = {1,2,3,4,5,6,7,8,9,0}
y = {1,3,5,7,9}
print x - y

输出:

>>> print x - y
set([0, 8, 2, 4, 6])

答案 3 :(得分:28)

如果重复和订购商品有问题:

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

a = [1,2,3,3,3,3,4]
b = [1,3]
result: [2, 3, 3, 3, 4]

答案 4 :(得分:15)

对于许多用例,您需要的答案是:

ys = set(y)
[item for item in x if item not in ys]

这是aaronasterling's answerquantumSoup's answer之间的混合。

aaronasterling的版本对len(y)中的每个元素进行x项目比较,因此需要二次时间。 quantumSoup的版本使用集合,因此它为x中的每个元素执行单个常量时间集查找 - 但是,因为它将 xy转换为集合,它会失去你元素的顺序。

通过仅将y转换为集合,并按顺序迭代x,您可以获得两全其美的线性时间和订单保存。*


但是,这仍然存在来自quantumSoup版本的问题:它需要您的元素可以清除。这几乎是集合的本质所在。**如果你想尝试,例如,从另一个dicts列表中减去一个dicts列表,但要减去的列表很大,你会怎么做?

如果你能以某种方式装饰你的价值,那么它就可以解决问题了。例如,使用平面字典,其值本身可以清除:

ys = {tuple(item.items()) for item in y}
[item for item in x if tuple(item.items()) not in ys]

如果您的类型有点复杂(例如,通常您处理的是可混合的JSON兼容值,或者其值是递归相同类型的列表或词组),您仍然可以使用此解决方案。但是有些类型只是无法转换为任何类型的哈希。


如果您的物品没有,也不能制作,可以清洗,但它们具有可比性,您至少可以获得对数线性时间(O(N*log M),这比{{1}好很多通过排序和使用O(N*M)来列出解决方案的时间,但不如设置解决方案的O(N+M)时间那么好:

bisect

如果您的物品既不可清洗也不可比,那么您就会陷入二次解决方案。


*请注意,您也可以使用一对ys = sorted(y) def bisect_contains(seq, item): index = bisect.bisect(seq, item) return index < len(seq) and seq[index] == item [item for item in x if bisect_contains(ys, item)] 对象来执行此操作,您可以在其中找到配方和第三方模块。但我认为这更简单。

**设置查找是恒定时间的原因是它所要做的就是对值进行散列并查看是否有该哈希的条目。如果它不能散列该值,则无效。

答案 5 :(得分:8)

在集合中查找值比在列表中查找更快:

[item for item in x if item not in set(y)]

我相信这会略微好于:

[item for item in x if item not in y]

两者都保留了列表的顺序。

答案 6 :(得分:2)

试试这个。

def subtract_lists(a, b):
    """ Subtracts two lists. Throws ValueError if b contains items not in a """
    # Terminate if b is empty, otherwise remove b[0] from a and recurse
    return a if len(b) == 0 else [a[:i] + subtract_lists(a[i+1:], b[1:]) 
                                  for i in [a.index(b[0])]][0]

>>> x = [1,2,3,4,5,6,7,8,9,0]
>>> y = [1,3,5,7,9]
>>> subtract_lists(x,y)
[2, 4, 6, 8, 0]
>>> x = [1,2,3,4,5,6,7,8,9,0,9]
>>> subtract_lists(x,y)
[2, 4, 6, 8, 0, 9]     #9 is only deleted once
>>>

答案 7 :(得分:2)

其他解决方案存在以下几个问题之一:

  1. 他们不保留订单,或者
  2. 它们不会删除精确数量的元素,例如对于x = [1, 2, 2, 2]y = [2, 2],他们将y转换为set,并删除所有匹配元素(仅保留[1])或删除每个唯一元素之一(离开[1, 2, 2]),那么正确的行为是将2删除两次,离开[1, 2],或者
  3. 他们从事O(m * n)工作,而最佳解决方案可以从事O(m + n)工作

Alain was on the right track with Counter解决#2和#3,但是该解决方案将失去排序。保留顺序的解决方案(为要删除的值n中的n个重复项,删除每个值的前list个副本)是:

from collections import Counter

x = [1,2,3,4,3,2,1]  
y = [1,2,2]  
remaining = Counter(y)

out = []
for val in x:
    if remaining[val]:
        remaining[val] -= 1
    else:
        out.append(val)
# out is now [3, 4, 3, 1], having removed the first 1 and both 2s.

Try it online!

要删除每个元素的 last 个副本,只需将for循环更改为for val in reversed(x):并在退出{{1后立即添加out.reverse() }}循环。

根据for的长度来构造CounterO(n),根据y的形式来迭代xO(n)长度,x成员资格测试和突变为Counter,而O(1)则摊销list.append(给定的O(1)可以为append,但对于许多O(n),总体大O平均值append,因为越来越少的人需要重新分配),因此完成的总体工作是O(1)

您还可以通过测试来确定O(m + n)中是否有未从y中删除的元素:

x

答案 8 :(得分:1)

@aaronasterling提供的答案看起来不错,但是它与列表的默认界面不兼容:x = MyList(1, 2, 3, 4) vs x = MyList([1, 2, 3, 4])。因此,下面的代码可以用作更友好的python列表:

class MyList(list):
    def __init__(self, *args):
        super(MyList, self).__init__(*args)

    def __sub__(self, other):
        return self.__class__([item for item in self if item not in other])

示例:

x = MyList([1, 2, 3, 4])
y = MyList([2, 5, 2])
z = x - y

答案 9 :(得分:1)

我认为这更快:

In [1]: a = [1,2,3,4,5]

In [2]: b = [2,3,4,5]

In [3]: c = set(a) ^ set(b)

In [4]: c
Out[4]: {1}

答案 10 :(得分:1)

我认为最简单的方法是使用set()。

{{1}}

答案 11 :(得分:0)

此示例减去两个列表:

# List of pairs of points
list = []
list.append([(602, 336), (624, 365)])
list.append([(635, 336), (654, 365)])
list.append([(642, 342), (648, 358)])
list.append([(644, 344), (646, 356)])
list.append([(653, 337), (671, 365)])
list.append([(728, 13), (739, 32)])
list.append([(756, 59), (767, 79)])

itens_to_remove = []
itens_to_remove.append([(642, 342), (648, 358)])
itens_to_remove.append([(644, 344), (646, 356)])

print("Initial List Size: ", len(list))

for a in itens_to_remove:
    for b in list:
        if a == b :
            list.remove(b)

print("Final List Size: ", len(list))

答案 12 :(得分:0)

您可以使用集合中的计数器:

from collections import Counter
result = list((Counter(x)-Counter(y)).elements())