我想做类似的事情:
>>> 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列表不支持此操作。最佳方法是什么?
Answers:
使用列表理解:
[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
但是,如果您并非绝对需要列表属性(例如,订购),则只需将集用作其他答案即可。
list
变量名,因为它会遮盖list
构造函数。如果您确实使用“列表”,请在其前加上下划线。此外,通过删除*
,您破坏了我的代码...
[1,1,2,2] - [1,2]
您将得到一个空列表。[1,1,2,2] - [2]
给出,[1,1]
所以它并不是真正的列表减法,更像是“列表X中的列表,而集合Y中没有元素”。
y
为a (这与原始工作的成本相似)。您需要要么在listcomp之外进行测试,要么进行测试,或者作为一个骇人听闻的黑客,这样做会滥用嵌套的listcomps将其作为单行缓存。可以使用性能稍差一些的单线解决方案,因为to的参数仅构造一次。set
yset = set(y)
if item not in yset
[item for yset in [set(y)] for item in x if item not in yset]
yset
list(itertools.filterfalse(set(y).__contains__, x))
filterfalse
TypeError: unhashable type: 'dict'
如果重复和订购商品有问题:
[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]
对于许多用例,您想要的答案是:
ys = set(y)
[item for item in x if item not in ys]
这是aaronasterling的答案和QuantumSoup的答案之间的混合体。
aaronasterling的版本会对len(y)
中的每个元素进行项目比较x
,因此需要二次时间。quantumSoup的版本用途套,所以它在每个元素一个固定时间组查找x
-但是,因为其转换都 x
和y
成组,它就会失去你的元素的顺序。
通过仅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
对象来执行此操作,可以为它们找到配方和第三方模块。但是我认为这比较简单。
**原因是集合查询是固定时间的原因是它要做的就是对值进行哈希处理,然后查看是否有该哈希的条目。如果无法散列值,则此方法将无效。
在集合中查找值比在列表中查找值更快:
[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]
。
if i not in set(y)
所需时间比if i not in y
(y
列出)多25%。预先转换设置可节省55%的时间。使用x
和进行了相当短的测试y
,但是如果有的话,差异应该在长度上更加明显。
y
的每个元素x
; 除非相等比较相对于散列计算而言确实非常昂贵,否则这将始终不为所动item not in y
。
如果列表中允许重复的元素,则可以使用集合中的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]) ]
Counter.subtract
不会删除零值元素(-
并且-=
确实subtract
会删除,但不会),因此您永远不会停止删除元素。您需要替换not v in c
为not c[v]
(对于不存在的元素返回零,因此您可以通过来安全地测试返回的“零度” not
)。
其他解决方案具有以下几个问题之一:
x = [1, 2, 2, 2]
并将y = [2, 2]
它们转换y
为set
,或者删除所有匹配的元素([1]
仅保留)或删除每个唯一元素中的一个(保留[1, 2, 2]
),而正确的行为是删除2
两次,离开[1, 2]
,或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
循环后立即添加。
构建Counter
是O(n)
在以下方面y
的长度,迭代x
是O(n)
在以下方面x
的长度,以及Counter
会员的测试和突变的O(1)
,而list.append
摊销O(1)
(一个给定的append
可以O(n)
,但对许多append
S,整体大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
int
s计入固定长度的数组中),或者必须要做的事比O(m + n)
做事(例如,第二大的大) -O将产生一list
组唯一的值/计数对,将O(1)
dict
查找更改为O(log n)
二进制搜索;您将需要带有其计数的唯一值,而不仅仅是排序的非唯一值,因为否则您将O(n)
为此付出费用以删除排序后的元素list
)。
试试这个。
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
>>>
@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
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}
本示例减去两个列表:
# 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))