Python list elements removal logical error? [duplicate] - python

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Removing from a list while iterating over it
I have this code:
s = [2,3,4,5]
for i in s:
s.remove(i)
print(s)
When I run it, the result is:
[3,5]
What is the logical error here ?

You are iterating over the list while modifying it, which is causing your problem.
Make a temporary list (list(s)) that you iterate over and modify the original to your needs:
>>> s = [2,3,4,5]
>>>
>>> for i in list(s):
... s.remove(i)
...
>>> print(s)
[]

The logical error is in removing elements from a list while iterating through the same list.
Python hides some implementation details, of course. Internally, you remove the first item, leaving [3,4,5]. Then you remove the second item, leaving [3,5]. At this point you have removed two items from a list that is only two items long, so you are done.

It is because of the iterator. Consider this code segment:
s = [2,3,4,5]
for i in s:
print "i",i
j = s
print "j", j
s.remove(i)
print "removed"
print "j", j
print "s", s
print s
The output would be
i 2
j [2, 3, 4, 5]
removed
j [3, 4, 5]
s [3, 4, 5]
i 4
j [3, 4, 5]
removed
j [3, 5]
s [3, 5]
[3, 5]
Basically, when you execute the for loop, the the internal iterator goes to s[0], then s[1], etc. When the next round of the loop executes, s[1] is already 4 instead of 3 because s itself has been modified.

Related

Reverse a list in python using append and pop method [duplicate]

This question already has answers here:
Issue with reversing list using list.pop()
(4 answers)
How do I reverse a list or loop over it backwards?
(37 answers)
Closed 1 year ago.
In the tutorial video I was watching the range used in for loop was "range(len(l))" which successfully reversed the list.
But I guess simply putting " l " will solve the purpose and it didn't can someone please tell me why is that?
def reversed_list(l):
rev = []
for i in l:
l_popped = l.pop()
rev.append(l_popped)
return rev
SAMPLE_LIST = [1,2,3,4]
print(reversed_list(SAMPLE_LIST))
OUTPUT:
[4, 3]
I'm not sure what the tutorial you watched did, but range(len(_)) will not reverse, it just creates a range of the size of your list, starting at 0.
If you want to reverse a list in Python, just use the builtin reversed:
l = list("hello")
print(list(reversed(l))) # at list to have an actual list as opposed to an iterator
# ['o', 'l', 'l', 'e', 'h']
print(range(len(l)))
# range(0, 5)
PS: The reason your solution doesn't work is you're changing the object you iterate over during the iteration by using pop. A simple way to debug this is to add a print statement:
def reversed_list(l):
rev = []
for i in l:
print("This is l:", l)
l_popped = l.pop()
rev.append(l_popped)
return rev
print(reversed_list([1,2,3,4]))
# This is l: [1, 2, 3, 4]
# This is l: [1, 2, 3]
# [4, 3]
This loop is root cause of problem.
for i in l:
l_popped = l.pop()
rev.append(l_popped)
return rev
Let us try to dry run.
When i = 1 (index 0), it pops 4 and rev = [4], l = [1, 2, 3]
When i = 2 (index 1), it pops 3 and rev = [4, 3], l = [1, 2]
Now, index crosses the length of l hence operation on remaining elements are not executed.
If you really want to use pop to reverse a list, you can make a shallow copy of the original list. Every time you iterate, the length of the list reduces
>>> x=[1,2,3,4]
>>> z=x.copy()
>>> y=[x.pop(i) for i in range(len(z)-1,-1,-1)]
>>> y
[4, 3, 2, 1]

How to parse these operations through lists?

Program description:
Program accepts a list l containing other lists. Output l where lists with length greater than 3 will be changed accordingly: the element with index 3 is going to be a sum of removed elements (from third to the end).
My solution:
l = [[1,2], [3,4,4,3,1], [4,1,4,5]]
s = 0
for i in range(len(l)-1):
if len(l[i]) > 3:
for j in range(3,len(l[i])-1):
s += l[i][j]
l[i].remove(l[i][j])
l[i].insert(len(l[i]),s)
l
Test:
Input: [[1,2], [3,4,4,3,1], [4,1,4,5]]
Expected Output: [[1, 2], [3, 4, 8], [4, 1, 9]]
Program run:
Input: [[1,2], [3,4,4,3,1], [4,1,4,5]]
Output: [[1, 2], [4, 4, 3, 1, 3], [4, 1, 4, 5]]
Question: I don't understand what can be the source of the problem in this case, why should it add some additional numbers to the end, instead of summ. I will appreciate any help.
remove is the wrong function. You should use del instead. Read the documentation to understand why.
And another bug you have is that you do not reset s. It should be set to 0 in the outer for loop.
But you're making it too complicated. I think it's better to show how you can do it really easy.
for e in l: # No need for range. Just iterate over each element
if len(e) > 3:
e[2]=sum(e[2:]) # Sum all the elements
del(e[3:]) # And remove
Or if you want it as a list comprehension that creates a new list and does not alter the old:
[e[0:2] + [sum(e[2:])] if len(e)>3 else e for e in l]
First of all, remove() is the wrong method, as it deletes by value, not index:
Python list method remove() searches for the given element in the list
and removes the first matching element.
You'd want to use del or pop().
Second of all, you're not slicing all of the elements from the end of the list, but only one value.
remove is reason why your code is not working. (as mentioned by Mat-KH in the other answer)
You can use list comprehension and lambda function to make it a two liner.
func = lambda x: x if len(x) < 3 else x[:2] + [sum(x[2:])]
l = [func(x) for x in l]

Python nested list comprehension (accessing nested elements) [duplicate]

This question already has answers here:
Explanation of how nested list comprehension works?
(11 answers)
Closed 5 years ago.
I'm having trouble understanding the syntax here.
matrix_a = [[1, 2], [3, 4], [5, 6]]
matrix_b = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
[a for a, b in matrix_a]
output: [1, 3, 5]
[a for b, a in matrix_a]
output: [2, 4, 6]
I understand a little about how list-comprehensions work, but I don't understand the syntax when accessing certain elements within a nested list.
I just can't wrap my head around this syntax. How is this syntax working? What does the comma represent? What does a for a mean? Can you explain whats going on under the hood? And finally how would you do this with matrix_b
If you convert it to a for loop it might be easier to see..?
res = []
for item in matrix_a:
a, b = item # a = item[0]; b = item[1]
res.append(a)
you're basically unpacking the individual items in the list and picking one of them.
I think you have mistaken in writing output here
[a for a, b in matrix_a] returns [1 2 4] which is logical , returning first element of each nested list item
see the screenshot
just understand in this way:
[a for b, a in matrix_a] #as
[a for [a, b] in matrix_a] #or
[a for (a, b) in matrix_a]
#matrix_a has elements as list of length 2 each
#so your list comprehenssion says, give me a output list -- having first element'a' from each element of matrix_a(whose each element represented as [a,b] type, with comma, becomes tuple (a,b)),
# then from [b,a] give me a, that is 2nd element
# and it will fail if you apply same on matrix_b, cause each element of matrix_b is not of type [a,b] i:e of length 2
# you will get "ValueError: too many values to unpack"
Let me know if anything is not clear. Thanks.

Python 3: Removing list item with for loop, is this the right way? [duplicate]

This question already has answers here:
How to remove items from a list while iterating?
(25 answers)
Closed 6 years ago.
I am still learning the basics of python, and I have just spent a while reading about how to remove an item from a list in python from within a for loop. Everything I've read suggests complex ways of doing this, and they say that you cannot remove an item from a list while you're iterating over it. However... this seems to work:
class Object():
def __init__(self):
self.y = 0
object_list = [Object(), Object(), Object()]
for thing in object_list:
thing.y += 1
if thing.y > 10:
object_list.remove(thing)
Why is this working when others say it isn't and write complicated workarounds? Is it because you aren't allowed to do it in Python 2 but can in Python 3?
And is this the right way to do this? Will it work as I want it or will it be prone to bugs? Would it be advisable to iterate over the list in reverse order if I plan to remove items?
Sorry if this has been answered before, but it's hard to know which resources refer to what as they all just say "python" in the tag (at least, the ones I've been reading, maybe that's because all the ones I have read are python 2?)
Thanks!
EDIT:
Sorry, there were a couple of copy and paste errors... I've fixed them...
EDIT:
I've been watching another one of Raymond Hettinger's videos... He mentions a way of removing items from a dictionary while iterating over it by using dict.keys(). Something like:
d = {'text': 'moreText', 'other': 'otherText', 'blah': 'moreBlah'}
for k in d.keys():
if k.startswith('o'):
del d[k]
Apparently using the keys makes it safe to remove the item while iterating. Is there an equivalent for lists? If there was I could iterate backwards over the list and remove items safely
Here are some examples
def example1(lst):
for item in lst:
if item < 4:
lst.remove(item)
return lst
def example2(lst):
for item in lst[:]:
if item < 4:
lst.remove(item)
return lst
def example3(lst):
i = 0
while i < len(lst):
if lst[i] < 4:
lst.pop(i)
else:
i += 1
return lst
def example4(lst):
return [item for item in lst if not item < 4]
def example5(lst):
for item in reversed(lst):
if item < 4:
lst.remove(item)
return lst
def example6(lst):
for i, item in reversed(list(enumerate(lst))):
if item < 4:
lst.pop(i)
return lst
def example7(lst):
size = len(lst) - 1
for i, item in enumerate(reversed(lst)):
if item < 4:
lst.pop(size - i)
return lst
def example8(lst):
return list(filter(lambda item: not item < 4, lst))
import itertools
def example9(lst):
return list(itertools.filterfalse(lambda item: item < 4, lst))
# Output
>>> lst = [1, 1, 2, 3, 2, 3, 4, 5, 6, 6]
>>> example1(lst[:])
[1, 3, 3, 4, 5, 6, 6]
>>> example2(lst[:])
[4, 5, 6, 6]
>>> example3(lst[:])
[4, 5, 6, 6]
>>> example4(lst[:])
[4, 5, 6, 6]
>>> example5(lst[:])
[4, 5, 6, 6]
>>> example6(lst[:])
[4, 5, 6, 6]
>>> example7(lst[:])
[4, 5, 6, 6]
>>> example8(lst[:])
[4, 5, 6, 6]
>>> example9(lst[:])
[4, 5, 6, 6]
Example 1
This example involves iterating through the list and removing values from it. The issue with this is that you are modifying the list as you go through it so your list changes during iteration and so some elements get skipped over.
Example 2
Here we are iterating over a shallow copy of the list instead of the list itself. The issue with this is if you have a large list it could be expensive to do so.
Example 3
The following is an example using pop instead of remove, the issue with remove is that it removes the first instance of the value it finds from the list. This will typically be of no issue unless you have objects which are equal. (See example 10)
Example 4
Instead of modifying the list here instead we create a new list using list comprehension allowing only specified values.
Example 5
This is an example of iterating through the list in reverse, the difference is that we use the built-in reversed function to apply a for-loop to in stead of a while loop with a counter.
Example 6
Similar example using pop instead.
Example 7
Better example using pop as we don't have to cast back to a list to use the reversed function.
Example 8
Example using the built-in filter method to remove the specified values.
Example 9
Similar example using the filerfalse method from itertools
class Example(object):
ID = 0
def __init__(self, x):
self._x = x
self._id = str(Example.ID)
Example.ID += 1
def __eq__(self, other):
return self._x == other._x
def __repr__(self):
return 'Example({})'.format(self._id)
def example10():
lst = [Example(5), Example(5)]
print(lst)
lst.remove(lst[1])
return lst
#Output
>>> example10()
[Example(0), Example(1)]
[Example(1)]
Example 10
Here we create two Example objects with the same values and by the equality method they are equal. The ID variable is there to help us differentiate between the two. Now we have specified that we want to remove the 2nd object from the list, however because both are equal the first item is actually removed instead.
Timings
These are pretty rough times and can vary slightly depending on your device. Although these identify which one is faster, this was tested with a list of 10,000 items so if you don't have anything close to that then any choice is fine really.
import timeit
import random
# Code from above is here
def test(func_name):
global test_lst
test_lst = lst[:]
return timeit.timeit("{}(test_lst)".format(func_name),
setup="from __main__ import {}, test_lst".format(func_name), number = 1)
if __name__ == '__main__':
NUM_TRIALS = 1000
lst = list(range(10000))
random.shuffle(lst) # Don't have to but makes it a bit interesting
test_list = lst[:]
for func in ('example2', 'example3', 'example4', 'example5',
'example6', 'example7', 'example8', 'example9'):
trials = []
for _ in range(NUM_TRIALS):
trials.append(test(func))
print(func, sum(trials) / len(trials) * 10000)
#Output
example2 8.487979147454494
example3 20.407155912623292
example4 5.4595031069025035
example5 7.945100572479213
example6 14.43537688078149
example7 9.088818018676008
example8 14.898256300967116
example9 13.865010859443247
It will work. However it's never a good idea to modify an object while you're iterating over it. You'll likely to get unexpected behaviour.
If I did this:
my_list = [1, 2, 3, 4]
for x in my_list:
if x+1 in my_list:
my_list.remove(x+1)
I'd expect my_list = [1] at the end. 1 remove 2, 2 removes 3, and 3 removes 4. If I check though I find my_list=[1,3]. This is because 2 was removed from the list in the first loop, so the second loop used 3 to remove 4, and 3 is still in the list.

Elegant Way of Ignoring Specific Python ListElements [duplicate]

This question already has answers here:
Get unique values from a list in python [duplicate]
(30 answers)
Closed 6 years ago.
I have recently started trying to learn Python, and I try to improve the way I write code, and make it more "Pythonic".
Therefore, it would really be nice if someone could explain to me if the following can be formulated more elegantly.
Hopefully this is not a duplicate (I checked, but you never know)
I have a list of 5 elements, and I want to return specific elements.
Let's say for example that I have [1, 2, 3, 3, 4].
I already have a function double(list), that if a list element exists twice returns that element (in this case 3).
I would like to generate from this list a tuple that contains the numbers that are exist only once (1, 2, 4).
One option is the following:
Run the double(list) function and get the value of the element that is double.
Create an empty tuple
Iterate over the list items, and if the value is not equal to what the double(list) function returned, add it to the tuple
Return the tuple.
My question is: is there a more elegant/Pythonic way of doing this?
(in one line, using maybe a more complex expression?)
Thanks in advance
The general way to do this is to make a set out of the elements and then count them, or just use collections.Counter, then go through the list and include only the appropriate elements, by either creating an empty list and then adding to it with a traditional loop or by using a comprehension with a filter.
>>> import collections
>>> l = [1, 2, 3, 3, 4]
>>> c = collections.Counter(l)
>>> new_list = [item for item in l if c[item] < 2]
>>> new_list
[1, 2, 4]
Since you want a single-line solution (well except for the actual list declaration of course :) ):
your_list = [1, 2, 3, 3, 4]
uniques = [item for item in your_list if your_list.count(item) == 1]
I would use collections.Counter for that:
>>> import collections
>>> l = [1, 2, 3, 3, 4]
>>> c = collections.Counter(l)
>>> [el for el in l if c[el] == 1]
[1, 2, 4]

Categories

Resources