找出一个列表中不在另一个列表中的元素。

309
我需要比较两个列表,以便创建一个新的列表,其中包含在一个列表中找到但不在另一个列表中的特定元素。例如:
main_list = []
list_1 = ["a", "b", "c", "d", "e"]
list_2 = ["a", "f", "c", "m"] 

我想遍历list_1,并将list_2中在list_1中找不到的所有元素追加到main_list中。
结果应该是:
main_list = ["f", "m"]

怎样用Python来做呢?

2
你是否正在寻找在list_1中没有出现过的list_2元素或者在list_1中与list_2元素不在同一索引位置的元素? - Patrick Haugh
10个回答

435

您可以使用集合(sets):

main_list = list(set(list_2) - set(list_1))

输出:

>>> list_1=["a", "b", "c", "d", "e"]
>>> list_2=["a", "f", "c", "m"]
>>> set(list_2) - set(list_1)
set(['m', 'f'])
>>> list(set(list_2) - set(list_1))
['m', 'f']

根据 @JonClements 的评论,这是一个更整洁的版本:

>>> list_1=["a", "b", "c", "d", "e"]
>>> list_2=["a", "f", "c", "m"]
>>> list(set(list_2).difference(list_1))
['m', 'f']

4
如果我们只关心“唯一”的元素,那么这很好,但是如果我们有多个“m”,例如,这种方法就无法识别它。 - Chinny84
确实,我没有对你的回答进行负面评价,尤其是针对一个不清晰的原始问题。 - Chinny84
17
你可以将其写为list(set(list_2).difference(list_1)),这避免了显式的set转换... - Jon Clements
没问题!感谢 @leaf 的格式化帮助。 - nrlakin
所以 set() - set() 基本上是从前一个 set() 中删除任何在后一个 set() 中的值,仅保留唯一值,并将其作为字典输出? - oldboy
显示剩余3条评论

201

简介:
解决方案(1)

import numpy as np
main_list = np.setdiff1d(list_2,list_1)
# yields the elements in `list_2` that are NOT in `list_1`

解决方案(2) 您需要一个排序后的列表

def setdiff_sorted(array1,array2,assume_unique=False):
    ans = np.setdiff1d(array1,array2,assume_unique).tolist()
    if assume_unique:
        return sorted(ans)
    return ans
main_list = setdiff_sorted(list_2,list_1)




解释:
(1) 您可以使用NumPy的setdiff1d(array1,array2,assume_unique=False)方法。

assume_unique参数询问用户数组是否已经唯一。如果是False,则首先确定唯一元素。如果是True,则函数将假设元素已经是唯一的,并跳过确定唯一元素的步骤。

这将返回array1中不在array2中的唯一值。assume_unique默认为False

如果您关心基于Chinny84的回答的唯一元素,则只需简单地使用以下代码(其中assume_unique=False => 默认值):

import numpy as np
list_1 = ["a", "b", "c", "d", "e"]
list_2 = ["a", "f", "c", "m"] 
main_list = np.setdiff1d(list_2,list_1)
# yields the elements in `list_2` that are NOT in `list_1`


(2) 针对希望答案排序的人,我编写了一个自定义函数:

import numpy as np
def setdiff_sorted(array1,array2,assume_unique=False):
    ans = np.setdiff1d(array1,array2,assume_unique).tolist()
    if assume_unique:
        return sorted(ans)
    return ans

要获得答案,请运行:

main_list = setdiff_sorted(list_2,list_1)

SIDE NOTES:
(a) 解决方案2(使用自定义函数setdiff_sorted)返回一个列表,而解决方案1返回一个数组

(b) 如果你不确定元素是否唯一,请在A和B两个解决方案中都使用NumPy的setdiff1d的默认设置。可能会出现什么问题?请参见注释(c)。

(c) 如果其中任何一个列表不是唯一的,情况将有所不同。比如说list_2不唯一:list2 = ["a", "f", "c", "m", "m"]。保持list1不变:list_1 = ["a", "b", "c", "d", "e"]。将assume_unique的默认值设置为True会得到["f", "m", "m"](两个解决方案都是这样)。为什么呢?这是因为用户假设元素是唯一的。因此最好将assume_unique保持默认值。注意,两个答案都是排序过的。


如果您的列表已经排序,这也将返回一个有序列表。将其转换为集合然后获取差异的本地解决方案(如下所示)返回一个无序列表,这可能会使视觉检查结果更加困难。 - Doubledown
1
嗨,@Doubledown!您的问题已在编辑后的帖子中得到解决。希望这可以帮到您! - JP Maulion

93

使用类似于下面这样的列表推导式

main_list = [item for item in list_2 if item not in list_1]

输出:

>>> list_1 = ["a", "b", "c", "d", "e"]
>>> list_2 = ["a", "f", "c", "m"] 
>>> 
>>> main_list = [item for item in list_2 if item not in list_1]
>>> main_list
['f', 'm']

编辑:

如下方评论中提到的那样,对于大型列表,上述方法并非最佳解决方案。在这种情况下,更好的选择是先将list_1转换为一个set

set_1 = set(list_1)  # this reduces the lookup time from O(n) to O(1)
main_list = [item for item in list_2 if item not in set_1]

3
注意:对于更大的list_1,您可能需要先将其转换为set/frozenset,例如 set_1 = frozenset(list_1),然后使用 [item for item in list_2 if item not in set_1] 的方法生成main_list,这样可以将每个项的检查时间从O(n)降低到(大约)O(1) - ShadowRanger
请注意,如果您尝试ettanany发布的解决方案,请注意。我按原样尝试了ettanany的解决方案,并且对于较大的列表确实非常慢。您能否更新答案以纳入shadowranger的建议? - Doubledown
1
能否获取索引而不是字符串? - JareBear
1
@JareBear 你可以使用enumerate()函数:[index for (index, item) in enumerate(list_2) if item not in list_1] - ettanany
@ettanany非常感谢你!!我会尽快实现,我已经做过了。但是你的代码看起来更加简洁。 - JareBear

87

不确定为什么上面的解释这么复杂,当你有本地方法可用时:

main_list = list(set(list_2)-set(list_1))

11
保持秩序可能是原因。 - Keith

12

如果您想要一个仅需要O(max(n, m))的工作量来处理长度为nm的输入(忽略导入)的一行解决方案,而不是O(n * m)的工作量,则可以使用the itertools模块来实现:

from itertools import filterfalse

main_list = list(filterfalse(set(list_1).__contains__, list_2))

这利用了函数式函数在构造时接受回调函数的特性,使其能够创建回调函数并在每个元素中重复使用,而无需将其存储在某个地方(因为filterfalse内部存储它);列表推导和生成器表达式也可以做到这一点,但很丑陋。†

这与以下单行代码产生相同的结果:

main_list = [x for x in list_2 if x not in list_1]

速度为:

set_1 = set(list_1)
main_list = [x for x in list_2 if x not in set_1]

当然,如果比较是要按位置进行的话,那么:

list_1 = [1, 2, 3]
list_2 = [2, 3, 4]

应该产生:

main_list = [2, 3, 4]

由于在list_2中没有任何一个值与list_1中相同索引的值匹配,你应该绝对采用Patrick's answer的方法,该方法不涉及临时的listset(即使使用set大约是O(1),它们每次检查的"常数"因子比简单的相等检查更高),并且仅需要O(min(n, m))的工作量,比其他任何答案都要少。如果你的问题与位置有关,则是唯一匹配错位偏移的元素的正确解决方案。

†:要使用列表推导式作为单行代码执行相同操作的方式是滥用嵌套循环来创建和缓存“最外层”循环中的值,例如:

main_list = [x for set_1 in (set(list_1),) for x in list_2 if x not in set_1]

这也在Python 3中带来了轻微的性能优势(因为现在set_1在推导代码中是局部作用域,而不是每次检查都从嵌套作用域查找;在Python 2中这并不重要,因为Python 2不使用闭包进行列表推导;它们在使用它们的相同作用域中运行)。


6
main_list=[]
list_1=["a", "b", "c", "d", "e"]
list_2=["a", "f", "c", "m"]

for i in list_2:
    if i not in list_1:
        main_list.append(i)

print(main_list)

输出:

['f', 'm']

等价的列表推导式解决方案一样,如果list_1很大,而list_2的大小不是微不足道的话,这种方法会很慢,因为它涉及到len(list_2)次对list_1O(n)扫描,使其成为O(n * m)(其中nm分别是list_2list_1的长度)。如果您事先将list_1转换为set/frozenset,则可以在O(1)中完成包含检查,使得总工作量与list_2的长度成比例,即O(n)(从技术上讲,是O(max(n, m)),因为您需要执行O(m)的工作来创建set)。 - ShadowRanger

4
我使用了两种方法,并发现其中一种方法比另一种更有用。以下是我的答案:
我的输入数据:
crkmod_mpp = ['M13','M18','M19','M24']
testmod_mpp = ['M13','M14','M15','M16','M17','M18','M19','M20','M21','M22','M23','M24']

方法1: np.setdiff1d 我喜欢这种方法胜过其他方法,因为它保留了位置。

test= list(np.setdiff1d(testmod_mpp,crkmod_mpp))
print(test)
['M15', 'M16', 'M22', 'M23', 'M20', 'M14', 'M17', 'M21']

方法2:虽然它给出了与方法1相同的答案,但会打乱顺序。
test = list(set(testmod_mpp).difference(set(crkmod_mpp)))
print(test)
['POA23', 'POA15', 'POA17', 'POA16', 'POA22', 'POA18', 'POA24', 'POA21']

方法1 np.setdiff1d 完美地满足了我的需求。 此答案仅供参考。


4
如果需要考虑出现次数,你可能需要使用类似 collections.Counter 的东西:
list_1=["a", "b", "c", "d", "e"]
list_2=["a", "f", "c", "m"] 
from collections import Counter
cnt1 = Counter(list_1)
cnt2 = Counter(list_2)
final = [key for key, counts in cnt2.items() if cnt1[key] != counts]

>>> final
['f', 'm']

如承诺的那样,它也可以处理不同数量的出现次数,称为“差异”:

list_1=["a", "b", "c", "d", "e", 'a']
cnt1 = Counter(list_1)
cnt2 = Counter(list_2)
final = [key for key, counts in cnt2.items() if cnt1[key] != counts]

>>> final
['a', 'f', 'm']

3

我会将这些列表 zip 在一起,以便逐个元素进行比较。

main_list = [b for a, b in zip(list1, list2) if a!= b]

如果OP想要逐个元素进行比较(不清楚,示例可能有两种方式),那么这比其他答案要高效得多,因为它只需要一次便宜的遍历就可以同时遍历两个列表,并构建一个新的列表,没有额外的临时变量,也没有昂贵的包含检查等。 - ShadowRanger
1
@ShadowRanger 这只适用于逐元素差异,这是一个关键点。 - ford prefect
@fordprefect:是的。我的回答涵盖了位置无关差异。 - ShadowRanger

0

从ser1中删除与ser2相同的项。

输入

ser1 = pd.Series([1, 2, 3, 4, 5]) ser2 = pd.Series([4, 5, 6, 7, 8])

解决方案

ser1[~ser1.isin(ser2)]


欢迎来到Stack Overflow。这个问题已经有八个回答,其中一个被原帖作者接受了。请描述一下你的答案如何改进已经提出的内容。 - chb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接