Python列表减法运算

Python列表减法运算,python,list,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列表不支持这一点 最好的方法是什么?使用 或者

我想做类似的事情:

>>> 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列表不支持这一点 最好的方法是什么?

使用

或者,您可以只设置x和y,这样就不必进行任何转换。

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

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

使用列表理解:

[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   
但是,如果您不一定需要列表属性(例如,排序),只需按照其他答案的建议使用集合。

试试这个

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

如果存在重复和订购项目的问题:

[i代表a中的i,如果不是b中的i或b中的i.remove(i)]


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

ys = set(y)
[item for item in x if item not in ys]
这是和之间的混合

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

通过仅将
y
转换为一个集合,并按顺序迭代
x
,您可以获得最佳的线性时间和顺序保持*


但是,quantumSoup的版本仍然存在一个问题:它要求元素是可散列的。这几乎是集合的本质。**如果你试图,例如,从另一个dict列表中减去一个dict列表,但是要减去的列表很大,你会怎么做

如果你可以用某种方式来修饰你的值,使它们可以散列,那么问题就解决了。例如,对于值本身可散列的平面字典:

ys = {tuple(item.items()) for item in y}
[item for item in x if tuple(item.items()) not in ys]
如果您的类型稍微复杂一些(例如,您经常处理JSON兼容的值,这些值是可散列的,或者列表或dict的值是递归的相同类型),您仍然可以使用此解决方案。但是有些类型不能转换成任何可散列的内容


如果您的项目不可散列,也无法进行散列,但它们是可比较的,那么您至少可以通过排序和使用对分法,获得对数线性时间(
O(N*logm)
,这比列表解决方案的
O(N*M)
时间好得多,但不如设置解决方案的
O(N+M)
时间好:

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)]
ys=已排序(y)
def对分包含(序号,项目):
索引=二等分。二等分(序号,项目)
返回索引

如果您的项既不可散列也不可比较,那么您就只能使用二次解


*请注意,您也可以通过使用一对
OrderedSet
对象来实现这一点,您可以为这些对象找到配方和第三方模块。但我认为这更简单


**设置查找是固定时间的原因是,它所要做的就是散列值,并查看是否有该散列的条目。如果无法对值进行散列,则无法执行此操作。

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

[item for item in x if item not in set(y)]
我相信这将比以下几点更好:

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

两者都保留列表的顺序。

此示例减去两个列表:

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

@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

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

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

我认为实现这一点最简单的方法是使用set()


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

  • 他们不维持秩序,或者
  • 它们不删除元素的精确计数,例如,对于
    x=[1,2,2]
    y=[2,2]
    它们将
    y
    转换为
    ,或者删除所有匹配元素(只保留
    [1]
    ),或者删除每个唯一元素中的一个(保留
    [1,2]
    ),正确的行为是删除
    2
    两次,留下
    [1,2]
    ,或
  • 它们可以
    O(m*n)
    工作,而最优解可以
    O(m+n)
    工作
  • 解决#2和#3,但该解决方案将失去顺序。保留顺序的解决方案(删除要删除的
    值列表中
    n
    重复的每个值的第一个
    n
    副本)是:

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

    y
    长度而言,构造
    计数器是
    O(n)
    ,就
    x
    长度而言,迭代
    x
    O(n)
    ,就
    x
    长度而言,
    计数器
    成员测试和变异是
    O(1)
    ,而
    列表.append
    是摊销
    O(1)
    (一个给定的
    append
    可以是
    O(n)
    ,但是对于许多
    append
    s,总的big-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
    

    这将丢失任何顺序。这可能会也可能不重要,取决于上下文。这还将丢失任何可能需要/想要维护的重复项。我得到
    TypeError:unhable type:“dict”
    如果要比较的列表顺序较大,并且列表中的项目重复,则这会更快
    result = [ v for c in [Counter(y)] for v in x if not c[v] or c.subtract([v]) ]
    
    >>> 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]
    
    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.
    
    remaining = +remaining  # Removes all keys with zero counts from Counter
    if remaining:
        # remaining contained elements with non-zero counts