How do I remove the last occurrence of an element from the list?

The list.remove () function is used to remove the first time an item appears in the list. Is there a built-in function to delete last time? For example, 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 way than what I am currently doing this:

X.reverse()
X.remove('ch')
X.reverse()

      

Soon I will also have to worry about cases where the item being deleted is potentially not on the list. Therefore, methods that are error-free in this case are preferred. Thank!

+3


source to share


6 answers


if 'ch' in X:
    X.reverse()
    X.remove('ch')
    X.reverse()

      

The most pythonic way is to do 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) since x in list

both list.reverse()

are O (N), so there isn't much in between. If you expect the element to be there normally, you can store the check x in list

using try: catch, however, if you expect it to usually not be there, you can store 2 reverse()

by checking the membership first.

+6


source


There is nothing wrong with your code at all. It works, it is clear why it works, it is difficult to be mistaken or misunderstood.

Yes, you could do it faster, but only as a constant factor. (Your algorithm runs two reverse

s, for steps N

each and one remove

, which is a N-1

step, so O(N)

. And since your data is not sorted or something that helped us find the value faster, obviously the ideal algorithm would be too O(N)

.) And at the cost of complication.

The obvious, probably faster way to do this is to simply manually iterate from the end until we find a value, and then remove that value. It also avoids having to deal with ValueError

. Using it enumerate

might help ... but getting it right (without copying it all over) can be tricky.

So let's compare them to your existing code, both wrapped it in try

/ except

and in 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 small list that suits you, the test isn't even worth working on, so I came up with my own random data (in real life, you should 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

      

It's good that the latter wasn't a very good idea ... but the other is about 25% faster than we started. So we saved a whole 9 microseconds, with the data 4 orders of magnitude larger than your actual data. It depends on whether the less clear and easier to use code is worth. (And I'm not going to show you my enumerate

non-copy based implementation , because I was wrong.: P)

+3


source


Create a reverse list keeping the original indices and delete 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 a string, it leaves the list untouched.

+2


source


Ok first you can check if an item is in a list using an operator if in

. Then you can cancel the list and remove the item.

if "ch" in X:
    X.reverse()
    X.remove("ch")
    X.reverse()

      

+1


source


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

      

0


source


Another..

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 doesn't break 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)

      

0


source







All Articles