检查子列表是否包含项



我在Python中有一个列表列表。如下图所示,我想检查其中一个子列表是否包含项目。以下尝试失败。有没有人知道一种简单的方法——不用我自己写for循环?

>>> a = [[1,2],[3,4],[5,6],7,8,9]
>>> 2 in a

我本来希望True,但结果是False

>>> a = [[1,2],[3,4],[5,6],7,8,9]
>>> any(2 in i for i in a)
True

对于包含一些列表和一些整数的列表,在测试搜索目标是否在i中之前,需要测试元素i是否为列表。

>>> any(2 in i for i in a if isinstance(i, list))
True
>>> any(8 in i for i in a if isinstance(i, list))
False

如果你不检查i是否是一个列表,那么你会得到如下错误。公认的答案是错误的,因为它给出了这个错误。

>>> any(8 in i for i in a)
Traceback (most recent call last):
File "<pyshell#3>", line 1, in <module>
any(8 in i for i in a)
File "<pyshell#3>", line 1, in <genexpr>
any(8 in i for i in a)
TypeError: argument of type 'int' is not iterable

我认为在这种情况下,我们可以通过将布尔表达式的求值委托给它自己的函数来从函数编程中获得一些灵感。这样,如果你需要改变bool条件的行为,你只需要改变函数定义!

假设您想要检查子列表,并且还要检查碰巧在顶级中的int。我们可以定义一个函数,在对单个列表元素执行比较时返回布尔值:

def elem(a, b):
'''
Defines if an object b matches a.
'''
return (isinstance(b, int) and a == b) or (isinstance(b, list) and a in b)

请注意,这个函数没有说明我们的列表——我们使用的参数b只是列表中的一个元素,但我们可以很容易地调用它来比较两个值。现在我们有以下内容:

>>> a = [[1,2],[3,4],[5,6],7,8,9]
>>> any(elem(2, i) for i in a)
True
>>> any(elem(8, i) for i in a)
True
>>> any(elem(10, i) for i in a)
False

宾果!这种类型的定义的另一个好处是,它允许您部分应用函数,并使您能够为只搜索一种类型的数字分配名称:

from functools import partial
>>> contains2 = partial(elem, 2)
>>> any(map(contains2, a))
True
>>> b = [[1],[3,4],[5,6],7,8,9]]
>>> any(map(contains2, b))
False

在我看来,这使得代码可读性更强,但需要一些样板文件和了解map的功能,因为你可以让你的变量名变得有意义,而不是让临时的列表理解变量堆积如山。我并不特别在乎函数方法是否不那么Python化——Python是一种多基语言,我认为它这样看起来更好,简单明了。但这是个人的选择——这取决于你自己。

现在让我们假设我们的情况已经改变,我们现在只想检查子列表——这还不足以在顶级中发生。这没关系,因为现在我们需要改变的只是elem的定义。让我们看看:

def elem(a, b):
return isinstance(b, list) and a in b

我们刚刚删除了在b是顶级int的情况下匹配的可能性!如果我们现在运行这个:

>>> a = [[1,2],[3,4],[5,6],7,8,9,"a",["b","c"]]
>>> any(elem(2, i) for i in a)
True
>>> any(elem(8, i) for i in a)
False

我将举最后一个例子来说明这种类型的定义有多强大。假设我们有一个任意深度嵌套的整数列表。我们如何检查整数是否在任何级别中?

我们可以采用递归方法,而且根本不需要太多修改:

def elem(a, b):
return (isinstance(b, int) and a == b) or 
(isinstance(b, list) and any(map(partial(elem, a), b)))

因为我们使用了这个递归定义,它被定义为作用于单个元素,所以之前使用的所有代码行仍然有效:

>>> d = [1, [2, [3, [4, 5]]]]
>>> any(elem(1, i) for i in d)
True
>>> any(elem(4, i) for i in d)
True
>>> any(elem(10, i) for i in d)
False
>>> any(map(contains2, d))
True

当然,考虑到这个函数现在是递归的,我们真的可以直接调用它:

>>> elem(4, d)
True

但问题仍然是,这种模块化方法允许我们在不接触主脚本的情况下只更改elem的定义来更改功能,这意味着更少的TypeErrors和更快的重构。

我认为没有任何方法可以在没有某种循环的情况下进行测试。

下面是一个函数,它使用一个简单的for循环来显式检查子列表中的对象:

def sublist_contains(lst, obj):
for item in lst:
try:
if obj in item:
return True
except TypeError:
pass
return False

当然,这不会测试对象是否在顶级列表中,如果有多个级别的嵌套,也不会起作用。这里有一个使用递归的更通用的解决方案,它将循环放入传递给内置函数any:的生成器表达式中

def nested_contains(lst, obj):
return any(item == obj or
isinstance(item, list) and nested_contains(item, obj)
for item in lst)

实现这一点的简单方法是:

a = [[1,2],[3,4],[5,6],7,8,9]
result = [2 in i for i in a]
True in result --> True

最新更新