我想检查子列表是否以完全相同的元素顺序出现在另一个(较大的(列表中。我还希望它允许使用通配符。例如,我有以下列表:
>>> 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
将处理,因为具有空可迭代项的any
是False
!(。
另一个解决方案,具有自定义比较功能:
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)