Question

Cette question a déjà une réponse ici:

Quand je suis à l'écriture de code en Python, j'ai souvent besoin de supprimer des éléments à partir d'une liste ou d'un autre type de séquence en fonction de certains critères.Je n'ai pas trouvé une solution élégante et efficace, que de supprimer des éléments à partir d'une liste que vous sont actuellement à itérer est mauvais.Par exemple, vous ne pouvez pas faire ceci:

for name in names:
    if name[-5:] == 'Smith':
        names.remove(name)

J'ai l'habitude de faire quelque chose comme ceci:

toremove = []
for name in names:
    if name[-5:] == 'Smith':
        toremove.append(name)
for name in toremove:
    names.remove(name)
del toremove

C'est innefficient, assez laid et éventuellement buggy (comment fait-il gérer plusieurs "John Smith" entrées?).Quelqu'un aurait-il une solution plus élégante, ou au moins plus efficace?

Que diriez-vous celui qui fonctionne avec les dictionnaires?

Était-ce utile?

La solution

Deux façons de le faire juste le filtrage sont:

  1. À l'aide de filter:

    names = filter(lambda name: name[-5:] != "Smith", names)

  2. En utilisant des interprétations de la liste:

    names = [name for name in names if name[-5:] != "Smith"]

Notez que dans les deux cas garder les valeurs pour lesquelles la fonction de prédicat donne True, de sorte que vous devez inverser la logique (c'est à direvous dites "garder les gens qui n'ont pas le nom de famille Smith" au lieu de "supprimer les gens qui ont le nom de famille Smith").

Modifier Drôle...deux personnes individuellement affichées à la fois des réponses que j'ai proposé comme j'ai été poster le mien.

Autres conseils

Vous pouvez également faire une itération vers l'arrière sur la liste:

for name in reversed(names):
    if name[-5:] == 'Smith':
        names.remove(name)

Ceci a l'avantage de ne pas créer une nouvelle liste (comme filter ou une compréhension de liste) et utilise un itérateur au lieu d'une liste de copie (comme [:]).

Notez que bien que la suppression d'éléments lors de l'itération en arrière est sûr, en les insérant est un peu plus compliqué.

La réponse la plus évidente est celle que Jean et quelques autres personnes ont donné, à savoir:

>>> names = [name for name in names if name[-5:] != "Smith"]       # <-- slower

Mais qui a l'inconvénient qu'il crée une nouvelle liste d'objet, plutôt que de la réutilisation de l'objet d'origine.J'ai fait un peu de profilage et d'expérimentation, et la méthode la plus efficace, je suis venu avec est:

>>> names[:] = (name for name in names if name[-5:] != "Smith")    # <-- faster

L'attribution de noms "[:]" signifie "remplacer le contenu de la liste de noms avec la valeur suivante".C'est différent de la juste attribution des noms, en ce qu'elle ne veut pas créer une nouvelle liste d'objet.La partie droite de l'affectation est un générateur d'expression (notez l'utilisation de parenthèses, plutôt qu'entre crochets).Ce sera la cause de Python pour itérer à travers la liste.

Certains rapide de profilage suggère que c'est environ 30% plus rapide que la liste de compréhension de la démarche, et d'environ 40% plus rapide que le filtre approche.

Mise en garde:alors que cette solution est plus rapide que la solution la plus évidente, c'est plus obscur, et s'appuie sur plus avancées Python techniques.Si vous ne l'utilisez, je vous recommande l'accompagnant d'un commentaire.C'est probablement seulement en valeur de l'aide dans le cas où vous vous souciez vraiment de la performance de cette opération particulière (ce qui est assez rapide n'importe quoi).(Dans le cas où je l'ai utilisé, j'ai été faire Un* faisceau de recherche, et utilisé cette option pour supprimer les points de recherche de la recherche de la poutre.)

À l'aide de une compréhension de liste

list = [x for x in list if x[-5:] != "smith"]

Il ya des moments où le filtrage (soit à l'aide de filtre ou d'une compréhension de liste) ne fonctionne pas.Ce qui se passe quand un autre objet est la détention d'une référence à la liste que vous modifiez et vous avez besoin de modifier la liste à la place.

for name in names[:]:
    if name[-5:] == 'Smith':
        names.remove(name)

La seule différence à partir du code original est l'utilisation de names[:] au lieu de names dans la boucle for.De cette façon, le code effectue une itération sur une (profonde) copie de la liste et les absorptions de fonctionner comme prévu.Depuis la liste de copie est peu profonde, il est assez rapide.

filtre serait génial pour cela.Exemple Simple:

names = ['mike', 'dave', 'jim']
filter(lambda x: x != 'mike', names)
['dave', 'jim']

Edit: Corey de la compréhension de liste est génial.

names = filter(lambda x: x[-5:] != "Smith", names);

Les deux solutions, filtre et la compréhension requiert la construction d'une nouvelle liste.Je ne sais pas assez de Python-même pour être sûr, mais je pense qu'un plus classique (mais moins élégant) approche pourrait être plus efficace:

names = ['Jones', 'Vai', 'Smith', 'Perez']

item = 0
while item <> len(names):
    name = names [item]
    if name=='Smith':
        names.remove(name)
    else:
        item += 1

print names

De toute façon, pour de courtes listes, je m'en tiens à l'une des deux solutions proposées précédemment.

Pour répondre à votre question au sujet de travailler avec les dictionnaires, il faut noter que Python 3.0 comprendra dict des compréhensions:

>>> {i : chr(65+i) for i in range(4)}

Dans le même temps, vous pouvez faire un quasi-dict compréhension de cette manière:

>>> dict([(i, chr(65+i)) for i in range(4)])

Ou plus directement la réponse:

dict([(key, name) for key, name in some_dictionary.iteritems if name[-5:] != 'Smith'])

Si la liste doit être filtrée en place et la taille de la liste est assez grande, puis les algorithmes mentionnés dans les réponses précédentes, qui sont basés sur la liste.remove(), peut-être impropre, parce que leur complexité de calcul est O(n^2).Dans ce cas, vous pouvez utiliser les pas-si pythonic fonction:

def filter_inplace(func, original_list):
  """ Filters the original_list in-place.

  Removes elements from the original_list for which func() returns False.

  Algrithm's computational complexity is O(N), where N is the size
  of the original_list.
  """

  # Compact the list in-place.
  new_list_size = 0
  for item in original_list:
    if func(item):
      original_list[new_list_size] = item
      new_list_size += 1

  # Remove trailing items from the list.
  tail_size = len(original_list) - new_list_size
  while tail_size:
    original_list.pop()
    tail_size -= 1


a = [1, 2, 3, 4, 5, 6, 7]

# Remove even numbers from a in-place.
filter_inplace(lambda x: x & 1, a)

# Prints [1, 3, 5, 7]
print a

Edit:En fait, la solution à https://stackoverflow.com/a/4639748/274937 est supérieure à la mienne solution.Il est plus pythonic et fonctionne plus rapidement.Donc, voici une nouvelle filter_inplace() mise en œuvre:

def filter_inplace(func, original_list):
  """ Filters the original_list inplace.

  Removes elements from the original_list for which function returns False.

  Algrithm's computational complexity is O(N), where N is the size
  of the original_list.
  """
  original_list[:] = [item for item in original_list if func(item)]

Le filtre et les interprétations de la liste sont ok pour ton exemple, mais ils ont un couple de problèmes:

  • Ils font une copie de votre liste et de retour de la nouvelle, et qui sera inefficace lorsque la liste est vraiment très grand
  • Ils peuvent être vraiment lourd lorsque les critères de sélection des articles (dans votre cas, si le nom de[-5:] == 'Smith') est plus compliqué, ou a plusieurs conditions.

Votre solution originale est en fait plus efficace pour les très grandes listes, même si l'on peut convenir qu'il est plus laid.Mais si vous vous inquiétez que vous pouvez avoir plusieurs "John Smith", il peut être résolu en supprimant basée sur la position et non pas sur la valeur:

names = ['Jones', 'Vai', 'Smith', 'Perez', 'Smith']

toremove = []
for pos, name in enumerate(names):
    if name[-5:] == 'Smith':
        toremove.append(pos)
for pos in sorted(toremove, reverse=True):
    del(names[pos])

print names

Nous ne pouvons pas choisir une solution sans tenir compte de la taille de la liste, mais pour les grandes listes, je préfère votre 2-passer la solution à la place du filtre ou de la liste des compréhensions

Dans le cas d'un jeu.

toRemove = set([])  
for item in mySet:  
    if item is unwelcome:  
        toRemove.add(item)  
mySets = mySet - toRemove 

Voici mon filter_inplace la mise en œuvre qui peut être utilisé pour filtrer les éléments d'une liste en place, j'ai trouvé cela sur mon propre indépendamment avant de trouver cette page.C'est le même algorithme que ce PabloG posté, juste plus générique, de sorte que vous pouvez l'utiliser pour les listes de filtre en place, il est également capable de supprimer de la liste basée sur le comparisonFunc si inversée est réglé True;une sorte de conte de filtre si vous voulez.

def filter_inplace(conditionFunc, list, reversed=False):
    index = 0
    while index < len(list):
        item = list[index]

        shouldRemove = not conditionFunc(item)
        if reversed: shouldRemove = not shouldRemove

        if shouldRemove:
            list.remove(item)
        else:
            index += 1

Eh bien, c'est clairement un problème avec la structure de données que vous utilisez.Utiliser une table de hachage par exemple.Certaines implémentations de soutien à entrées multiples par touche, on peut donc soit de la pop la plus récente de l'élément de désactiver ou supprimer tous les de la.

Mais ce n'est, et ce que vous allez trouver la solution, élégance dans une autre structure de données, pas de l'algorithme.Peut-être que vous pouvez faire mieux si elle est triée, ou quelque chose, mais itération sur une liste est à votre seule méthode ici.

edit: on ne se rend compte qu'il a demandé "l'efficacité"...toutes ces méthodes que nous venons de parcourir la liste, qui est le même que ce qu'il a proposé.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top