1

I have a to do list in the form of tuples...

[(datetime.datetime(2014, 2, 28, 0, 0), 'tutorial signons'), (datetime.datetime(2014, 4, 9, 0, 0), 'assignment 1'), (datetime.datetime(2014, 4, 22, 0, 0), 'assignment 2'), (datetime.datetime(2014, 6, 14, 0, 0), 'exam study'), (datetime.datetime(2014, 3, 15, 0, 0), 'buy groceries'), (datetime.datetime(2014, 3, 20, 0, 0), 'laundry'), (datetime.datetime(2014, 3, 26, 0, 0), 'maths assignment'), (datetime.datetime(2014, 3, 31, 0, 0), 'write todo list'), (datetime.datetime(2014, 4, 4, 0, 0), 'apply for a job'), (datetime.datetime(2014, 4, 14, 0, 0), 'procrastinate'), (datetime.datetime(2014, 4, 19, 0, 0), 'buy easter eggs'), (datetime.datetime(2014, 4, 25, 0, 0), 'buy anzac biscuits')]

and I'm trying to write a function that will remove a tuple from the list if the to do task matches a name, if no name is found it will return False. This is what I have so far, but I always get false, even if the task exists.

def remove_item(todolist, name):
for t in todolist:
    if t[1] is name:
        del t
    else:
        return False
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129

3 Answers3

2

The problem with your code is that you are testing for reference equality ('is' operator) instead of value equality ('==' operator).

E.g.

>>> a = 'tutorial signons'
>>> b = 'tutorial signons'

a is b will return False, but a == b will return True.

See this for more details.

Hence, this should do what you were expecting:

  def remove_item(todo_list, name):
     for t in todolist:
         if t[1] == name:
            del t
         else:
            return False

However, deleting elements from a list you are iterating is not a very good idea. You might want to create a new list or use something like:

filtered_list = filter(lambda x: x[1] != 'some_string', todo_list)

This will remove each tuple from the list that matches some_sting.

Community
  • 1
  • 1
neocortex
  • 359
  • 2
  • 11
1

Deleting an item from a list while iterating through it is a bad idea (it messes up the list iterator, so you skip the test item following every item you delete). If you have two items to be removed one after the other, well, you just failed.

There are three ways to get around this;

  1. Iterate through the list in reverse order (a bit messy, but it works)

  2. Use a list comprehension - build a new list, omitting the values to be "removed". This is usually the cleanest to read.

  3. Iterate through the list and keep a list of indices to remove, then go through the list of indices to remove and delete (item i - number of items already removed), ie compensate for list shrinkage. Also messy.

Option 2 would look something like

def remove_item(todolist, name):
    result = [item for item in todolist if name != item[1]]
    if len(todolist) == len(result):
        return False  # nothing removed
    else:
        return result
Hugh Bothwell
  • 55,315
  • 8
  • 84
  • 99
  • There's also the option of iterating through the list, keeping track of an index where non-deleted items should go and copying non-deleted items into the locations they should end up in, then truncating the list at the end. This achieves linear runtime and constant space overhead, but it tends to be less readable than the list comprehension. – user2357112 Apr 03 '14 at 01:16
0

There are a few things wrong with your code:

  1. Use of the is operator which compares object identity. Use == to get what you expect.
  2. Deleting an item during iteration screws up the iterator. Instead, capture the index of the item to be deleted, and delete it outside the loop. A better option is to filter the list or use a list comprehension to get a new list.
  3. Your return False statement is at the wrong indentation. It should be outside the for loop.

I would write the code like this:

def remove_item(todo_list, item):
    return filter(lambda x: x[1] != item, todo_list)
spinlok
  • 3,561
  • 18
  • 27