检查列表中是否存在保留顺序的子列表,并允许使用通配符



我想检查子列表是否以完全相同的元素顺序出现在另一个(较大的(列表中。我还希望它允许使用通配符。例如,我有以下列表:

>>> my_lists
[[0, 0, 1, 0, 2, 0, 0, 0, 0, 0, 2, 2],
[1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 0, 0, 1, 1, 1, 1],
[1, 1, 1, 1, 2, 2, 1, 2, 2, 2, 1, 1, 2, 1, 1, 0, 0, 1, 1, 1, 1],
[1, 1, 1, 1, 0, 2, 1, 2, 1, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 1, 1, 2, 1, 2, 2, 1, 1],
[0, 1, 1, 1, 1, 2, 2, 2, 1, 0, 0, 0, 2, 2, 1, 1, 0, 0, 1, 1, 0],
[1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 0, 0, 1, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 1, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0],
[0, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 0, 0, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 1, 1, 0, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 2, 1, 1, 0, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 1, 0, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1],
[0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]]

子列表:[0, 0, 0, 1]。如果我想找到哪些列表包含这个确切的子列表,我可以做(取自这里(:

def my_func(_list, sub_list):
n = len(sub_list)
return any((sub_list== _list[i:i+n]) for i in range(len(_list)-n+1))
for l in my_lists:
if my_func(l, [0, 0, 0, 1]):
print(l)

其基本上使得所有可能的子列表具有与sub_list相同的长度并且检查是否有任何子列表相等。由于这些列表包含[0, 0, 0, 1]:,我将得到以下输出

[1, 1, 1, 1, 0, 2, 1, 2, 1, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 1, 0]
[0, 0, 0, 0, 1, 1, 2, 1, 2, 2, 1, 1]
[1, 0, 0, 0, 0, 0, 1, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0]
[0, 0, 0, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]

现在我还想添加通配符,这意味着我可以为子列表提供通配符元素。例如,现在我想查找子列表[*, *, 0, 0, 0, 1, *]。这里的星号表示,对于这些元素,值可以是列表中的任何值。但对于这些星号,必须有一个值。子列表[*, *, 0, 0, 0, 1, *]现在将输出:

[1, 1, 1, 1, 0, 2, 1, 2, 1, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 1, 0]
[1, 0, 0, 0, 0, 0, 1, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0]

请注意,现在不包括[0, 0, 0, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],因为在[0, 0, 0, 1]序列开始之前,该列表没有两个值。[0, 0, 0, 0, 1, 1, 2, 1, 2, 2, 1, 1]也是如此,它在序列之前也没有两个值。请注意,星号可以是np.nan.之类的任何内容

我将如何扩展上面的代码以允许使用通配符?

一种方法是在检查子列表时使用all和跳过星号的if

def my_func(a_list, sub_list):
n = len(sub_list)
# list-level comparison is now via element-wise
return any(all(sub_item == chunk_item
for sub_item, chunk_item in zip(sub_list, a_list[i:i+n])
if not np.isnan(sub_item))  # "is_not_asterisk" condition
for i in range(len(a_list)-n+1))

其中我使用not np.isnan(...)作为问题中提到的星号条件;但可能有很多事情:例如,如果星号在子列表中的字面意思是"*",那么那里的条件就变为if sub_item != "*"

np.nan为星号的样本:

for a_list in my_lists:
if my_func(a_list, [np.nan, np.nan, 0, 0, 0, 1, np.nan]):
print(a_list)

给出

[1, 1, 1, 1, 0, 2, 1, 2, 1, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 1, 0]
[1, 0, 0, 0, 0, 0, 1, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0]

all在可迭代项为空的情况下返回True,因此如果传递了一个全星号子列表,它将为所有候选项返回True(只要它们的长度允许,any将处理,因为具有空可迭代项的anyFalse!(。

另一个解决方案,具有自定义比较功能:

def custom_cmp(l1, l2):
if len(l1) != len(l2):
return False
for a, b in zip(l1, l2):
if a == "*":  # you can check here for b=='*' if you wish
continue
if a != b:
return False
return True

def my_func(_list, sub_list):
n = len(sub_list)
return any(
custom_cmp(sub_list, _list[i : i + n])
for i in range(len(_list) - n + 1)
)

for l in my_lists:
if my_func(l, ["*", "*", 0, 0, 0, 1, "*"]):
print(l)

打印:

[1, 1, 1, 1, 0, 2, 1, 2, 1, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 1, 0]
[1, 0, 0, 0, 0, 0, 1, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0]

如果我们创建一个SuperInt类,允许我们包装int,但使其等于另一个具有相同值的实例(或具有相同值"正常"int(字符串'*',我们可以使用您已经拥有的相同代码。

WILDCARD = '*'
class SuperInt(int):
def __eq__(self, other):
if not isinstance(other, self.__class__) and other == WILDCARD:
# or
# if isinstance(other, str) and other == '*': but there might be a caveat with that
return True
return super().__eq__(other)

my_lists转换为使用SuperInt实例:

for i, li in enumerate(my_lists):
my_lists[i] = list(map(SuperInt, li))

运行与您已经拥有的代码完全相同的(只是用上面定义的WILDCARD替换*(:

def my_func(_list, sub_list):
n = len(sub_list)
return any((sub_list == _list[i:i+n]) for i in range(len(_list)-n+1))
for l in my_lists:
if my_func(l, [WILDCARD, WILDCARD, 0, 0, 0, 1, WILDCARD]):
print(l)

输出

[1, 1, 1, 1, 0, 2, 1, 2, 1, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 1, 0]
[1, 0, 0, 0, 0, 0, 1, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0]

如果列表中的元素总是单个字符(例如示例中的数字(,则可以转换为字符串并使用re模块搜索主题。这比@Mustafa Aydın的答案快10多倍,在我看来,作为一句话写起来相当快。但当然,它不适用于具有多字符元素的数据集。

import re
[l for l in my_lists if re.search('..0001.', ''.join(map(str, l)))]

或者,使用过滤器:

list(filter(lambda l: re.search('..0001.', ''.join(map(str, l))), my_lists))

如果你需要将你的查询输入为一个列表,其中包含提议的通配符格式:

pattern = ['*', '*', 0, 0, 0, 1, '*']
[l
for l in my_lists
if re.search(''.join(map(str, pattern)).replace('*', '.'),
''.join(map(str, l)))
]

这个问题已经得到了回答,但这里有另一个我喜欢认为是"老派"的选项

my_lists = [
[0, 0, 1, 0, 2, 0, 0, 0, 0, 0, 2, 2],
[1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 0, 0, 1, 1, 1, 1],
[1, 1, 1, 1, 2, 2, 1, 2, 2, 2, 1, 1, 2, 1, 1, 0, 0, 1, 1, 1, 1],
[1, 1, 1, 1, 0, 2, 1, 2, 1, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 1, 1, 2, 1, 2, 2, 1, 1],
[0, 1, 1, 1, 1, 2, 2, 2, 1, 0, 0, 0, 2, 2, 1, 1, 0, 0, 1, 1, 0],
[1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 0, 0, 1, 0, 0, 0],
[1, 0, 0, 0, 0, 0, 1, 1, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0],
[0, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 0, 0, 1, 1, 1, 1, 1, 1, 1],
[0, 0, 1, 1, 0, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 2, 1, 1, 0, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 1, 0, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1],
[0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0],
[0, 0, 0, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
]
my_sub_list = [0, 0, 0, '*', 1]
def my_function(_list, _sub):
a = len(_list)
b = len(_sub)
x = 0                                              # sublist index
y = 0                                              # list index
while y < a:
if (_sub[x] == _list[y]) or (_sub[x] == '*'):  # we have a match
x += 1                                     # increment sublist index counter
y += 1                                     # increment list index counter
if x == b:                                 # if sublist index equals sublist length, then we've found a match
print(_list)
return
else:
if x > 0:                                   # We had a partial match
y -= x - 1                              # Resetting index counter to next index after previous match
else:
y += 1                                  # No match, so we're moving to the next index
x = 0                                       # Resetting sublist index to the beginning

for l in my_lists:
my_function(l, my_sub_list)

最新更新