Как удалить каждое вхождение sub-list из списка
у меня есть два списка:
big_list = [2, 1, 2, 3, 1, 2, 4]
sub_list = [1, 2]
Я хочу удалить все вхождения sub_list в big_list.
результат должен быть [2, 3, 4]
для строк вы можете использовать это:
'2123124'.replace('12', '')
но AFAIK это не работает для списков.
это не дубликат удаление подсписка из списка так как я хочу удалить все под-списки из большого списка. В другом вопросе результат должен быть [5,6,7,1,2,3,4]
.
Update: для простоты я взял целые числа в этом примере. Но элементы списка могут быть любые объекты.
обновление 2:
если big_list = [1, 2, 1, 2, 1]
и sub_list = [1, 2, 1]
,
Я хочу, чтобы результат был [2, 1]
(типа '12121'.replace('121', '')
)
Update3:
мне не нравится копировать + вставлять исходный код из StackOverflow в мой код. Вот почему я создал второй вопрос в software-recommendations: 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]))
14 ответов
вам придется реализовать его самостоятельно. Вот основная идея:
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 link демонстрируя результат
>>> 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)
обновление
в моем первом решении была ошибка. Смог исправить это (обновил мой код выше), но теперь метод выглядит более сложным. С точки зрения производительности он по-прежнему делает лучше, чем решение от Сумасшедший Физик на моем локальном компьютере.
использовать itertools.zip_longest
чтобы создать N кортежей элементов (где n-длина вложенного списка), а затем отфильтровать текущий элемент и следующие 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
, например,def remove(seq, subsequence):
.
Вариант 2 - с more_itertools
индексы расположены для каждой соответствующей под-последовательности в iterable. При итерации перечисленного итератора, если индекс найден в 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]
Это для onliner если вы хотите функцию из библиотеки, пусть это будет
[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))), ''))))
Я по существу делаю поиск / замену строковыми эквивалентами списков. Впоследствии я сопоставляю их с целыми числами, чтобы сохранить исходные типы переменных. Это будет работать для любого размера списков big и sub.
однако, вполне вероятно, что это не сработает, если вы вызываете его произвольные объекты, если они не имеют текстового представления. Кроме того, этот метод приводит к сохранению только текстовой версии объектов; это проблема, если исходные типы данных должны поддерживаться.
для этого я составил решение с другим подходом:
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 равны, все дубликаты были найдены, когда я останавливаюсь. Я не использовал попытку-за исключением того, что я не думаю, что должны быть какие-либо ошибки индексирования.
Это похоже на ответ @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's answer.
то, что вы пытаетесь достичь, можно сделать, преобразовав его в список строк и после замены снова преобразовать его в целочисленный тип.
в одной строке вы можете сделать это так
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]
как можно компактнее:
a=[]
for i in range(0,len(big_list)):
if big_list[i]==1 and big_list[i+1]==2:
a.extend((i,i+1))
[big_list[x] for x in [x for x in range(len(big_list)) if x not in a]]
Out[101]: [2, 3, 4]
тот же подход в R:
big_list <- c(2, 1, 2, 3, 1, 2, 4)
sub_list <- c(1,2)
a<-c()
for(i in 1:(length(big_list)-1)){if (all(big_list[c(i,i+1)]==sub_list)) a<-c(a,c(i,i+1)) }
big_list[-a]
[1] 2 3 4
Я думаю, что R проще.
[big_list[x] for x in [x for x in range(len(big_list)) if x not in a]]
эквивалентно big_list[-a]