Supprimer plusieurs éléments d'une liste


160

Est-il possible de supprimer plusieurs éléments d'une liste en même temps? Si je veux supprimer des éléments aux index 0 et 2, et essayer quelque chose comme del somelist[0], suivi de del somelist[2], la deuxième instruction supprimera réellement somelist[3].

Je suppose que je pourrais toujours supprimer les éléments les plus numérotés en premier, mais j'espère qu'il existe un meilleur moyen.

Réponses:


110

Vous pouvez utiliser enumerateet supprimer les valeurs dont l'index correspond aux index que vous souhaitez supprimer:

indices = 0, 2
somelist = [i for j, i in enumerate(somelist) if j not in indices]

2
Presque, seulement si vous supprimez la liste entière. ce sera len (indices) * len (somelist). Il crée également une copie, qui peut ou non être souhaitée
Richard Levasseur

si vous recherchez une valeur dans une liste, c'est le cas. l'opérateur "in" travaille sur les valeurs d'une liste, alors qu'il travaille sur les clés d'un dict. Si je me trompe, veuillez m'indiquer le pep / référence
Richard Levasseur

5
la raison pour laquelle j'ai choisi le tuple pour les indices n'était que la simplicité de l'enregistrement. ce serait un travail parfait pour set () donnant O (n)
SilentGhost

18
Il ne s'agit pas du tout de supprimer des éléments d'une liste, mais plutôt de créer une toute nouvelle liste. Si quelque chose contient une référence à la liste d'origine, il contiendra toujours tous les éléments.
Tom Future du

2
@SilentGhost Pas nécessaire de faire une énumération. Que diriez-vous de ceci somelist = [ lst[i] for i in xrange(len(lst)) if i not in set(indices) ]:?
ToolmakerSteve

183

Pour une raison quelconque, je n'aime aucune des réponses ici. Oui, ils fonctionnent, mais à proprement parler, la plupart d'entre eux ne suppriment pas des éléments d'une liste, n'est-ce pas? (Mais faire une copie puis remplacer l'original par la copie éditée).

Pourquoi ne pas simplement supprimer d'abord l'index le plus élevé?

Y a-t-il une raison à cela? Je ferais juste:

for i in sorted(indices, reverse=True):
    del somelist[i]

Si vous ne voulez vraiment pas supprimer les éléments à l'envers, alors je suppose que vous devriez simplement déincrémenter les valeurs d'index qui sont supérieures au dernier index supprimé (vous ne pouvez pas vraiment utiliser le même index puisque vous avez une liste différente) ou utiliser une copie de la liste (qui ne serait pas «supprimer» mais remplacer l'original par une copie modifiée).

Est-ce que je manque quelque chose ici, une raison de NE PAS supprimer dans l'ordre inverse?


1
Je ne sais pas pourquoi cela n'a pas été choisi comme réponse acceptée !. Merci pour cela.
swathis

4
Il y a deux raisons. (a) Pour une liste, la complexité temporelle serait plus élevée que la méthode "faire une copie" (utilisant un ensemble d'indices) en moyenne (en supposant des indices aléatoires) parce que certains éléments doivent être décalés plusieurs fois vers l'avant. (b) Au moins pour moi, c'est difficile à lire, car il existe une fonction de tri qui ne correspond à aucune logique de programme réelle et n'existe que pour des raisons techniques. Même si maintenant je comprends déjà parfaitement la logique, je pense toujours que ce serait difficile à lire.
Nuit impérissable du

1
@ImperishableNightpouvez-vous élaborer (a)? Je ne comprends pas "certains éléments doivent être déplacés". Pour (b), vous pouvez simplement définir une fonction si vous avez besoin de clarté de lecture.
tglaria

109

Si vous supprimez plusieurs éléments non adjacents, ce que vous décrivez est le meilleur moyen (et oui, assurez-vous de partir de l'index le plus élevé).

Si vos éléments sont adjacents, vous pouvez utiliser la syntaxe d'affectation de tranche:

a[2:10] = []

95
Vous pouvez également dire del a[2:10]avec le même effet.
STH

8
@sth Fait intéressant, del est un peu plus rapide que l'attribution.
thefourtheye

24

Vous pouvez utiliser numpy.deletecomme suit:

import numpy as np
a = ['a', 'l', 3.14, 42, 'u']
I = [0, 2]
np.delete(a, I).tolist()
# Returns: ['l', '42', 'u']

Si cela ne vous dérange pas de finir avec un numpytableau à la fin, vous pouvez omettre le fichier .tolist(). Vous devriez également voir des améliorations de vitesse assez importantes, ce qui en fait une solution plus évolutive. Je ne l'ai pas comparé, mais les numpyopérations sont du code compilé écrit en C ou en Fortran.


1
solution générale lorsque les éléments ne sont pas consécutifs +1
noɥʇʎԀʎzɐɹƆ

1
question ici, que diriez-vous de supprimer ['a', 42].
evanhutomo

ÉNORMES points bonus pour cette solution, par rapport aux autres, pour la vitesse. Ce que je peux dire, c'est que pour un très grand ensemble de données, il me fallait plusieurs minutes pour réaliser quelque chose qui ne prenait que quelques secondes avec un bon vieux numpy.
legel

18

En tant que spécialisation de la réponse de Greg, vous pouvez même utiliser une syntaxe de tranche étendue. par exemple. Si vous souhaitez supprimer les éléments 0 et 2:

>>> a= [0, 1, 2, 3, 4]
>>> del a[0:3:2]
>>> a
[1, 3, 4]

Cela ne couvre aucune sélection arbitraire, bien sûr, mais cela peut certainement fonctionner pour supprimer deux éléments.


16

En tant que fonction:

def multi_delete(list_, *args):
    indexes = sorted(list(args), reverse=True)
    for index in indexes:
        del list_[index]
    return list_

S'exécute en n log (n) temps, ce qui devrait en faire la solution correcte la plus rapide à ce jour.


1
La version avec args.sort (). Reverse () est nettement meilleure. Cela arrive aussi à travailler avec des dictons au lieu de lancer ou, pire, de corrompre silencieusement.

sort () n'est pas défini pour le tuple, vous devez d'abord convertir en liste. sort () renvoie None, vous ne pouvez donc pas utiliser reverse () dessus.
SilentGhost

@ R. Pate: J'ai supprimé la première version pour cette raison. Merci. @ SilentGhost: Correction du problème.
Nikhil Chelliah

@Nikhil: non, vous n'avez pas;) args = list (args) args.sort () args.reverse () mais la meilleure option serait: args = sorted (args, reverse = True)
SilentGhost

2
n log n? Vraiment? Je ne pense pas que ce del list[index]soit O (1).
user202729

12

Alors, vous souhaitez essentiellement supprimer plusieurs éléments en un seul passage? Dans ce cas, la position de l'élément suivant à supprimer sera compensée par le nombre de suppressions précédentes.

Notre objectif est de supprimer toutes les voyelles, qui sont précalculées pour être les indices 1, 4 et 7. Notez que son important les index to_delete sont dans l'ordre croissant, sinon cela ne fonctionnera pas.

to_delete = [1, 4, 7]
target = list("hello world")
for offset, index in enumerate(to_delete):
  index -= offset
  del target[index]

Ce serait plus compliqué si vous vouliez supprimer les éléments dans n'importe quel ordre. OMI, le tri to_deletepourrait être plus facile que de déterminer quand vous devriez ou ne devriez pas soustraire index.


8

Je suis un débutant total en Python, et ma programmation pour le moment est grossière et sale pour le moins, mais ma solution était d'utiliser une combinaison des commandes de base que j'ai apprises dans les premiers tutoriels:

some_list = [1,2,3,4,5,6,7,8,10]
rem = [0,5,7]

for i in rem:
    some_list[i] = '!' # mark for deletion

for i in range(0, some_list.count('!')):
    some_list.remove('!') # remove
print some_list

De toute évidence, en raison du choix d'un caractère "marque pour suppression", cela a ses limites.

En ce qui concerne les performances à mesure que la taille de la liste évolue, je suis sûr que ma solution est sous-optimale. Cependant, c'est simple, ce qui j'espère plaira à d'autres débutants, et fonctionnera dans des cas simples où some_listest d'un format bien connu, par exemple, toujours numérique ...


2
à la place d'utiliser '!' comme caractère spécial, utilisez Aucun. Cela garde chaque personnage valide et libère vos possibilités
portforwardpodcast

5

Voici une alternative, qui n'utilise pas enumerate () pour créer des tuples (comme dans la réponse originale de SilentGhost).

Cela me semble plus lisible. (Peut-être que je me sentirais différemment si j'avais l'habitude d'utiliser enumerate.) CAVEAT: Je n'ai pas testé les performances des deux approches.

# Returns a new list. "lst" is not modified.
def delete_by_indices(lst, indices):
    indices_as_set = set(indices)
    return [ lst[i] for i in xrange(len(lst)) if i not in indices_as_set ]

REMARQUE: syntaxe Python 2.7. Pour Python 3, xrange=> range.

Usage:

lst = [ 11*x for x in xrange(10) ]
somelist = delete_by_indices( lst, [0, 4, 5])

une liste:

[11, 22, 33, 66, 77, 88, 99]

--- PRIME ---

Supprimez plusieurs valeurs d'une liste. Autrement dit, nous avons les valeurs que nous voulons supprimer:

# Returns a new list. "lst" is not modified.
def delete__by_values(lst, values):
    values_as_set = set(values)
    return [ x for x in lst if x not in values_as_set ]

Usage:

somelist = delete__by_values( lst, [0, 44, 55] )

une liste:

[11, 22, 33, 66, 77, 88, 99]

C'est la même réponse que précédemment, mais cette fois nous avons fourni les VALEURS à supprimer [0, 44, 55].


J'ai décidé que @ SilentGhost était seulement difficile à lire, à cause des noms de variables non descriptifs utilisés pour le résultat de l'énumération. De plus, les parenthèses auraient facilité la lecture. Voici donc comment je mot sa solution (avec « ensemble » , ajoute, pour la performance): [ value for (i, value) in enumerate(lst) if i not in set(indices) ]. Mais je vais laisser ma réponse ici, car je montre également comment supprimer par valeurs. Ce qui est un cas plus facile, mais pourrait aider quelqu'un.
ToolmakerSteve

@ Veedrac- merci; J'ai réécrit pour construire le set en premier. Que pensez-vous - solution plus rapide maintenant que SilentGhost? (Je ne considère pas assez important pour réellement le temps , il suffit de demander votre avis.) De même, je voudrais réécrire la version de SilentGhost que indices_as_set = set(indices), [ value for (i, value) in enumerate(lst) if i not in indices_as_set ]pour l' accélérer.
ToolmakerSteve

Y a-t-il une raison de style pour le double soulignement delete__by_values()?
Tom

5

Une méthode alternative de compréhension de liste qui utilise des valeurs d'index de liste:

stuff = ['a', 'b', 'c', 'd', 'e', 'f', 'woof']
index = [0, 3, 6]
new = [i for i in stuff if stuff.index(i) not in index]

Cela renvoie:

['b', 'c', 'e', 'f']

bonne réponse, mais nommer la liste des index comme indexétant trompeur car dans l'itérateur de la liste est utilisée la méthodeindex()
Joe

4

voici une autre méthode qui supprime les éléments en place. aussi si votre liste est vraiment longue, elle est plus rapide.

>>> a = range(10)
>>> remove = [0,4,5]
>>> from collections import deque
>>> deque((list.pop(a, i) for i in sorted(remove, reverse=True)), maxlen=0)

>>> timeit.timeit('[i for j, i in enumerate(a) if j not in remove]', setup='import random;remove=[random.randrange(100000) for i in range(100)]; a = range(100000)', number=1)
0.1704120635986328

>>> timeit.timeit('deque((list.pop(a, i) for i in sorted(remove, reverse=True)), maxlen=0)', setup='from collections import deque;import random;remove=[random.randrange(100000) for i in range(100)]; a = range(100000)', number=1)
0.004853963851928711

+1: Utilisation intéressante de deque pour effectuer une action for dans le cadre d'une expression, plutôt que d'exiger un bloc "for ..:". Cependant, pour ce cas simple, je trouve Nikhil pour bloc plus lisible.
ToolmakerSteve

4

Cela a été mentionné, mais personne n'a réussi à faire les choses correctement.

La O(n)solution serait:

indices = {0, 2}
somelist = [i for j, i in enumerate(somelist) if j not in indices]

C'est vraiment proche de la version de SilentGhost , mais ajoute deux accolades.


Ce n'est pas le O(n)cas si vous comptez les recherches effectuées log(len(indices))pour chaque itération.
Mad Physicist

@MadPhysicist j not in indicesest O(1).
Veedrac

Je ne sais pas comment vous obtenez ce chiffre. Puisque les indices est un ensemble, j not in indicesnécessite toujours une recherche, ce qui est O(log(len(indices))). Bien que je convienne qu'une recherche dans un ensemble de 2 éléments se qualifie comme O(1), dans le cas général, ce sera le cas O(log(N)). De toute façon O(N log(N))bat toujours O(N^2).
Mad Physicist


Et que faisaient exactement deux broches?
Nuclear03020704

4
l = ['a','b','a','c','a','d']
to_remove = [1, 3]
[l[i] for i in range(0, len(l)) if i not in to_remove])

C'est fondamentalement la même chose que la réponse la plus votée, juste une manière différente de l'écrire. Notez que l'utilisation de l.index () n'est pas une bonne idée, car elle ne peut pas gérer les éléments dupliqués dans une liste.


2

La méthode Remove entraînera beaucoup de décalage des éléments de la liste. Je pense qu'il vaut mieux faire une copie:

...
new_list = []
for el in obj.my_list:
   if condition_is_true(el):
      new_list.append(el)
del obj.my_list
obj.my_list = new_list
...

2

techniquement, la réponse est NON, il n'est pas possible de supprimer deux objets EN MÊME TEMPS. Cependant, il est possible de supprimer deux objets en une seule ligne de beau python.

del (foo['bar'],foo['baz'])

supprimera récursivement foo['bar'], puisfoo['baz']


Cela supprime un objet dict, pas une liste, mais je suis toujours en train de le + 1 car c'est vraiment joli!
Ulf Aslak

Cela s'applique également à la liste, avec une syntaxe appropriée. Cependant, l'affirmation est qu'il n'est pas possible de supprimer deux objets en même temps est fausse; voir la réponse de @bobince
Pedro Gimeno

2

nous pouvons le faire en utilisant une boucle for itérant sur les index après avoir trié la liste d'index dans l'ordre décroissant

mylist=[66.25, 333, 1, 4, 6, 7, 8, 56, 8769, 65]
indexes = 4,6
indexes = sorted(indexes, reverse=True)
for i in index:
    mylist.pop(i)
print mylist

2

Pour les indices 0 et 2 de la listeA:

for x in (2,0): listA.pop(x)

Pour certains indices aléatoires à supprimer de la listeA:

indices=(5,3,2,7,0) 
for x in sorted(indices)[::-1]: listA.pop(x)

2

Je voulais un moyen de comparer les différentes solutions qui permettaient de tourner facilement les boutons.

J'ai d'abord généré mes données:

import random

N = 16 * 1024
x = range(N)
random.shuffle(x)
y = random.sample(range(N), N / 10)

Puis j'ai défini mes fonctions:

def list_set(value_list, index_list):
    index_list = set(index_list)
    result = [value for index, value in enumerate(value_list) if index not in index_list]
    return result

def list_del(value_list, index_list):
    for index in sorted(index_list, reverse=True):
        del(value_list[index])

def list_pop(value_list, index_list):
    for index in sorted(index_list, reverse=True):
        value_list.pop(index)

Ensuite, je timeitcomparais les solutions:

import timeit
from collections import OrderedDict

M = 1000
setup = 'from __main__ import x, y, list_set, list_del, list_pop'
statement_dict = OrderedDict([
    ('overhead',  'a = x[:]'),
    ('set', 'a = x[:]; list_set(a, y)'),
    ('del', 'a = x[:]; list_del(a, y)'),
    ('pop', 'a = x[:]; list_pop(a, y)'),
])

overhead = None
result_dict = OrderedDict()
for name, statement in statement_dict.iteritems():
    result = timeit.timeit(statement, number=M, setup=setup)
    if overhead is None:
        overhead = result
    else:
        result = result - overhead
        result_dict[name] = result

for name, result in result_dict.iteritems():
    print "%s = %7.3f" % (name, result)

Production

set =   1.711
del =   3.450
pop =   3.618

Donc, le générateur avec les indices dans a setétait le gagnant. Et delc'est légèrement plus rapide alors pop.


Merci pour cette comparaison, cela m'a conduit à faire mes propres tests (en fait vient d'emprunter votre code) et pour un petit nombre d'éléments à supprimer, la surcharge pour créer un SET en fait la pire solution (utilisez 10, 100, 500 pour le longueur de «y» et vous verrez). Comme la plupart du temps, cela dépend de l'application.
tglaria

2

Vous pouvez utiliser cette logique:

my_list = ['word','yes','no','nice']

c=[b for i,b in enumerate(my_list) if not i in (0,2,3)]

print c

2

Une autre implémentation de l'idée de retirer de l'indice le plus élevé.

for i in range(len(yourlist)-1, -1, -1):
    del yourlist(i)

1

Je peux en fait penser à deux façons de le faire:

  1. trancher la liste comme (cela supprime les 1er, 3ème et 8ème éléments)

    somelist = somelist [1: 2] + somelist [3: 7] + somelist [8:]

  2. faites-le en place, mais un à la fois:

    somelist.pop (2) somelist.pop (0)


1

Vous pouvez le faire sur un dict, pas sur une liste. Dans une liste, les éléments sont en séquence. Dans un dict, ils ne dépendent que de l'index.

Code simple juste pour l'expliquer en faisant :

>>> lst = ['a','b','c']
>>> dct = {0: 'a', 1: 'b', 2:'c'}
>>> lst[0]
'a'
>>> dct[0]
'a'
>>> del lst[0]
>>> del dct[0]
>>> lst[0]
'b'
>>> dct[0]
Traceback (most recent call last):
  File "<pyshell#19>", line 1, in <module>
    dct[0]
KeyError: 0
>>> dct[1]
'b'
>>> lst[1]
'c'

Une façon de "convertir" une liste dans un dict est:

>>> dct = {}
>>> for i in xrange(0,len(lst)): dct[i] = lst[i]

L'inverse est:

lst = [dct[i] for i in sorted(dct.keys())] 

Quoi qu'il en soit, je pense qu'il vaut mieux commencer à supprimer de l'index supérieur comme vous l'avez dit.


Est-ce que Python garantit que [dct [i] for i in dct] utilisera toujours des valeurs croissantes de i? Si oui, list (dct.values ​​()) est sûrement mieux.

Je n'y pensais pas. Vous avez raison. Il n'y a pas de garantie que j'ai lu [ici] [1] que les articles seront choisis dans l'ordre, ou du moins dans l'ordre prévu. J'ai édité. [1]: docs.python.org/library/stdtypes.html#dict.items
Andrea Ambu

2
Cette réponse parle des dictionnaires d'une manière fondamentalement erronée. Un dictionnaire a des CLÉS (pas des INDICES). Oui, les paires clé / valeur sont indépendantes les unes des autres. Non, peu importe l'ordre dans lequel vous supprimez les entrées. La conversion en dictionnaire juste pour supprimer certains éléments d'une liste serait exagérée.
ToolmakerSteve

1

Pour généraliser le commentaire de @sth . La suppression d'élément dans n'importe quelle classe, qui implémente abc.MutableSequence , et listen particulier, se fait via __delitem__la méthode magique. Cette méthode fonctionne de la même manière que __getitem__, ce qui signifie qu'elle peut accepter un entier ou une tranche. Voici un exemple:

class MyList(list):
    def __delitem__(self, item):
        if isinstance(item, slice):
            for i in range(*item.indices(len(self))):
                self[i] = 'null'
        else:
            self[item] = 'null'


l = MyList(range(10))
print(l)
del l[5:8]
print(l)

Cela produira

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 'null', 'null', 'null', 8, 9]

1

L'importer uniquement pour cette raison peut être excessif, mais si vous l'utilisez de pandastoute façon, la solution est simple et directe:

import pandas as pd
stuff = pd.Series(['a','b','a','c','a','d'])
less_stuff = stuff[stuff != 'a']  # define any condition here
# results ['b','c','d']

1
some_list.remove(some_list[max(i, j)])

Évite les frais de tri et la copie explicite de la liste


0

Que diriez-vous de l'un d'entre eux (je suis très nouveau dans Python, mais ils semblent ok):

ocean_basin = ['a', 'Atlantic', 'Pacific', 'Indian', 'a', 'a', 'a']
for i in range(1, (ocean_basin.count('a') + 1)):
    ocean_basin.remove('a')
print(ocean_basin)

["Atlantique", "Pacifique", "Indien"]

ob = ['a', 'b', 4, 5,'Atlantic', 'Pacific', 'Indian', 'a', 'a', 4, 'a']
remove = ('a', 'b', 4, 5)
ob = [i for i in ob if i not in (remove)]
print(ob)

["Atlantique", "Pacifique", "Indien"]


0

Aucune des réponses proposées jusqu'à présent n'effectue la suppression en place en O (n) sur la longueur de la liste pour un nombre arbitraire d'indices à supprimer, voici donc ma version:

def multi_delete(the_list, indices):
    assert type(indices) in {set, frozenset}, "indices must be a set or frozenset"
    offset = 0
    for i in range(len(the_list)):
        if i in indices:
            offset += 1
        elif offset:
            the_list[i - offset] = the_list[i]
    if offset:
        del the_list[-offset:]

# Example:
a = [0, 1, 2, 3, 4, 5, 6, 7]
multi_delete(a, {1, 2, 4, 6, 7})
print(a)  # prints [0, 3, 5]

0

Vous pouvez également utiliser remove.

delete_from_somelist = []
for i in [int(0), int(2)]:
     delete_from_somelist.append(somelist[i])
for j in delete_from_somelist:
     newlist = somelist.remove(j)

0

Je mets tout cela ensemble dans une list_difffonction qui prend simplement deux listes comme entrées et renvoie leur différence, tout en préservant l'ordre d'origine de la première liste.

def list_diff(list_a, list_b, verbose=False):

    # returns a difference of list_a and list_b,
    # preserving the original order, unlike set-based solutions

    # get indices of elements to be excluded from list_a
    excl_ind = [i for i, x in enumerate(list_a) if x in list_b]
    if verbose:
        print(excl_ind)

    # filter out the excluded indices, producing a new list 
    new_list = [i for i in list_a if list_a.index(i) not in excl_ind]
    if verbose:
        print(new_list)

    return(new_list)

Exemple d'utilisation:

my_list = ['a', 'b', 'c', 'd', 'e', 'f', 'woof']
# index = [0, 3, 6]

# define excluded names list
excl_names_list = ['woof', 'c']

list_diff(my_list, excl_names_list)
>> ['a', 'b', 'd', 'e', 'f']
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.