0

I tried to delete the val value in nums.

nums=[3,3]  
val=3

class Solution:
    def removeElement(self, nums, val):
        if not nums:
            return []
        for i in nums:
            if i == val:
                nums.remove(i)
        return nums

I excepted: []

but Output: [3]

Maybe this is a stupid question.But I hope someone can help me explain it. Thank you.

Patrick Haugh
  • 59,226
  • 13
  • 88
  • 96
D.T
  • 73
  • 8

6 Answers6

1

This is a great example of why you should not modify a list while you iterate over it. List is somewhat unusual because it actually allows you to do the modification in a fairly predictable manner. Many other iterable objects will raise an error if you even try.

When you do for i in nums, it is converted to for i in iter(nums). The iterator over a list keeps track of the index that you are at. Here is what happens in your loop:

  1. nums is [3, 3], the iterator is at position zero:

    3, 3
    ^
    
  2. The item matches val, so it is removed. The list is shifted back one item:

    3
    ^
    
  3. The iterator moves forward one item. This is the end of the list, so iteration stops:

    3
      ^
    

This will happen whenever you have any pair of matching elements sequentially within the list. It would not happen if your list was [3, 4, 3]:

  1. Start

    3, 4, 3
    ^
    
  2. Remove

    4, 3
    ^
    
  3. Increment

    4, 3
       ^
    
  4. Remove

    4
       ^
    
  5. End

There are a couple of ways of getting the filtered list. The most obvious, that is hashed to death in the other answers, is to make a new list, usually via comprehension:

nums = [x for x in nums if x != val]

There is, however, an in-place solution as well. It is not really optimal, but for cases where you must work in place, it is a possibility. You can always implement your own iteration steps if you iterate over the index intelligently:

i = 0
while i < len(nums):  # Note that len is called in every iteration
    if nums[i] == val:
        nums.remove[i]
    else:
        i += 1

Something like this is a good idea if your method can not return the list for some reason.

As an afterthought, you can even do in-place filtering with a comprehension:

nums[:] = [x for x in nums if x != val]

This creates a temporary list, and assigns its contents to the contents of nums, but does not change the object nums refers to.

Mad Physicist
  • 107,652
  • 25
  • 181
  • 264
  • Thank you.That's a great explanation. – D.T Feb 01 '18 at 16:53
  • @D.T, Glad it helped. I added one more workaround at the end – Mad Physicist Feb 01 '18 at 17:22
  • Why nums[:], not nums? – D.T Feb 01 '18 at 17:29
  • @D.T `nums = ...` assigns a new object to `nums` you will never see the change outside your function if you do not return this new reference. `nums[:] = ...` replaces the elements of the current object, without creating a new reference. The second version is in-place: it modifies the original input list so that the change is visible outside your function. – Mad Physicist Feb 01 '18 at 18:19
0

Never delete (or alter) the list while iterating over it.

Comprehension is going to solve your problems:

nums = [i for i in nums if i != val]

What happened in your case?

  1. You went through list.
  2. First element was 3
  3. You removed it.
  4. Second element became the first.
  5. You were already checking the first element so now it is skipped.
  6. Loop ends.
  7. You are left with [3].
zipa
  • 27,316
  • 6
  • 40
  • 58
  • "Never delete (or alter) the list while iterating over it." Why such an absolute? If you do it sensibly, it may not be a good idea, but I can think of where it is a perfectly valid solution. – Mad Physicist Feb 01 '18 at 16:40
  • It does sound Sith but sometimes it's for the best :) – zipa Feb 01 '18 at 16:44
  • One example of where OP's code is useful is when you need to modify the list in-place. Your solution does not allow this directly. – Mad Physicist Feb 01 '18 at 17:24
0

As @tobias_k has said in comments, it's a bad idea to remove items from a list while you're iterating over that list. Instead, you should keep track of another list of items not equal to the parameter val.

def removeElement(self, nums, val):
    if not nums:
        return []
    remaining = []
    for num in nums:
        if num != val:
            remaining.append(num)
    return remaining

For your purpose, what I have above is logically equivalent to the other answers.

Skam
  • 7,298
  • 4
  • 22
  • 31
0

Removing items from a list when you're iterating through the list causes problems because you're causing the list to shift around at the same time that the loop is trying to keep track of its position.

Suppose you have a list of Fruits: Apple, Banana, Banana, Cherry, Date. Suppose you want to remove Banana. So you write code to do this:

for each fruit in Fruits
    if fruit is Banana
        remove fruit

Here's one possible scenario, depending on how iteration is implemented by the programming language or library: First, the code points to element 0 (assuming zero-based indexing), where it finds Apple. Apple doesn't match Banana. So the code moves on to the next item in the list, in position 1: Banana. Banana matches Banana, so that Banana is removed.

Now the list Fruits consists of Apple, Banana, Cherry, Date. The second Banana is sitting at position 1, where the first Banana had been. The Cherry is now sitting where the second Banana had been, in position 2; and the Date is sitting where Cherry had been, in position 3.

Since the code has already done a comparison and deletion at position 1, it isn't going to do another one! It's time for it to increment, to move on to position 2, where it finds Cherry. This means that it has skipped over the second Banana. When the loop completes, you'll still have that second Banana there. The list will be Apple, Banana, Cherry, Date.

An approach that may work, depending on how arrays are implemented (including whether they are dynamic and you can remove elements from them without causing the remaining elements to be resequenced), is to create an array from the elements in the list, and then iterate through them by index, in turn, from n-1 through 0 (assuming zero-based arrays). You have to do it in reverse order or else you'll run into the same problem as before. When you iterate in reverse order, a deletion causes the elements you've already visited to shift, but not the elements you haven't visited yet.

Green Grasso Holm
  • 468
  • 1
  • 4
  • 18
-1
for i in nums:
    if i == val:
        nums.remove(i)

The issue is that you are iterating through a list while you are changing it. A quicker way to do this without editing the original list is using list comprehension:

if not nums:
    return 0
return (x for x in nums if x != val)
  • This returns a generator, not a list. Also, it is not an in-place solution. This is not really equivalent to OP's code. – Mad Physicist Feb 01 '18 at 17:24
-1

I don't work in python, but from what I see, you're trying to remove 1 element from an array. If you know the element, why not simply try: - do not loop - try to remove the 1 element from numns - use exception handling if needed ie element might not exist in array - return nums

you could also try exiting the loop once the element is found. No need to go further if you removed the element you want to remove.

Ray DeBruyn
  • 41
  • 1
  • 7