web-dev-qa-db-fra.com

Comment supprimer chaque occurrence de sous-liste de la liste

J'ai deux listes:

big_list = [2, 1, 2, 3, 1, 2, 4]
sub_list = [1, 2]

Je veux supprimer toutes les occurrences de sous-liste dans big_list.

le résultat devrait être [2, 3, 4]

Pour les chaînes, vous pouvez utiliser ceci:

'2123124'.replace('12', '')

Mais autant que je sache, cela ne fonctionne pas pour les listes.

Ce n'est pas une copie de Supprimer une sous-liste d'une liste puisque je veux supprimer toutes les sous-listes de la grande liste. Dans l'autre question, le résultat devrait être [5,6,7,1,2,3,4].

Mise à jour: pour simplifier, j'ai pris des nombres entiers dans cet exemple. Mais les éléments de la liste peuvent être des objets arbitraires.

Update2:

si big_list = [1, 2, 1, 2, 1] et sub_list = [1, 2, 1], Je veux que le résultat soit [2, 1] (comme '12121'.replace('121', ''))

Update3:

Je n'aime pas copier + coller le code source de StackOverflow dans mon code. C'est pourquoi j'ai créé la deuxième question à l'adresse suivante: software/software: https://softwarerecs.stackexchange.com/questions/51273/library-to-remove-every-occurrence-of-sub-list-from-list-python

Update4: si vous connaissez une bibliothèque pour appeler cette méthode, écrivez-la comme réponse, car c'est ma solution préférée. 

Le test devrait réussir ce test:

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]))
45
guettli

Vous devrez le mettre en œuvre vous-même. Voici l'idée de base:

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

Cela suit chaque élément de la liste d'origine et l'ajoute à une liste de sortie s'il ne fait pas partie du sous-ensemble. Cette version n’est pas très efficace, mais elle fonctionne comme l’exemple de chaîne que vous avez fourni, dans le sens où elle crée une nouvelle liste ne contenant pas votre sous-ensemble. Cela fonctionne aussi pour les types d'éléments arbitraires tant qu'ils supportent ==. Supprimer [1,1,1] de [1,1,1,1] donnera correctement [1], comme pour une chaîne.

Voici un lien IDEOne montrant le résultat de

>>> remove_sublist([1, 'a', int, 3, float, 'a', int, 5], ['a', int])
[1, 3, <class 'float'>, 5]
25
Mad Physicist

Essayez del et slicing. La pire complexité temporelle est 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)

résultat:

[1, 3, <class 'float'>, 5]
14
Marcus.Aurelianus

Une approche récursive:

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))

Cela génère:

[2, 3, 4]
8
blhsing

Une version améliorée pour vérifier si 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
6
mingganz

Que dis-tu de ça:

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

Performance:

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)

Mettre à jour

Ma première solution avait un bug. A été capable de le réparer (mis à jour mon code ci-dessus) mais la méthode semble beaucoup plus compliquée maintenant. En termes de performances, il fait toujours mieux que la solution de Mad Physicist sur mon ordinateur local.

6
RandomDude

Utilisez itertools.Zip_longest pour créer n tuples d'éléments (où n est la longueur de la sous-liste), puis filtrez l'élément en cours et les n-1 éléments suivants lorsqu'un élément correspond à la sous-liste.

>>> 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]

Pour améliorer l'efficacité, vous pouvez calculer Tuple(sub_list) et len(sub_list) avant de commencer à filtrer

>>> 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]
5
Sunitha

Update: la bibliothèque more_itertools a publié more_itertool.replace , un outil qui résout ce problème (voir option 3). 

Premièrement, voici quelques autres options qui fonctionnent sur les itérables génériques (listes, chaînes, itérateurs, etc.):

Code

Option 1 - sans bibliothèques:

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   

Option 2 - avec 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

Démo 

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']

Option 3 - avec more_itertools.replace :

Démo

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']

Détails

Dans tous ces exemples, nous analysons la séquence principale avec des tranches de fenêtre plus petites. Nous cédons tout ce qui ne se trouve pas dans la tranche et ignorons ce qui se trouve dans la tranche.

Option 1 - sans bibliothèques

Itérer une séquence énumérée et évaluer des tranches de taille n (la longueur de la sous-séquence). Si la tranche à venir est égale à la sous-séquence, réinitialisez skip et renvoyez l'élément. Sinon, parcourez-le. skip indique combien de fois faire avancer la boucle, par ex. sublist est de taille n=2, donc il saute deux fois par match.

Notez que vous pouvez convertir cette option afin qu'elle fonctionne avec séquences seul en supprimant les deux premières affectations de Tuple et en remplaçant le paramètre iterable par seq, par exemple. def remove(seq, subsequence):.

Option 2 - avec more_itertools

Les indices sont situés pour chaque sous-séquence correspondante dans un itérable. Lors de l'itération d'un itérateur énuméré, si un index est trouvé dans indices, la sous-séquence restante est ignorée en consommant les éléments n-1 suivants de l'itérateur. Sinon, un article est cédé.

Installez cette bibliothèque via > pip install more_itertools.

Option 3 - avec more_itertools.replace :

Cet outil remplace une sous-séquence d'éléments définis dans un prédicat par des valeurs de substitution. Pour supprimer des éléments, nous substituons un conteneur vide, par exemple. substitutes=[]. La longueur des éléments remplacés est spécifiée par le paramètre window_size (cette valeur est égale à la longueur de la sous-séquence). 

5
pylang

Plus lisible que tout ce qui précède et sans aucune empreinte mémoire supplémentaire:

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]

Ceci est pour onliner si vous vouliez une fonction de la bibliothèque, que ce soit ceci

[x for x in remove_sublist([1, 2], [2, 1, 2, 3, 1, 2, 4])]
4
Dmitry Dyachkov

Une approche un peu différente dans 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]

Notez que vous devez les supprimer dans l'ordre inverse pour ne pas gommer les index suivants.

En Python3, la signature de Locate () sera différente.

3
user5319825

(Pour l'approche finale, voir le dernier extrait de code)

J'aurais pensé qu'une simple conversion de chaîne suffirait:

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))), ''))))

Je fais essentiellement une recherche/remplacement avec les équivalents chaîne des listes. Je les mappe ensuite sur des entiers afin que les types d'origine des variables soient conservés. Cela fonctionnera pour toutes les tailles de grandes et de petites listes.

Cependant, il est probable que cela ne fonctionnera pas si vous l'appelez sur des objets arbitraires s'ils n'ont pas de représentation textuelle. De plus, cette méthode a pour résultat que seule la version textuelle des objets est conservée; c'est un problème si les types de données d'origine doivent être conservés.

Pour cela, j'ai composé une solution avec une approche différente:

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

Essentiellement, je supprime tous les doublons de la sous-liste lorsque je les trouve et les ajoute à la nouvelle liste lorsque je trouve un élément qui ne fait pas partie d'un duplicata. Lorsque new_list et big_list sont égales, tous les doublons ont été trouvés, c'est-à-dire quand je m'arrête. Je n'ai pas essayé, sauf que je ne pense pas qu'il devrait y avoir d'erreur d'indexation.

Ceci est similaire à la réponse de @ MadPhysicist et a à peu près la même efficacité, mais le mien consomme moins de mémoire .

Cette deuxième approche fonctionnera pour tout type d'objet avec n'importe quelle taille de liste et est donc beaucoup plus flexible que la première approche. Cependant, la première approche est plus rapide si vos listes ne sont que des entiers.

Cependant, je n'ai pas encore fini! J'ai concocté une compréhension d'une liste à une ligne qui a la même fonctionnalité que la deuxième approche!

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 ]))]

Au début, cela semble intimidant, mais je vous assure que c'est assez simple! Premièrement, je crée une liste des index où le premier élément de la sous-liste a eu lieu. Ensuite, pour chacun de ces index, je vérifie si les éléments suivants forment la sous-liste. Si tel est le cas, la plage d'index qui constitue le doublon de la sous-liste est ajoutée à une autre liste. Ensuite, j'utilise une fonction d'itertools pour aplatir la liste de listes obtenue. Chaque élément de cette liste aplatie est un index qui est un duplicata de la sous-liste. Enfin, je crée une new_list qui comprend tous les éléments de la big_list ayant un index non trouvé dans la liste aplatie.

Je ne pense pas que cette méthode est dans l'une des autres réponses. J'aime le plus, car c'est très chouette une fois que vous avez compris comment cela fonctionne et qui est très efficace (en raison de la nature de la compréhension des listes).

1
Adi219

Vous pouvez utiliser la récursivité avec un générateur:

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)))

Sortie:

[2, 3, 4]
[]
[1, 3, <class 'float'>, 5]
[1, 1]
0
Ajax1234

Ce que vous essayez d’atteindre peut être fait en le convertissant en liste de chaînes et après l’avoir remplacé, le convertir en type entier.

En une seule ligne, vous pouvez le faire comme ceci

map(int,list(("".join(map(str, big_list))).replace("".join(map(str, sub_list)),'').replace(''.join((map(str, sub_list))[::-1]),'')))

Contribution

big_list = [1, 2, 1, 2, 1]
sub_list = [1, 2, 1]

Sortie

[2, 1]

Contribution

big_list = [2, 1, 2, 3, 1, 2, 4]
sub_list = [1, 2]

Ouput

[2, 3, 4]

0
HimanshuGahlot

Juste pour le plaisir, voici l'approximation la plus proche d'un one-liner:

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]

Ne croyez pas que cela fonctionne? Vérifiez-le sur IDEone !

Bien sûr, il est loin d’être efficace et déformablement cryptique, mais cela devrait aider à convaincre le PO d’accepter la réponse de @Mad Physician .

0
Leon