Κομψός τρόπος για να καταργήσετε στοιχεία από σειρά με την Python;

ψήφοι
51

Όταν γράφω κώδικα σε Python, συχνά πρέπει να αφαιρέσετε στοιχεία από μια λίστα ή άλλου τύπου ακολουθία με βάση κάποια κριτήρια. Δεν έχω βρει μια λύση που είναι κομψή και αποδοτική, όπως την αφαίρεση στοιχείων από μια λίστα που επί του παρόντος την επανάληψη μέσα είναι κακό. Για παράδειγμα, δεν μπορείτε να το κάνετε αυτό:

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

Συνήθως καταλήγουν να κάνουν κάτι σαν αυτό:

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

Αυτό είναι innefficient, αρκετά άσχημο και ενδεχομένως λάθη (πώς να το χειριστεί πολλαπλές εγγραφές «John Smith»;). Υπάρχει κάποιος που να έχει μια πιο κομψή λύση, ή τουλάχιστον ένα πιο αποτελεσματικό ένα;

Τι θα λέγατε για ένα που λειτουργεί με λεξικά;

Δημοσιεύθηκε 20/08/2008 στις 18:41
πηγή χρήστη
Σε άλλες γλώσσες...                            


14 απαντήσεις

ψήφοι
-2

Λοιπόν, αυτό είναι σαφώς ένα ζήτημα με τη δομή δεδομένων που χρησιμοποιείτε. Χρησιμοποιήστε ένα Hashtable για παράδειγμα. Μερικές εφαρμογές υποστηρίζουν πολλαπλές εγγραφές ανά κλειδί, έτσι μπορεί κανείς να σκάσει είτε το νεότερο στοιχείο off, ή να αφαιρέσετε όλα αυτά.

Αλλά αυτό είναι, και τι πάμε να βρούμε τη λύση είναι, κομψότητα μέσα από μια διαφορετική δομή δεδομένων, δεν αλγόριθμο. Ίσως μπορείτε να κάνετε καλύτερα αν είναι ταξινομημένο, ή κάτι τέτοιο, αλλά επανάληψη σε μια λίστα είναι μόνο μέθοδος σας εδώ.

edit: το ένα έχει συνειδητοποιήσει ρώτησε για «απόδοση» ... όλες αυτές οι προτεινόμενες μέθοδοι απλώς επαναλάβει πάνω από τη λίστα, το οποίο είναι το ίδιο με αυτό που προτείνεται.

Απαντήθηκε 20/08/2008 στις 18:46
πηγή χρήστη

ψήφοι
2
names = filter(lambda x: x[-5:] != "Smith", names);
Απαντήθηκε 20/08/2008 στις 18:48
πηγή χρήστη

ψήφοι
3

φίλτρο θα ήταν φοβερό γι 'αυτό. Απλό παράδειγμα:

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

Επεξεργασία: κατάλογος Corey της κατανόησης είναι πολύ φοβερό.

Απαντήθηκε 20/08/2008 στις 18:49
πηγή χρήστη

ψήφοι
10

Χρησιμοποιώντας μια λίστα με κατανόηση

list = [x for x in list if x[-5:] != "smith"]
Απαντήθηκε 20/08/2008 στις 18:49
πηγή χρήστη

ψήφοι
53

Δύο εύκολοι τρόποι για να επιτευχθεί ακριβώς το φιλτράρισμα είναι:

  1. Χρήση filter:

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

  2. Χρήση της λίστας Η κατανόηση:

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

Να σημειωθεί ότι και στις δύο περιπτώσεις διατηρούν τις αξίες για τις οποίες η λειτουργία κατηγόρημα αξιολογεί με True, έτσι θα πρέπει να αντιστραφεί η λογική (δηλαδή λέτε «να κρατήσει τους ανθρώπους που δεν έχουν το επώνυμο Smith» αντί για «αφαιρέστε τους ανθρώπους που έχουν το επώνυμο Σιδηρουργός").

Επεξεργασία Αστεία ... δύο άτομα μεμονωμένα δημοσιεύτηκε τόσο από τις απαντήσεις που πρότεινα, όπως ήμουν απόσπαση δική μου.

Απαντήθηκε 20/08/2008 στις 18:50
πηγή χρήστη

ψήφοι
2

Και οι δύο λύσεις, φίλτρο και κατανόηση απαιτεί την οικοδόμηση μιας νέας λίστας. Δεν ξέρω αρκετά από τα εσωτερικά Python για να είστε σίγουροι, αλλά σκέφτομαι ότι μια πιο παραδοσιακή (αλλά λιγότερο κομψά) προσέγγιση θα μπορούσε να είναι πιο αποτελεσματική:

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

Τέλος πάντων, για σύντομες λίστες, έχω κολλήσει με μία από τις δύο λύσεις που προτείνονται νωρίτερα.

Απαντήθηκε 20/08/2008 στις 19:20
πηγή χρήστη

ψήφοι
1

Οι Η κατανόηση του φίλτρου και ο κατάλογος είναι εντάξει για παράδειγμα σας, αλλά έχουν μια-δυο προβλήματα:

  • Κάνουν ένα αντίγραφο της λίστας σας και να επιστρέψετε το νέο, και ότι θα είναι αναποτελεσματική, όταν ο αρχικός κατάλογος είναι πραγματικά μεγάλη
  • Μπορούν να είναι πολύ περίπλοκη, όταν τα κριτήρια για να πάρει τα στοιχεία (στην περίπτωσή σας, εάν το όνομα [-5:] == «Smith») είναι πιο περίπλοκη, ή έχει αρκετές προϋποθέσεις.

αρχική λύση σας είναι στην πραγματικότητα πιο αποτελεσματικό για πολύ μεγάλες λίστες, ακόμα και αν μπορούμε να συμφωνήσουμε ότι είναι άσχημο. Αλλά αν ανησυχείτε ότι μπορείτε να έχετε πολλαπλές «John Smith», μπορεί να καθοριστεί με τη διαγραφή με βάση τη θέση και όχι στην τιμή:

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

Δεν μπορεί να πάρει μια λύση χωρίς να ληφθεί υπόψη το μέγεθος της λίστας, αλλά και για τις μεγάλες λίστες θα προτιμούσα λύση 2-pass σας, αντί των φίλτρων ή καταλόγων Η κατανόηση

Απαντήθηκε 02/10/2008 στις 19:44
πηγή χρήστη

ψήφοι
4

Υπάρχουν φορές που το φιλτράρισμα (είτε με τη χρήση φίλτρων ή μια λίστα κατανόηση) δεν λειτουργεί. Αυτό συμβαίνει όταν κάποιο άλλο αντικείμενο που κατέχουν μια αναφορά στον κατάλογο που τροποποιείτε και θα πρέπει να τροποποιήσετε τη λίστα στη θέση του.

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

Η μόνη διαφορά από το πρωτότυπο κώδικα είναι η χρήση των names[:]αντί namesστο βρόχο for. Με αυτόν τον τρόπο οι επαναλαμβάνεται κώδικα πάνω από ένα (ρηχό) αντίγραφο της λίστας και το έργο μετακομίσεις, όπως αναμενόταν. Δεδομένου ότι η αντιγραφή λίστα είναι ρηχή, είναι αρκετά γρήγορη.

Απαντήθηκε 05/10/2008 στις 12:48
πηγή χρήστη

ψήφοι
2

Για να απαντήσω στην ερώτησή σας σχετικά με την εργασία με τα λεξικά, θα πρέπει να σημειωθεί ότι η Python 3.0 θα περιλαμβάνει dict Η κατανόηση :

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

Εν τω μεταξύ, μπορείτε να κάνετε μια οιονεί-dict κατανόηση αυτόν τον τρόπο:

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

Ή ως μια πιο άμεση απάντηση:

dict([(key, name) for key, name in some_dictionary.iteritems if name[-5:] != 'Smith'])
Απαντήθηκε 07/10/2008 στις 15:33
πηγή χρήστη

ψήφοι
37

Μπορείτε επίσης να επαναλάβει προς τα πίσω πάνω από τη λίστα:

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

Αυτό έχει το πλεονέκτημα ότι δεν δημιουργεί μια νέα λίστα (όπως filterή μια λίστα κατανόησης) και χρησιμοποιεί ένα iterator αντί για ένα αντίγραφο λίστας (όπως [:]).

Να σημειωθεί ότι αν και αφαίρεση στοιχείων, ενώ την επανάληψη προς τα πίσω είναι ασφαλές, εισάγοντας τους είναι κάπως πιο περίπλοκη.

Απαντήθηκε 08/10/2008 στις 02:24
πηγή χρήστη

ψήφοι
1

Στην περίπτωση ενός συνόλου.

toRemove = set([])  
for item in mySet:  
    if item is unwelcome:  
        toRemove.add(item)  
mySets = mySet - toRemove 
Απαντήθηκε 07/12/2009 στις 05:01
πηγή χρήστη

ψήφοι
28

Η προφανής απάντηση είναι αυτή που ο John και μερικά άλλα άτομα έδωσαν, και συγκεκριμένα:

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

Αλλά αυτό έχει το μειονέκτημα ότι δημιουργεί ένα νέο αντικείμενο λίστας, αντί να χρησιμοποιήσει εκ νέου το αρχικό αντικείμενο. Έκανα κάποια προφίλ και τον πειραματισμό, και την πιο αποτελεσματική μέθοδο ήρθα με είναι:

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

Εκχώρηση σε «ονόματα [:]» ουσιαστικά σημαίνει «να αντικαταστήσει τα περιεχόμενα της λίστας ονομάτων με την ακόλουθη τιμή». Είναι διαφορετικό από ακριβώς την ανάθεση σε ονόματα, δεδομένου ότι δεν δημιουργεί ένα νέο αντικείμενο λίστα. Η δεξιά πλευρά του εκχώρησης είναι μια έκφραση γεννήτρια (σημειώστε τη χρήση των παρενθέσεων αντί αγκύλες). Αυτό θα προκαλέσει Python για να μετακινηθείτε σε όλη τη λίστα.

Μερικές γρήγορες προφίλ υποδηλώνει ότι αυτό είναι περίπου 30% πιο γρήγορα από ό, τι την προσέγγιση της κατανόησης λίστα, και περίπου 40% πιο γρήγορα από ό, τι την προσέγγιση του φίλτρου.

Προειδοποίηση : ενώ η λύση αυτή είναι ταχύτερη από την προφανή λύση, είναι πιο ασαφής, και βασίζεται σε πιο προηγμένες τεχνικές Python. Εάν χρησιμοποιείτε αυτό, ήθελα να συστήσω το συνοδεύει με ένα σχόλιο. Είναι ίσως αξίζει χρησιμοποιώντας μόνο στις περιπτώσεις που πραγματικά ενδιαφέρονται για την εκτέλεση της συγκεκριμένης λειτουργίας (που είναι αρκετά γρήγορος δεν έχει σημασία τι). (Σε περίπτωση που χρησιμοποίησα αυτό, έκανα μια αναζήτηση πορείας *, και χρησιμοποιείται για να αφαιρέσετε τα σημεία αναζήτησης από την ακτίνα αναζήτησης.)

Απαντήθηκε 09/01/2011 στις 15:41
πηγή χρήστη

ψήφοι
2

Εάν η λίστα θα πρέπει να διηθηθεί σε-θέση και το μέγεθος λίστας είναι αρκετά μεγάλο, τότε αλγόριθμοι που αναφέρονται στις προηγούμενες απαντήσεις, οι οποίες βασίζονται σε list.remove (), μπορεί να είναι ακατάλληλη, επειδή υπολογιστική πολυπλοκότητα τους είναι O (n ^ 2) . Σε αυτή την περίπτωση μπορείτε να χρησιμοποιήσετε την ακόλουθη όχι και τόσο pythonic λειτουργίας:

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

Επεξεργασία: Στην πραγματικότητα, η λύση στο https://stackoverflow.com/a/4639748/274937 είναι ανώτερη από την λύση των ναρκών. Είναι πιο pythonic και λειτουργεί πιο γρήγορα. Έτσι, εδώ είναι μια εφαρμογή νέα filter_inplace ():

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)]
Απαντήθηκε 02/04/2012 στις 17:20
πηγή χρήστη

ψήφοι
1

Εδώ είναι μου filter_inplaceεφαρμογή που μπορεί να χρησιμοποιηθεί για το φιλτράρισμα στοιχεία από μια λίστα σε θέση, ήρθα με αυτό για τη δική μου ανεξάρτητη πριν από την εξεύρεση αυτής της σελίδας. Είναι το ίδιο αλγόριθμο, όπως αυτό δημοσιεύτηκε PabloG, μόλις έκανε πιο γενικό ώστε να μπορείτε να το χρησιμοποιήσετε για να φιλτράρετε τις λίστες στη θέση του, είναι επίσης σε θέση να αφαιρέσει από τη λίστα με βάση το comparisonFuncαν αντιστραφεί έχει οριστεί True? ένα είδος-του της αναστραφεί το φίλτρο αν θέλετε.

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
Απαντήθηκε 15/03/2013 στις 15:12
πηγή χρήστη

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more