问题
The list.remove() function serves to remove the first time an item appears in a list. Is there a built-in function to remove the last time? For instance if I have a list, say:
X = ['sf', 'cc', 'ch', 'sc', 'sh', 'ch']
and I want to remove the last 'ch' from the list, is there a better method than what I'm currently doing, which is:
X.reverse()
X.remove('ch')
X.reverse()
I will soon also have to worry about cases where the item being removed is potentially not in the list. So methods that do not throw errors in this case would be preferred. Thanks!
回答1:
if 'ch' in X:
X.reverse()
X.remove('ch')
X.reverse()
The most pythonic way would be to do a try: except
around remove:
X.reverse()
try:
X.remove('ch')
except:
pass
X.reverse()
As per your comment on speed, both of these methods are O(N), as x in list
and list.reverse()
are both O(N), so there's not much between them. If you expect the element to usually be there, you can save the x in list
check by using try: catch, however if you expect it to usually not be there, you can save the 2 reverse()
s by checking for membership first.
回答2:
There's really nothing wrong with your code at all. It works, it's clear why it works, it's hard to get wrong or misunderstand.
Yes, you could make it faster, but only by a constant factor. (Your algorithm does a two reverse
s, for N
steps each, and one remove
, which is N-1
steps, so O(N)
. And since your data aren't sorted or anything that would help us find a value faster, it's obvious that the ideal algorithm would also be O(N)
.) And at the cost of making it more complicated.
The obvious probably-faster way to do it is to just manually iterate from the end until we find a value, then delete that value. That also avoids having to deal with the ValueError
. Using enumerate
might help… but getting it right (without copying the whole thing) may be tricky.
So, let's compare these to your existing code, both wrapped it in a try
/except
, and in an if
:
def f_rev_ex(xs, s):
xs.reverse()
try:
xs.remove(s)
except ValueError:
pass
xs.reverse()
def f_rev_if(xs, s):
if s in xs:
xs.reverse()
xs.remove(s)
xs.reverse()
def f_for(xs, s):
for i in range(len(xs)-1, -1, -1):
if s == xs[i]:
del xs[i]
break
def f_enum(xs, s):
for i, x in reversed(list(enumerate(xs))):
if x == s:
del xs[i]
break
For a list as tiny as yours, the test isn't even worth running, so I invented my own random data (in real life you have to know your data, of course):
In [58]: xs = [random.choice(string.ascii_lowercase) for _ in range(10000)]
In [59]: %timeit y = x[:]; f_rev_ex(y, 'a')
10000 loops, best of 3: 34.7 µs per loop
In [60]: %timeit y = x[:]; f_rev_if(y, 'a')
10000 loops, best of 3: 35.1 µs per loop
In [61]: %timeit y = x[:]; f_for(y, 'a')
10000 loops, best of 3: 26.6 µs per loop
In [62]: %timeit y = x[:]; f_enum(y, 'a')
1000 loops, best of 3: 604 µs per loop
Well, that last one wasn't a very good idea… but the other one is about 25% faster than what we started with. So we've saved a whole 9 microseconds, on data 4 orders of magnitude larger than your actual data. It's up to you whether that's worth the less-readable, easier-to-screw up code. (And I'm not going to show you my enumerate
-based implementation without copying, because I got it wrong. :P)
回答3:
Produce a reversed list, preserving the original indexes and remove the first instance you find.
X = ['sf', 'cc', 'ch', 'sc', 'sh', 'ch']
print X
for i, e in reversed(list(enumerate(X))):
if e == 'ch':
del X[i]
break
print X
If it doesn't find the string it leaves the list untouched.
回答4:
Well first you can check if the item is in the list using a if in
statement. Then you can reverse the list and remove the element.
if "ch" in X:
X.reverse()
X.remove("ch")
X.reverse()
回答5:
Yet another answer...
def remove_last_occurrence(lst, element):
'''
Removes the last occurrence of a given element in a list (modifies list in-place).
:return bool:
True if the element was found and False otherwise.
'''
for i, s in enumerate(reversed(lst)):
if s == element:
del lst[len(lst) - 1 - i]
return True
return False
回答6:
Yet another one ..
def remove_last_occurrence_one_liner(lst, element):
"""
Removes the last occurrence of a given element in a list (modifies list in-place).
Raises same exception than lst.index(element) if element can not be found.
"""
del lst[len(lst) - lst[::-1].index(element) - 1]
But it does not beat the for loop from abarnert
x = [random.choice(string.ascii_lowercase) for _ in range(10000)]
%timeit y = x[:]; f_rev_ex(y, 'a')
34.3 µs ± 219 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit y = x[:]; f_rev_if(y, 'a')
34.9 µs ± 195 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit y = x[:]; f_for(y, 'a')
26.9 µs ± 109 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
%timeit y = x[:]; f_enum(y, 'a')
699 µs ± 4.86 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
%timeit y = x[:]; remove_last_occurrence_one_liner(y, 'a')
49 µs ± 375 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
来源:https://stackoverflow.com/questions/30114518/how-to-remove-the-last-occurrence-of-an-item-from-a-list