3
\$\begingroup\$

The simplified question is:

Given a list of numbers, write a Python script that will remove the last odd number from the list.

One of the challenges is if the last odd number appears multiple times, some approaches will remove the first occurrence of that number, rather than the last.

One solution that I have is to find the max index for the odd numbers and then pop that number from the list:

def remove_last_odd(numbers):
    count = 0
    for num in numbers:
        if num%2 == 1:
            max = count
        count += 1
    print max
    numbers.pop(max)

def run():
    numbers = [1, 7, 2, 34, 8, 7, 2, 5, 14, 22, 93, 48, 76, 15, 7]
    print numbers
    remove_last_odd(numbers)
    print numbers

run()    

This works, but I feel like there's got to be a better way. I've thought about reversing the list, removing the first odd number and then re-reversing:

# use this instead of remove_last_odd function
def remove_backwards(numbers):
    rev_num = numbers[::-1]
    for num in rev_num:
        if num % 2 == 1:
            rev_num.remove(num)
            numbers = rev_num[::-1]
            return numbers

This also works, and feels cleaner, but if anyone can provide advice on preferred methods/more Pythonic approaches, it'd be much appreciated!

\$\endgroup\$
  • \$\begingroup\$ I don't have time for writing a complete answer now, but what is now the run function could be made into a doctest for remove_last_odd. \$\endgroup\$ – mkrieger1 Feb 24 '16 at 10:27
6
\$\begingroup\$

By my interpretation, if the list contains no odd numbers, then the function should make no change. Your first function, since it unconditionally ends with numbers.pop(max), will crash with an UnboundLocalError: local variable 'max' referenced before assignment.

count += 1 is awkward. Counting loops are normally written using some kind of range() or enumerate().

The first implementation is inefficient, since it examines the entire list from the beginning.


The second function, as you noted, is inefficient in that it makes two copies of the list. Furthermore, rev_num.remove(num) is fully O(n): it performs another linear search, and then it copies the rest of the elements to fill the hole.


I would write a solution this way. This function mutates the list, and also indicates which index was removed, if any.

def remove_last_odd(numbers):
    for i in range(len(numbers) - 1, -1, -1):
        if numbers[i] % 2:
            numbers.pop(i)
            return i
    return None

Here is another way to do it, using enumerate() and reversed(), which iterates backwards without copying the list. Note that it returns a negative index.

def remove_last_odd(numbers):
    for i, num in enumerate(reversed(numbers), 1):
        if num % 2:
            numbers.pop(-i)
            return -i
    return None
\$\endgroup\$
  • \$\begingroup\$ Wasn't aware of the optional second enumerate argument, nice! \$\endgroup\$ – Jaime Feb 23 '16 at 23:05
  • \$\begingroup\$ Brilliant! Thanks! Now I'm going to go learn about "fully O(n)". \$\endgroup\$ – Stidgeon Feb 24 '16 at 15:14
  • \$\begingroup\$ That's just a way of saying that if the input list is 5× longer, then the code will take roughly 5× longer to execute. \$\endgroup\$ – 200_success Feb 24 '16 at 15:43
  • \$\begingroup\$ Why not just return numbers.pop(-i)? \$\endgroup\$ – SuperBiasedMan Feb 24 '16 at 17:50
  • \$\begingroup\$ @SuperBiasedMan That depends on whether you want to return the element that was removed or the index of the element that was removed. \$\endgroup\$ – 200_success Feb 24 '16 at 17:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.