[英]How to remove every occurrence of sub-list from list
我有两个清单:
big_list = [2, 1, 2, 3, 1, 2, 4]
sub_list = [1, 2]
我想删除big_list中的所有sub_list事件。
结果应该是[2, 3, 4]
对于字符串,您可以使用此:
'2123124'.replace('12', '')
但AFAIK这对列表不起作用。
这不是从列表中删除子列表的重复,因为我想从大列表中删除所有子列表。 在另一个问题中,结果应为[5,6,7,1,2,3,4]
。
更新:为简单起见,我在此示例中使用了整数。 但列表项可以是任意对象。
UPDATE2:
如果big_list = [1, 2, 1, 2, 1]
和sub_list = [1, 2, 1]
,我希望结果为[2, 1]
(如'12121'.replace('121', '')
)
UPDATE3:
我不喜欢将StackOverflow中的源代码复制+粘贴到我的代码中。 这就是我在软件建议中提出第二个问题的原因: https : //softwarerecs.stackexchange.com/questions/51273/library-to-remove-every-occurrence-of-sub-list-from-list-python
Update4:如果您知道要进行此方法调用的库,请将其写为答案,因为这是我首选的解决方案。
测试应通过此测试:
def test_remove_sub_list(self):
self.assertEqual([1, 2, 3], remove_sub_list([1, 2, 3], []))
self.assertEqual([1, 2, 3], remove_sub_list([1, 2, 3], [4]))
self.assertEqual([1, 3], remove_sub_list([1, 2, 3], [2]))
self.assertEqual([1, 2], remove_sub_list([1, 1, 2, 2], [1, 2]))
self.assertEquals([2, 1], remove_sub_list([1, 2, 1, 2, 1], [1, 2, 1]))
self.assertEqual([], remove_sub_list([1, 2, 1, 2, 1, 2], [1, 2]))
你必须自己实现它。 这是基本的想法:
def remove_sublist(lst, sub):
i = 0
out = []
while i < len(lst):
if lst[i:i+len(sub)] == sub:
i += len(sub)
else:
out.append(lst[i])
i += 1
return out
这将沿原始列表的每个元素进行步骤,如果它不是子集的成员,则将其添加到输出列表。 此版本效率不高,但它的工作方式与您提供的字符串示例类似,因为它创建的新列表不包含您的子集。 它也适用于任意元素类型,只要它们支持==
。 从[1,1,1,1]
删除[1,1,1]
将正确地导致[1]
,就像字符串一样。
这是一个显示结果的IDEOne链接
>>> remove_sublist([1, 'a', int, 3, float, 'a', int, 5], ['a', int])
[1, 3, <class 'float'>, 5]
尝试del
和slicing
。 最差的时间复杂度是O(N^2)
。
sub_list=['a', int]
big_list=[1, 'a', int, 3, float, 'a', int, 5]
i=0
while i < len(big_list):
if big_list[i:i+len(sub_list)]==sub_list:
del big_list[i:i+len(sub_list)]
else:
i+=1
print(big_list)
结果:
[1, 3, <class 'float'>, 5]
递归方法:
def remove(lst, sub):
if not lst:
return []
if lst[:len(sub)] == sub:
return remove(lst[len(sub):], sub)
return lst[:1] + remove(lst[1:], sub)
print(remove(big_list, sub_list))
这输出:
[2, 3, 4]
用于检查lst[i:i+len(sub)] < len(lst)
改进版本
def remove_sublist(lst, sub):
i = 0
out = []
sub_len = len(sub)
lst_len = len(lst)
while i < lst_len:
if (i+sub_len) < lst_len:
if lst[i: i+sub_len] == sub:
i += sub_len
else:
out.append(lst[i])
i += 1
else:
out.append(lst[i])
i += 1
return out
这个怎么样:
def remove_sublist(lst, sub):
max_ind_sub = len(sub) - 1
out = []
i = 0
tmp = []
for x in lst:
if x == sub[i]:
tmp.append(x)
if i < max_ind_sub: # partial match
i += 1
else: # found complete match
i = 0
tmp = []
else:
if tmp: # failed partial match
i = 0
out += tmp
if x == sub[0]: # partial match
i += 1
tmp = [x]
else:
out.append(x)
return out
性能:
lst = [2, 1, 2, 3, 1, 2, 4]
sub = [1, 2]
%timeit remove_sublist(lst, sub) # solution of Mad Physicist
%timeit remove_sublist_new(lst, sub)
>>> 2.63 µs ± 112 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
>>> 1.77 µs ± 13.7 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
更新
我的第一个解决方案有一个bug。 能够修复它(更新我上面的代码),但该方法现在看起来更复杂。 在性能方面,它仍然比我在本地机器上的Mad Physicist的解决方案更好。
使用itertools.zip_longest
创建n个元素元组(其中n是sub_list的长度),然后当其中一个元素与sub_list匹配时,过滤当前元素和下一个n-1个元素
>>> from itertools import zip_longest, islice
>>> itr = zip_longest(*(big_list[i:] for i in range(len(sub_list))))
>>> [sl[0] for sl in itr if not (sl == tuple(sub_list) and next(islice(itr, len(sub_list)-2, len(sub_list)-1)))]
[2, 3, 4]
为了提高效率,您可以在开始过滤之前计算tuple(sub_list)
和len(sub_list)
>>> l = len(sub_list)-1
>>> tup = tuple(sub_list)
>>> [sl[0] for sl in itr if not (sl == tup and next(islice(itr, l-1, l)))]
[2, 3, 4]
更新 : more_itertools
库发布了more_itertool.replace
,这是一个解决此特定问题的工具(参见选项3)。
首先,这里有一些其他选项适用于通用迭代(列表,字符串,迭代器等):
码
选项1 - 没有库 :
def remove(iterable, subsequence):
"""Yield non-subsequence items; sans libraries."""
seq = tuple(iterable)
subsequence = tuple(subsequence)
n = len(subsequence)
skip = 0
for i, x in enumerate(seq):
slice_ = seq[i:i+n]
if not skip and (slice_ == subsequence):
skip = n
if skip:
skip -= 1
continue
yield x
选项2 - 使用more_itertools
import more_itertools as mit
def remove(iterable, subsequence):
"""Yield non-subsequence items."""
iterable = tuple(iterable)
subsequence = tuple(subsequence)
n = len(subsequence)
indices = set(mit.locate(mit.windowed(iterable, n), pred=lambda x: x == subsequence))
it_ = enumerate(iterable)
for i, x in it_:
if i in indices:
mit.consume(it_, n-1)
else:
yield x
演示
list(remove(big_list, sub_list))
# [2, 3, 4]
list(remove([1, 2, 1, 2], sub_list))
# []
list(remove([1, "a", int, 3, float, "a", int, 5], ["a", int]))
# [1, 3, float, 5]
list(remove("11111", "111"))
# ['1', '1']
list(remove(iter("11111"), iter("111")))
# ['1', '1']
选项3 - 使用more_itertools.replace
:
演示
pred = lambda *args: args == tuple(sub_list)
list(mit.replace(big_list, pred=pred, substitutes=[], window_size=2))
# [2, 3, 4]
pred=lambda *args: args == tuple(sub_list)
list(mit.replace([1, 2, 1, 2], pred=pred, substitutes=[], window_size=2))
# []
pred=lambda *args: args == tuple(["a", int])
list(mit.replace([1, "a", int, 3, float, "a", int, 5], pred=pred, substitutes=[], window_size=2))
# [1, 3, float, 5]
pred=lambda *args: args == tuple("111")
list(mit.replace("11111", pred=pred, substitutes=[], window_size=3))
# ['1', '1']
pred=lambda *args: args == tuple(iter("111"))
list(mit.replace(iter("11111"), pred=pred, substitutes=[], window_size=3))
# ['1', '1']
细节
在所有这些示例中,我们使用较小的窗口切片扫描主序列。 我们产生切片中未找到的任何内容并跳过切片中的任何内容。
选项1 - 没有库
迭代枚举序列并评估大小为n
切片(子序列的长度)。 如果即将到来的切片等于子序列,则重置skip
并产生该项。 否则,迭代它。 skip
跟踪多少次推进循环,例如sublist
的大小为n=2
,因此每次匹配跳过两次。
注意,你可以通过删除前两个元组赋值并用seq
替换iterable
参数来转换此选项以单独使用序列 ,例如def remove(seq, subsequence):
选项2 - 使用more_itertools
在迭代中为每个匹配的子序列定位索引。 在迭代枚举迭代器时,如果在索引中找到indices
,则通过使用迭代器中的下一个n-1
元素来跳过剩余的子序列。 否则,产生一个项目。
通过> pip install more_itertools
安装此库。
选项3 - 使用more_itertools.replace
:
此工具用替换值替换谓词中定义的项的子序列。 要删除项目,我们替换空容器,例如substitutes=[]
。 替换项的长度由window_size
参数指定(该值等于子序列的长度)。
比以上任何内容更具可读性且无额外内存占用:
def remove_sublist(sublist, mainlist):
cursor = 0
for b in mainlist:
if cursor == len(sublist):
cursor = 0
if b == sublist[cursor]:
cursor += 1
else:
cursor = 0
yield b
for i in range(0, cursor):
yield sublist[i]
如果你想从库中获得一个函数,那就是在线工具,就这样吧
[x for x in remove_sublist([1, 2], [2, 1, 2, 3, 1, 2, 4])]
在Python 2.x中有点不同的方法!
from more_itertools import locate, windowed
big_list = [1, 2, 1, 2, 1]
sub_list = [1, 2, 1]
"""
Fetching all starting point of indexes (of sub_list in big_list)
to be removed from big_list.
"""
i = list(locate(windowed(big_list, len(sub_list)), pred=lambda x: x==tuple(sub_list)))
"""
Here i comes out to be [0, 2] in above case. But index from 2 which
includes 1, 2, 1 has last 1 from the 1st half of 1, 2, 1 so further code is
to handle this case.
PS: this won't come for-
big_list = [2, 1, 2, 3, 1, 2, 4]
sub_list = [1, 2]
as here i comes out to be [1, 4]
"""
# The further code.
to_pop = []
for ele in i:
if to_pop:
if ele == to_pop[-1]:
continue
to_pop.extend(range(ele, ele+len(sub_list)))
# Voila! to_pop consists of all the indexes to be removed from big_list.
# Wiping out the elements!
for index in sorted(to_pop, reverse=True):
del big_list[index]
请注意,您需要以相反的顺序删除它们,以免丢弃后续索引。
在Python3中, locate()的签名会有所不同。
(有关最终方法,请参阅最后一个代码段)
我原以为简单的字符串转换就足够了:
big_list = [2, 1, 2, 3, 1, 2, 4]
sub_list = [1, 2]
new_list = list(map(int, list((''.join(map(str, big_list))).replace((''.join(map(str, sub_list))), ''))))
我基本上使用列表的字符串等效进行查找/替换。 我之后将它们映射到整数,以便保留原始类型的变量。 这适用于任何大小的子列表和子列表。
但是,如果你在没有文本表示的情况下在任意对象上调用它,那么这可能不会起作用。 此外,该方法仅导致保留对象的文本版本; 如果需要维护原始数据类型,则会出现问题。
为此,我用不同的方法编写了一个解决方案:
new_list = []
i = 0
while new_list != big_list:
if big_list[i:i+len(sub_list)] == sub_list:
del big_list[i:i+len(sub_list)]
else:
new_list.append(big_list[i])
i += 1
基本上,当我找到它们时,我将删除sub_list的每个副本,并在找到不属于重复项的元素时附加到new_list。 当new_list和big_list相等时,找到了所有重复项,这就是我停止的时候。 我没有使用过try-except,因为我认为不应该有任何索引错误。
这类似于@ MadPhysicist的答案,效率大致相同,但我的内存消耗更少 。
第二种方法适用于任何类型的具有任何大小的列表的对象,因此比第一种方法更灵活。 但是,如果列表只是整数,第一种方法会更快。
但是,我还没有完成! 我编造了一个单行列表理解,它具有与第二种方法相同的功能!
import itertools
new_list = [big_list[j] for j in range(len(big_list)) if j not in list(itertools.chain.from_iterable([ list(range(i, i+len(sub_list))) for i in [i for i, x in enumerate(big_list) if x == sub_list[0]] if big_list[i:i+len(sub_list)] == sub_list ]))]
最初,这似乎令人生畏,但我向你保证这很简单! 首先,我创建了子列表的第一个元素发生的索引列表。 接下来,对于每个索引,我检查以下元素是否构成子列表。 如果是这样,则构成子列表副本的索引范围将添加到另一个列表中。 之后,我使用itertools中的函数来展平结果列表。 此展平列表中的每个元素都是与子列表重复的索引。 最后,我创建了一个new_list,它包含big_list的每个元素,它们在展平列表中找不到索引。
我不认为这种方法在任何其他答案中。 我最喜欢它,因为它很清楚,一旦你意识到它是如何工作的并且非常有效 (由于列表理解的性质)。
您可以使用生成器的递归:
def remove(d, sub_list):
if d[:len(sub_list)] == sub_list and len(sub_list) <= len(d[:len(sub_list)]):
yield from [[], remove(d[len(sub_list):], sub_list)][bool(d[len(sub_list):])]
else:
yield d[0]
yield from [[], remove(d[1:], sub_list)][bool(d[1:])]
tests = [[[2, 1, 2, 3, 1, 2, 4], [1, 2]], [[1, 2, 1, 2], [1, 2]], [[1, 'a', int, 3, float, 'a', int, 5], ['a', int]], [[1, 1, 1, 1, 1], [1,1,1]]]
for a, b in tests:
print(list(remove(a, b)))
输出:
[2, 3, 4]
[]
[1, 3, <class 'float'>, 5]
[1, 1]
只是为了好玩,这里是最接近单线的近似值:
from functools import reduce
big_list = [2, 1, 2, 3, 1, 2, 4]
sub_list = [1, 2]
result = reduce(lambda r, x: r[:1]+([1]+r[2:-r[1]],[min(len(r[0]),r[1]+1)]+r[2:])[r[-r[1]:]!=r[0]]+[x], big_list+[0], [sub_list, 1])[2:-1]
不相信它有效吗? 在IDEone上查看它!
当然,它远没有效率,而是令人厌恶的神秘,但它应该有助于说服OP接受@Mad Physicist的回答 。
您要实现的目标可以通过将其转换为字符串列表来完成,并在替换后再次将其转换为整数类型。
在一行中你可以这样做
map(int,list(("".join(map(str, big_list))).replace("".join(map(str, sub_list)),'').replace(''.join((map(str, sub_list))[::-1]),'')))
输入
big_list = [1, 2, 1, 2, 1]
sub_list = [1, 2, 1]
产量
[2,1]
输入
big_list = [2, 1, 2, 3, 1, 2, 4]
sub_list = [1, 2]
输出继电器
[2,3,4]
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.