优雅地在列表中查找子列表
Posted
技术标签:
【中文标题】优雅地在列表中查找子列表【英文标题】:elegant find sub-list in list 【发布时间】:2012-04-23 19:33:23 【问题描述】:给定一个列表,其中包含一个被噪声包围的已知模式,是否有一种优雅的方法可以获取与该模式相同的所有项目。下面是我的粗略代码。
list_with_noise = [7,2,1,2,3,4,2,1,2,3,4,9,9,1,2,3,4,7,4,3,1,2,3,5]
known_pattern = [1,2,3,4]
res = []
for i in list_with_noise:
for j in known_pattern:
if i == j:
res.append(i)
continue
print res
我们会得到2, 1, 2, 3, 4, 2, 1, 2, 3, 4, 1, 2, 3, 4, 4, 3
奖励:如果不存在完整模式,则避免附加 i(即,允许 1,2,3,4 但不允许 1,2,3)
例子:
find_sublists_in_list([7,2,1,2,3,4,2,1,2,3,4,9,9,1,2,3,4,7,4,3,1,2,3,5],[1,2,3,4])
[1,2,3,4],[1,2,3,4],[1,2,3,4]
find_sublists_in_list([7,2,1,2,3,2,1,2,3,6,9,9,1,2,3,4,7,4,3,1,2,6],[1,2,3,4])
[1,2,3],[1,2,3],[1,2,3]
列表包含命名元组。
【问题讨论】:
大饱眼福en.wikipedia.org/wiki/String_searching_algorithm 您可能想给出一些输入示例和相应的预期输出。你现在的问题还不清楚。 这是一个问题还是一个编程练习? @FerdinandBeyer 我不明白其中的区别。我目前有上面的代码,但想要“更好”的东西 让我澄清一下,你想从list_with_nose
获取那些在known_patterns
中的条目以相同的顺序吗?
【参考方案1】:
我知道这个问题已经 5 个月大并且已经“接受”了,但是在谷歌上搜索一个非常相似的问题让我想到了这个问题,所有的答案似乎都有几个相当重要的问题,而且我很无聊,想试着给出一个 SO 答案,所以我只是要喋喋不休地说出我发现的东西。
据我所知,问题的第一部分非常简单:只需返回原始列表,过滤掉所有不在“模式”中的元素。按照这样的想法,我想到的第一个代码使用了 filter() 函数:
def subfinder(mylist, pattern):
return list(filter(lambda x: x in pattern, mylist))
我会说这个解决方案肯定比原来的解决方案更简洁,但它并没有更快,或者至少不是明显的,如果没有很好的理由使用 lambda 表达式,我会尽量避免使用它们。事实上,我能想出的最佳解决方案涉及一个简单的列表理解:
def subfinder(mylist, pattern):
pattern = set(pattern)
return [x for x in mylist if x in pattern]
这个解决方案比原来的解决方案更优雅,速度也快得多:理解比原来的速度快 120%,而在我的测试中,将模式转换为一组第一个凹凸,速度提高了 320%。
现在是奖金:我会直接进入它,我的解决方案如下:
def subfinder(mylist, pattern):
matches = []
for i in range(len(mylist)):
if mylist[i] == pattern[0] and mylist[i:i+len(pattern)] == pattern:
matches.append(pattern)
return matches
这是 Steven Rumbalski 的“inefficient one liner”的变体,添加了“mylist[i] == pattern[0]”检查,并且由于 python 的短路评估,它比两者都快得多原始声明和 itertools 版本(以及据我所知的所有其他提供的解决方案)并且它甚至支持重叠模式。就这样吧。
【讨论】:
不错!我会在 xrange( len(mylist) - len(pattern) +1 ) 中为 i 做,但要避免不必要的比较:例如,如果 len(mylist) 是 1000,而 len(pattern) 是 50,那么当 i = 951 然后 len (mylist[i+len(pattern)]) 将是 49,而 len(pattern) 将是 50,所以没有办法可以匹配 另外,列出理解它 我发现第二个例子也发现了乱序的子列表。 我想我会将索引列表返回到找到匹配项的 mylist 中。返回模式的副本没有帮助,因为我们知道模式是什么。虽然很好的解决方案。谢谢! 请注意,这是针对相对较短的搜索模式进行优化的。从根本上讲,这种算法应该被优化以限制搜索区域中每个节点的访问。随着您增加搜索字符串的长度(和/或重复),您将增加对节点的重复访问次数。例如,[1,2,1,2,3] in [1,2,1,2,1,2,3]
将产生 21 次访问,而我的重叠解决方案只会产生 13 次访问。但是,访问计数开始提供比从 C 中下降的优化更差的性能是一个悬而未决的问题。【参考方案2】:
这将获得您问题的“奖励”部分:
pattern = [1, 2, 3, 4]
search_list = [7,2,1,2,3,4,2,1,2,3,4,9,9,1,2,3,4,7,4,3,1,2,3,5]
cursor = 0
found = []
for i in search_list:
if i == pattern[cursor]:
cursor += 1
if cursor == len(pattern):
found.append(pattern)
cursor = 0
else:
cursor = 0
对于非奖金:
pattern = [1, 2, 3, 4]
search_list = [7,2,1,2,3,4,2,1,2,3,4,9,9,1,2,3,4,7,4,3,1,2,3,5]
cursor = 0
found = []
for i in search_list:
if i != pattern[cursor]:
if cursor > 0:
found.append(pattern[:cursor])
cursor = 0
else:
cursor += 1
最后,这个处理重叠:
def find_matches(pattern_list, search_list):
cursor_list = []
found = []
for element in search_list:
cursors_to_kill = []
for cursor_index in range(len(cursor_list)):
if element == pattern_list[cursor_list[cursor_index]]:
cursor_list[cursor_index] += 1
if cursor_list[cursor_index] == len(pattern_list):
found.append(pattern_list)
cursors_to_kill.append(cursor_index)
else:
cursors_to_kill.append(cursor_index)
cursors_to_kill.reverse()
for cursor_index in cursors_to_kill:
cursor_list.pop(cursor_index)
if element == pattern_list[0]:
cursor_list.append(1)
return found
【讨论】:
@rikAtee 请注意,此解决方案将返回 2,而不是 3,导致 EOL 在问题的 cmets 中提到的重叠条件。 @rikAtee,不,我们不是实时追加的,我们只是在找到完全匹配后才追加,所以我们需要追加整个模式。 我的答案被否决了(甚至没有评论),而实际上通过不必要地和随意地将整个列表转换为字符串而引入了新错误的答案获得了 3 个赞成票?所以有时是一个奇怪的地方...... 确实如此,但它旁边还有一个大的绿色勾号! 这实际上是一个不完美的解决方案。例如,如果您在[1, 2, 1, 2, 1, 2, 3]
中搜索[1, 2, 1, 2, 3]
,它将错过匹配项。但是对于任何非重复匹配模式,这个解决方案都可以正常工作。如果要处理重复匹配模式,最有效的方法是预处理重复部分的搜索字符串,并能够让循环跟踪多个并发重叠匹配子部分。【参考方案3】:
一种基于迭代器的方法,仍然基于朴素算法,但尝试使用.index()
进行尽可能多的隐式循环:
def find_pivot(seq, subseq):
n = len(seq)
m = len(subseq)
stop = n - m + 1
if n > 0:
item = subseq[0]
i = 0
try:
while i < stop:
i = seq.index(item, i)
if seq[i:i + m] == subseq:
yield i
i += 1
except ValueError:
return
与其他几种具有不同程度的显式循环的方法相比:
def find_loop(seq, subseq):
n = len(seq)
m = len(subseq)
for i in range(n - m + 1):
if all(seq[i + j] == subseq[j] for j in (range(m))):
yield i
def find_slice(seq, subseq):
n = len(seq)
m = len(subseq)
for i in range(n - m + 1):
if seq[i:i + m] == subseq:
yield i
def find_mix(seq, subseq):
n = len(seq)
m = len(subseq)
for i in range(n - m + 1):
if seq[i] == subseq[0] and seq[i:i + m] == subseq:
yield i
一个人会得到:
a = list(range(10))
print(a)
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
b = list(range(5, 10))
print(b)
# [5, 6, 7, 8, 9]
funcs = find_pivot, find_loop, find_slice, find_mix,
for func in funcs:
print()
print(func.__name__)
print(list(func(a * 10, b)))
aa = a * 100
%timeit list(func(aa, b))
random.shuffle(aa)
%timeit list(func(aa, b))
# find_pivot
# [5, 15, 25, 35, 45, 55, 65, 75, 85, 95]
# 10000 loops, best of 3: 49.6 µs per loop
# 10000 loops, best of 3: 50.1 µs per loop
# find_loop
# [5, 15, 25, 35, 45, 55, 65, 75, 85, 95]
# 1000 loops, best of 3: 712 µs per loop
# 1000 loops, best of 3: 680 µs per loop
# find_slice
# [5, 15, 25, 35, 45, 55, 65, 75, 85, 95]
# 10000 loops, best of 3: 162 µs per loop
# 10000 loops, best of 3: 162 µs per loop
# find_mix
# [5, 15, 25, 35, 45, 55, 65, 75, 85, 95]
# 10000 loops, best of 3: 82.2 µs per loop
# 10000 loops, best of 3: 83.9 µs per loop
请注意,这比当前接受的测试输入答案快约 30%。
【讨论】:
【参考方案4】:list_with_noise = [7,2,1,2,3,4,2,1,2,3,4,9,9,1,2,3,4,7,4,3,1,2,3,5]
string_withNoise = "".join(str(i) for i in list_with_noise)
known_pattern = [1,2,3,4]
string_pattern = "".join(str(i) for i in known_pattern)
string_withNoise.count(string_pattern)
【讨论】:
与 Roman 的解决方案相同,但不够稳健(仅接受一位数的值)。【参考方案5】:给定:
a_list = [7,2,1,2,3,4,2,1,2,3,4,9,9,1,2,3,4,7,4,3,1,2,3,5]
pat = [1,2,3,4]
这是一个效率低下的单行:
res = [pat for i in range(len(a_list)) if a_list[i:i+len(pat)] == pat]
这是一个更高效的 itertools 版本:
from itertools import izip_longest, islice
res = []
i = 0
while True:
try:
i = a_list.index(pat[0], i)
except ValueError:
break
if all(a==b for (a,b) in izip_longest(pat, islice(a_list, i, i+len(pat)))):
res.append(pat)
i += len(pat)
i += 1
【讨论】:
为什么不用pat == a_list[i:i+len(pat)]
替换all(a==b…)
?这也将使您的代码更加健壮,因为列表 a_list
可能包含 None
元素,这会破坏 izip_longest()
测试(因为它会为缺少的元素生成 None
)。
@EOL 使用all
背后的想法是避免创建子列表。 None
的唯一风险是pat
包含None
,在这种情况下可以使用izip_longest
的fillvalue
参数。未使用 izip
是为了避免匹配列表末尾的部分模式。【参考方案6】:
解决问题的惯用、可组合的解决方案。
首先,我们需要借用an itertools
recipe、consume
(它从迭代器中消耗并丢弃给定数量的元素。然后我们将itertools
配方用于pairwise
,并将其扩展为@ 987654326@函数使用consume
:
import itertools
def nwise(iterable, size=2):
its = itertools.tee(iterable, size)
for i, it in enumerate(its):
consume(it, i) # Discards i elements from it
return zip(*its)
现在我们有了,解决奖金问题真的很容易:
def find_sublists_in_list(biglist, searchlist):
searchtup = tuple(searchlist)
return [list(subtup) for subtup in nwise(biglist, len(searchlist)) if subtup == searchtup]
# Or for more obscure but faster one-liner:
return map(list, filter(tuple(searchlist).__eq__, nwise(biglist, len(searchlist))))
类似地,对主要问题的更简洁和更快(如果不太漂亮)的解决方案取代了:
def subfinder(mylist, pattern):
pattern = set(pattern)
return [x for x in mylist if x in pattern]
与:
def subfinder(mylist, pattern):
# Wrap filter call in list() if on Python 3 and you need a list, not a generator
return filter(set(pattern).__contains__, mylist)
这行为相同,但避免了需要将临时 set
存储到名称中,并将所有过滤工作推送到 C。
【讨论】:
【参考方案7】:def sublist_in_list(sub, lis):
return str(sub).strip('[]') in str(lis).strip('[]')
【讨论】:
请为您的回答提供一些解释。以上是关于优雅地在列表中查找子列表的主要内容,如果未能解决你的问题,请参考以下文章