Python列表减法运算


227

我想做类似的事情:

>>> 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列表不支持此操作。最佳方法是什么?


@ezdazuzena这不是减法。这是两个列表之间的区别。您的分享不是对这个问题的重复。
Celik

1
[2,2]-[2]应该返回什么?[]?[2]?
麦凯

@McKay [2,2]-[2]应该返回[2]。[2,2]-[1,2,2,3]应该返回[]
Robino

这个问题与列表减法有关,但是可接受的答案更接近于设定减法。
罗宾诺

2
[2,1,2,3,2,4,2]-[2,3,2]应该返回什么,为什么?它应该在中间找到232并返回2142吗?还是应该每次都找到第一个并返回1242?或者是其他东西?我的意思是,这些答案不是显而易见的,而是取决于需求。
麦凯

Answers:


330

使用列表理解:

[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   

但是,如果您并非绝对需要列表属性(例如,订购),则只需将集用作其他答案即可。


10
@admica,不要使用list变量名,因为它会遮盖list构造函数。如果您确实使用“列表”,请在其前加上下划线。此外,通过删除*,您破坏了我的代码...
aaronasterling 2012年

19
如果这样做,[1,1,2,2] - [1,2]您将得到一个空列表。[1,1,2,2] - [2]给出,[1,1]所以它并不是真正的列表减法,更像是“列表X中的列表,而集合Y中没有元素”
Alfred Zien

@AlfredZien他说了什么
RetroCode

列表理解方法(在我的示例中)比集合差异方法要慢得多。
redfiloux19年

1
@BarnabasSzabolcs:那将不会节省任何事情,因为它将在每次检查之前转换y为a (这与原始工作的成本相似)。您需要要么在listcomp之外进行测试,要么进行测试,或者作为一个骇人听闻的黑客,这样做会滥用嵌套的listcomps将其作为单行缓存。可以使用性能稍差一些的单线解决方案,因为to的参数仅构造一次。setyset = set(y)if item not in yset[item for yset in [set(y)] for item in x if item not in yset]ysetlist(itertools.filterfalse(set(y).__contains__, x))filterfalse
ShadowRanger

259

使用设置差异

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

或者您可能只设置了x和y,所以您不必进行任何转换。


50
这将失去任何顺序。取决于上下文,这可能重要也可能无关紧要。
aaronasterling

63
这也将丢失所有可能需要/需要维护的重复项。
蛋白石

我知道了TypeError: unhashable type: 'dict'
Havnar '17

这是在这样的情况下更快地在那里被比较的名单是大
JqueryToAddNumbers

2
如果列表中项目的排序和重复对上下文不重要,则这是一个很好的答案,而且可读性强。
Wat Iamsuri,

37

这是“设置减法”操作。为此使用设置的数据结构。

在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])

1
list(set([[1,2,3,4,5])-set([1,2,3]))= [4,5]以便首先列出每个要设置的列表,然后减去(或单向diff )并返回列表。
gseattle

2
如果您想保持x集合的原始项目顺序,那就不好了。
Zahran

34

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

[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]

2
O(m * n)是可行的,尽管它是运行时的(而且当listcomp包含副作用时,我会畏缩);您可以使用collections.Counter获取O(m + n)运行时进行改进。
ShadowRanger

我很难理解这一点,有人可以解释吗?
anushka '19

20

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

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

这是aaronasterling的答案QuantumSoup的答案之间的混合体。

aaronasterling的版本会对len(y)中的每个元素进行项目比较x,因此需要二次时间。quantumSoup的版本用途套,所以它在每个元素一个固定时间组查找x-但是,因为其转换 xy成组,它就会失去你的元素的顺序。

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


但是,从QuantumSoup版本开始,这仍然存在一个问题:它要求您的元素是可哈希的。这几乎是内置于集合的本质中的。**如果您尝试从另一个字典列表中减去一个字典列表,但是要减去的列表很大,您会怎么做?

如果您可以用可哈希化的方式修饰值,则可以解决问题。例如,使用一个平面字典,其值本身可以哈希:

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),比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)]

如果您的商品既不可散列,也不可比,那么您将无法使用二次解法。


*请注意,您也可以通过使用一对OrderedSet对象来执行此操作,可以为它们找到配方和第三方模块。但是我认为这比较简单。

**原因是集合查询是固定时间的原因是它要做的就是对值进行哈希处理,然后查看是否有该哈希的条目。如果无法散列值,则此方法将无效。


7

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

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

我相信这会比以下方法更好:

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

两者都保留列表的顺序。


它会在每个循环中缓存set(y)而不转换y为新的集吗?否则,您需要abarnert的回答:ys = set(y); [i for i in x if i not in ys]
Jacktose

2
一些粗略的测试表明,if i not in set(y)所需时间比if i not in yy列出)多25%。预先转换设置可节省55%的时间。使用x和进行了相当短的测试y,但是如果有的话,差异应该在长度上更加明显。
Jacktose

1
@Jacktose:是的,这个解决方案做更多的工作,因为它具有遍历和哈希每个元素y每个元素x; 除非相等比较相对于散列计算而言确实非常昂贵,否则这将始终不为所动item not in y
ShadowRanger

@ShadowRanger这很有意义。如果集转换是执行该检查的可靠,快捷的方法,则您会认为编译器将始终以该方式进行检查。
Jacktose

5

如果列表中允许重复的元素,则可以使用集合中的Counter:

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

如果需要保留x的元素顺序:

result = [ v for c in [Counter(y)] for v in x if not c[v] or c.subtract([v]) ]

很好,尽管它确实失去了排序;修复起来有点复杂
ShadowRanger

@ShadowRanger,的确是。但一点点。
Alain T.

别介意我,我只是对listcomps的缓存和副作用感到不寒而栗(尽管我认为两者的结合消除了外部可见的副作用?)。:-)
ShadowRanger

另外,此代码不能按书面形式工作;Counter.subtract不会删除零值元素(-并且-=确实subtract会删除,但不会),因此您永远不会停止删除元素。您需要替换not v in cnot c[v](对于不存在的元素返回零,因此您可以通过来安全地测试返回的“零度” not)。
ShadowRanger

@ShadowRanger,好收获!立即修复。
Alain T.

3

其他解决方案具有以下几个问题之一:

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

Alain可以正确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.

在线尝试!

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

构建CounterO(n)在以下方面y的长度,迭代xO(n)在以下方面x的长度,以及Counter会员的测试和突变的O(1),而list.append摊销O(1)(一个给定的append可以O(n),但对许多appendS,整体大O平均数O(1),因为越来越少其中的一个需要重新分配),因此完成的总体工作是O(m + n)

您还可以进行测试以确定是否有某些元素y没有x通过测试从中删除:

remaining = +remaining  # Removes all keys with zero counts from Counter
if remaining:
    # remaining contained elements with non-zero counts

注意:这确实要求值可以散列,但是任何不需要散列对象的解决方案都不是通用的(例如,可以将ints计入固定长度的数组中),或者必须要做的事比O(m + n)做事(例如,第二大的大) -O将产生一list组唯一的值/计数对,将O(1) dict查找更改为O(log n)二进制搜索;您将需要带有其计数的唯一值,而不仅仅是排序的非唯一值,因为否则您将O(n)为此付出费用以删除排序后的元素list)。
ShadowRanger

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
>>>

2

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

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

1

@aaronasterling提供的答案看起来不错,但是它与list的默认接口不兼容: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

0

我认为这更快:

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}

这不是减法。实际上,这是两个列表之间的对称差异。
Parth Chauhan

而且,这仅适用于列表中的可
哈希

-1

本示例减去两个列表:

# 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))

8
避免这种情况,这是O(N ^ 2)
亚历山大–恢复莫妮卡
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.