Remove evens from a list with del function - python

I'm not allowed to use the .remove() function to delete even numbers in a list and the specs say: You will be modifying the original list, so its id() must not change.This is what I have:
def remove_evens(xs):
for i in range(len(xs)):
for x in xs:
if x%2==0:
del xs[i]
return xs
If for instance I test the function and I input xs=[1,2,3,4,5] it returns [3,5]. I don't know why the 1 doesn't return

That's because you're iterating over the list and modifying it at the same time. This means the index i isn't always correct because you've just removed an element from the list. This leads to skipping elements and that's why the 1 isn't in there.
You can do:
def remove_evens(xs):
return [x for x in xs if x % 2 != 0]
This uses a list comprehension to create a list without the even numbers. If you need to modify the existing list you can do:
def remove_evens(xs):
to_remove = []
for i, x in enumerate(xs):
if x % 2 == 0:
to_remove.append(i)
for j in to_remove:
del xs[j]
return xs
This creates a list to_remove which keeps track of the positions of the elements that need to be removed.

Maybe something like this will work for you.
>>> xs = [1,5,123,6,2,34]
>>> id(xs)
35519496L
>>> lastidx = len(xs) - 1
>>> for i, x in enumerate(reversed(xs)):
... if x%2==0:
... del xs[lastidx-i]
...
>>> xs
[1, 5, 123]
>>> id(xs)
35519496L
It's linear time because it iterates over the list only once. When you iterate over a list backwards, you can freely delete elements without modifying the indexes of future elements that you haven't looped over yet. Also the id of the list does not change with this method, as you can see. :)
Here is another way to do the same thing with only range/xrange and len.
>>> xs = [1,5,123,6,2,34]
>>> for i in range(len(xs)-1,-1,-1):
... if xs[i]%2==0:
... del xs[i]
...
>>> xs
[1, 5, 123]
Both ways do the same thing so whichever way you decide to use is a matter of preference/personal style.

It’s a bad idea to delete items from a list as you are iterating over the list.
Here’s a few ways in which you can achieve your goal:
>>> def deleteEvens(L):
... dels = []
... for i,x in enumerate(L):
... if not x%2:
... dels.append(i)
... for d in dels[::-1]:
... L.pop(d)
...
>>> L
[1, 2, 3, 4, 5]
>>> deleteEvens(L)
>>> L
[1, 3, 5]
OR
>>> L = [1,2,3,4,5]
>>> odds = [i for i in L if i%2]
>>> odds
[1, 3, 5]
OR
>>> def delEvens(L):
... dels = []
... for i,x in enumerate(L):
... if not x%2:
... dels.append(i)
... for d in dels[::-1]:
... del L[d]
...
>>> L = [1,2,3,4,5]
>>> delEvens(L)
>>> L
[1, 3, 5]

Related

how to pop all negative numbers from list with for loop without list comprehension in python

I'm an getting error message saying
"Traceback (most recent call last):
File "main.py", line 10, in
if list1[j] < 0:
IndexError: list index out of range"
I am trying to get rid of all the negative number from the sample input "10 -7 4 39 -6 12 2" to make an output like this 10 4 39 12 2 trying to use pop to do this.
I couldn't figure out an exact fix for this, I've through google searched and searched on stackoverflow.
I don't want list comprehension as an answer
input1 = str(input())
list1 = input1.split()
for i in range(len(list1)):
list1[i] = int(list1[i])
for j in range(len(list1)):
if list1[j] < 0:
list1.pop(j)
print(list1)
Why are you trying to use pop? Since you aren't saving the value, you should probably use del list1[index].
In any case, modifying a list while iterating it is 1) annoying to do correctly 2) very inefficient. You should instead create a new list be it with a list comprehension or with a regular loop.
The fundamental problem is that when you remove an item, all the indices after that have shifted.
Naively, you might think you can do something like:
for i, e in enumerate(data):
if e < 0:
del data[i]
This won't throw the index error, however, if you have two negative numbers next to each other, it will skip one:
>>> data = [1,2, -3, -4, 5]
>>> for i,e in enumerate(data):
... if e < 0:
... del data[i]
...
>>> data
[1, 2, -4, 5]
>>>
But here are a couple of ways that you could do it.
Keep in mind, these algorithms are inefficient
The first, simply keep track of the number of items you have removed and adjust the indices accordingly:
>>> removed = 0
>>> data = [1,2, -3, -4, 5]
>>> for i in range(len(data)):
... if data[i - removed] < 0:
... del data[i - removed]
... removed += 1
...
>>> data
[1, 2, 5]
Perhaps the simplest way is to use a while-loop, actually:
>>> data = [1,2, -3, -4, 5]
>>> i = 0
>>> while i < len(data):
... if data[i] < 0:
... del data[i]
... else:
... i += 1
...
>>> data
[1, 2, 5]
The important point above is to only increment i when you don't delete something.
And finally, you can simply iterate starting from the end of the list:
>>> data = [1,2, -3, -4, 5]
>>> for i in reversed(range(len(data))):
... if data[i] < 0:
... del data[i]
...
>>> data
[1, 2, 5]
>>>
To reiterate:
The above algorithms are all very inefficient.
They require linear space and quadratic time. You can create a new list in linear space and linear time.
Consider:
>>> data = [1,2, -3, -4, 5]
>>> new_data = []
>>> for x in data:
... if x >= 0:
... new_data.append(x)
...
>>> new_data
[1, 2, 5]
Or the equivalent list comprehension:
>>> data = [x for x in data if x >= 0]
>>> data
[1, 2, 5]
Code:
input1 = str(input())
list1 = input1.split()
ind_to_remove = []
for i in range(len(list1)):
list1[i] = int(list1[i])
if list1[i] < 0:
ind_to_remove.append(i)
ind_to_remove.sort(reverse=True)
for ind in ind_to_remove:
list1.pop(ind)
print(list1)
A list comprehension is probably best practice, however this is how you could do this without one. What you are doing is iterating through the length of the initial list, but pop is shrinking your list so that the original indices are no longer applicable to the list (hence the error).
One way to do this is to use .remove on the list. This will remove the first element that matches the value, which will work in this case. This is more like a hack, because we use the list() function to return a copy of the list so that will iterate over the copy and get each value from the list while we modify it. If we didn't create a copy, the loop would skip some values. This is probably cleaner than the other methods posted here if you don't want a list comprehension or to create a new list (however you are essentially creating a new list with this method memory-wise).
lst = ["1", "-3", "-5", "-3", "10", "10", "11", "-4"]
for val in list(lst):
if int(val) < 0:
lst.remove(val)
else:
lst[lst.index(val)] = int(val)

Check if a list contains incrementing elements starting from zero

I want to check if a list contains incrementing elements, incremented by one each and starting with 0. So, e.g. [0, 1, 2, 3] should return "valid", whereas e.g. [0, 1, 3, 4] or [-1, 0, 1] should return "not valid".
Is there an easy way to achieve that in Python? Maybe a built-in function?
If the problem is truly as you describe it, this can be solved trivially using range, like so:
myList = [...]
if myList == list(range(myList[-1] + 1)):
# Do stuff.
How about using all :
>>> l = [0, 1, 2, 3]
>>> not l[0] and all(y-x==1 for x,y in zip(l, l[1:]))
True
>>> l1 = [0,1,2,3,5,7]
>>> not l[0] and all(y-x==1 for x,y in zip(l1, l1[1:]))
False
>>> l2 = [0,1,2,3,4,7]
>>> not l[0] and all(y-x==1 for x,y in zip(l2, l2[1:]))
False
>>> l3=[-1,0,1,2]
>>> not l[0] and all(y-x==1 for x,y in zip(l3, l3[1:]))
False
>>> l2 = [0,1,2,3,4,5,7]
>>> not l[0] and all(y-x==1 for x,y in zip(l2, l2[1:]))
False
>>> l4=[0,2,3,4,5,7,8]
>>> not l[0] and all(y-x==1 for x,y in zip(l4, l4[1:]))
False
>>> l5=[0,2,3,4,5,6,7,8]
>>> not l[0] and all(y-x==1 for x,y in zip(l5, l5[1:]))
False
So you can put this into a function, this way:
def check_my_list(lst):
if not lst:
print 'List Empty'
return False
test = not lst[0] and all(y-x==1 for x,y in zip(lst,lst[1:])
return test
I think it can be solved via a one-liner in a functional manner:
print not len(filter(lambda i: i!=-1, [x[i]-x[i+1] for i in range(len(x)-1)]))
given True for x = [0, 1, 2, 3] and False for x = [0, 1, 3, 4]
Explanation:
[x[i]-x[i+1] for i in range(len(x)-1)]
makes a list of differences between successive elements, obviously we want to have all -1...
Then we use filter to select those elements which are different from -1, if there is at least one, it is not a incrementing elements and so the length of such filtered list > 0
If yes, i.e. there are just -1, the field is empty and we get length of 0.

Summing a list of lists of lists?

I'm trying to sum a list of lists of lists in Python but I'm getting the wrong output. I want the number 36 as my answer but I'm getting the sum of each bracket.
>>> list = [[[1,2],[3,4]],[[5,6],[7,8]]]
>>> for xs in list[0::1]:
... for x in xs[0::1]:
... sum(x)
...
3
7
11
15
You could probably do this recursively, which would work for nested lists of arbitrary depth:
def add_all(l):
try:
return sum(add_all(i) for i in l)
except TypeError:
return l
print add_all([[[1,2],[3,4]],[[5,6],[7,8]]]) # 36
Here's a little more fancy way to do it:
You can use itertools.chain to remove one level of nesting from your list of lists:
>>> lst = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
>>> from itertools import chain
>>> list(chain(*lst)) # note: list != lst
[[1, 2], [3, 4], [5, 6], [7, 8]]
Now apply it twice and sum all items:
>>> sum(chain(*chain(*lst)))
36
You can also use list comprehension like this -
>>> lst = [[[1,2],[3,4]],[[5,6],[7,8]]]
>>> sum([x for i in lst for y in i for x in y])
36
Or
>>> sum(sum(y) for x in lst for y in x)
36
Also, just FYI list is a bad name for a variable, since it overwrites the built-in list function.
If there are n nested lists (arbitrary number) I cannot think of a way to achieve the sum through list comprehension , but a simple recursive algorithm that would do the trick is -
>>> def sumOfList(element):
... if isinstance(element, list):
... return sum([sumOfList(x) for x in element])
... elif isinstance(element, int):
... return element
...
>>> sumOfList([[[1,2,3,4],[1,2,3,4]],[1,2,3]])
26
You can create a recursive function like this:
def rsum(lst):
if type(lst) != list:
return lst
if len(lst)==1:
return rsum(lst[0])
return rsum(lst.pop(0))+rsum(lst)
The difference is that it works for a nested list of any depth
Assign sublists sums to a variable:
total = 0
for x in list: # x is list of lists
for y in x: # y is a list
total = total + sum(y)
print total

How to reduce lines with list comprehensions in Python?

I have some syntax :
l_one = [ x for x in someList ] # list with duplicates
l = []
l_two = [ l.append( y ) for y in l_one if y not in l ] # duplicates removed
Both l and l_two are the same lists without duplicates. Is there way to reduce lines and maybe have oneliner?
EDIT :
Correction - l_two are the "Noned" list.
Actually, they aren't the same. .append() returns None because it modifies the list in place, so l_two is a list with a bunch of Nones. However, l will be the list with no dupes.
If you want to remove duplicates from a list, you can make it into a set:
l_two = list(set(l_one))
Note that this will remove order.
Try using a for-loop instead of a list comprehension if you want to use unhashable types:
l_one = [x for x in someList]
l_two = []
for i in l_one:
if i not in l_two:
l_two.append(i)
Or:
from itertools import groupby
l_two = [key for key, value in groupby(l_one)]
IF I'm understanding correctly, you're starting with a list called someList which may have duplicates, and you want to end up with the same list but with duplicates removed?
You could start by removing your first line of code, which just duplicates someList into a new (but identical) list called l_one:
>>> someList = [ 3,1,4,1,5,9,2,7 ]
>>> l = []
>>> [ l.append(y) for y in someList if y not in l]
[None, None, None, None, None, None, None]
>>> print l
[3, 1, 4, 5, 9, 2, 7]
>>>
This works even if the elements of someList are themselves lists:
>>> l = []
>>> someList = [[1,2],[2,1],[1,2],[3,4]]
>>> l = []
>>> [ l.append(y) for y in someList if y not in l]
[None, None, None]
>>> print l
[[1, 2], [2, 1], [3, 4]]
>>>

How to re-assign items in a list in Python?

I want to re-assign each item in a list in Python.
In [20]: l = [1,2,3,4,5]
In [21]: for i in l:
....: i = i + 1
....:
....:
But the list didn't change at all.
In [22]: l
Out[22]: [1, 2, 3, 4, 5]
I want to know why this happened. Could any body explain the list iterating in detail? Thanks.
You can't do it like that, you are merely changing the value binded to the name i. On each iteration of the for loop, i is binded to a value in the list. It is not a pointer in the sense that by changing the value of i you are changing a value in the list. Instead, as I said before, it is simply a name and you are just changing the value that name refers to. In this case, i = i + 1, binds i to the value i + 1. So you aren't actually affecting the list itself, to do that you have to set it by index.
>>> L = [1,2,3,4,5]
>>> for i in range(len(L)):
L[i] = L[i] + 1
>>> L
[2, 3, 4, 5, 6]
Some pythonistas may prefer to iterate like this:
for i, n in enumerate(L): # where i is the index, n is each number
L[i] = n + 1
However you can easily achieve the same result with a list comprehension:
>>> L = [1,2,3,4,5]
>>> L = [n + 1 for n in L]
>>> L
[2, 3, 4, 5, 6]
For more info: http://www.effbot.org/zone/python-objects.htm
This is because of how Python handles variables and the values they reference.
You should modify the list element itself:
for i in xrange(len(l)):
l[i] += 1
>>> a = [1, 2, 3, 4, 5]
>>> a = [i + 1 for i in a]
>>> a
[2, 3, 4, 5, 6]
Initially i is a pointer to the item inside the list, but when you reassign it, it will point to the new number, that is why the list will not be changed.
For a list of mutable objects it would work:
class Number(object):
def __init__(self,n):
self.n=n
def increment(self):
self.n+=1
def __repr__(self):
return 'Number(%d)' % self.n
a = [Number(i) for i in xrange(5)]
print a
for i in a:
i.increment()
print a
But int are not mutable, when you do an operation on them you get a new int object, and that is why it doesn't work in your case.

Categories