Order of the conditions inside a python while loop - python

Why dose the order of the conditions inside a while loop create different results. For example
nums = []
k=3
while (nums[-1]<k and nums ):
print('*')
gives error list index out of range
But this one doesn't show any error
nums = []
k=3
while( nums and nums[-1]<k ):
print('*')

Yes it is expected behaviour, if an empty list is accessed by any index (-1 or any), it always throw exception reason being to access an empty list.
num[-1] represents the last element in the list. And num=[] is an empty list. Hence num[-1] will always throw exception in this case.
while nums and nums[-1]<k :
is safe condition as it first checks whether list contains elements or not, if it contains then only nums[-1] get executed.

Let's take a step back and look at this without using any loops. Assuming we have an empty list like below:
nums = []
What would you expect nums[0] to do? How about the below:
nums[-1]
In both cases above, an index error would be raised, because the list nums is empty (it contains no elements, so there are elements within nums to access).
Now let's make it a little more difficult. How about the following:
last_num = nums and nums[-1] or None
Actually, that should be same (roughly) as the below:
last_num = nums[-1] if nums else None
This above syntax is a common idiom that implements a "safety check", meaning it retrieves the last element if num is a non-empty list, or a default value of None otherwise.

Related

Unexpected error: "IndexError: list index out of range" when trying to access a list within a function

def myMax(L):
print(L)
f=len(L)
ind=0
maxx=L[0]
for i in range(len(L)):
if L[i]>maxx:
maxx=L[i]
ind=i
return (ind,maxx)
print(myMax([1, 2, -9, 10]))
print(myMax([]))
I am quite new to python. Above is code that I have written which takes a list as input and returns a tuple with the index of the highest number and the highest number itself.
For some reason I am getting an "IndexError:list index out of range" on line 5, where "maxx=L[0]"
Any help would be appreciated
You pass empty list : myMax([]) to the function and you point to the first element of this empty list L[0]. You need to check if the list L is not empty.
Adding the below as the first lines of the function will help you to protect against None or empty list
if L is None or len(L) == 0:
raise ValueError('Input list can not be None or empty')
Because you are calling the function on an empty list on the last line of your code.
print(myMax([]))
The argument you are passing to myMax function is [] that have length of zero.
When you call the fifth line your are actually trying to access the first element of the list, that does exist. In fact list in Python are indexed from 0 to n - 1 where n is the size of the list.
By default when you try to access an array on an index that it does not contain, the interpreter raise the exception IndexError: list index out of range. There are several ways to handle this cases for example the try except control, but in your case since the function in returning an integer I suggest you to append a control at the very end in order to return -1 in such cases.
def myMax(L):
if len(L) == 0:
return -1
# your code here
In this way you will not try an out of bound access and you have a return code representing the error occurred.

Number Filtration Algorithm bug

So I wrote this algorithm where given a set of integers it will remove all integers except 0 and 7 and then it will check if the remaining integers are in a certain order and then will return a boolean. Code below:
def spy_game(nums):
for i in nums:
if i != 0:
if i == 7:
continue
else:
nums.remove(i)
else:
continue
stringlist = [str(o) for o in nums]
mystring = ''.join(stringlist)
return '007' in mystring
spy_game([1,0,2,4,0,7,5])
Now the problem is that if I run
(for example) spy_game([1,0,2,4,0,7,5]) it will not return True regardless of the fact that the sequence of interest is present. After I decided to return the list per se after the filtration process, I found that all numbers except the ones in the middle got filtered out. So in this example, if I return nums it will return [0, 4, 0, 7] although the 4 should've been removed. I am aware that there are more optimal alternatives to this algorithm but I just want to understand why it doesn't work. Thank you.
Instead of modifying the list, use another list to keep track of the wanted numbers.
You should not modify the list while iterating on it.
Here's a cleaned up version
def spy_game(nums):
ans = []
for i in nums:
if i == 0 or i == 7:
ans.append(i)
stringlist = [str(o) for o in ans]
mystring = ''.join(stringlist)
return '007' in mystring
zenwraight's comment says what the problem is: in Python, you can't modify a list while iterating over it.
As for why, the Python documentation discusses this in a note on the for statement's section:
An internal counter is used to keep track of which item is used next, and this is incremented on each iteration. … This means that if the [loop body] deletes the current … item from the sequence, the next item will be skipped (since it gets the index of the current item which has already been treated).
The documentation also describes what happens when you insert an element during a loop, and suggests one possible solution (using a slice to copy the list: for i in nums[:]: ...). In your use case, that solution is likely to work fine, but it is considerably less efficient than options that don't copy the entire list.
A better solution might be to use another list comprehension:
nums = [i for i in nums if i == 0 or i == 7]

Python: My program is not executing a for loop properly

I have a list, num_list, which contains random numbers identified by using random.
One for loop is responsible for selecting numbers from the original list and my compare function is supposed to compare the obtained number with the numbers present in my new list. If the number is greater than the first number obtained from the for loop, I check if the same num is less than the next num in the new list. This means that the num is between the numbers and is therefore inserted there. However, I am stuck in a loop and can't understand what is going on. I have tried everything I know but, sadly, I'm still just a beginner. I hope someone here can make head or tail of this unfortunate mess.
new_list.append((num_list)[0])
def compare(num, new_list, index, temp_char):
for char in new_list:
print(char)
if num > char:
index = new_list.index(char)
try:
temp_char = (new_list)[index + 1]
if num <= temp_char:
new_list.insert(index+1, num)
except:
new_list.insert(index, num)
break
for num in num_list:
#print(num)
compare(num, new_list, index, temp_char)
print(new_list)
As Sayse said in comment, you shouldn't modify a list while iterating over it. This leads to undefined behavior. Instead, copy the list new_list before iterating over it. To copy the list, there are many solutions, but here's a simple one: copied = [i for i in new_list]
This won't work if your list is a nested list.

In this short recursive function `list_sum(aList)`, the finish condition is `if not aList: return 0`. I see no logic in why this condition works

I am learning the recursive functions. I completed an exercise, but in a different way than proposed.
"Write a recursive function which takes a list argument and returns the sum of its integers."
L = [0, 1, 2, 3, 4] # The sum of elements will be 10
My solution is:
def list_sum(aList):
count = len(aList)
if count == 0:
return 0
count -= 1
return aList[0] + list_sum(aList[1:])
The proposed solution is:
def proposed_sum(aList):
if not aList:
return 0
return aList[0] + proposed_sum(aList[1:])
My solution is very clear in how it works.
The proposed solution is shorter, but it is not clear for me why does the function work. How does if not aList even happen? I mean, how would the rest of the code fulfill a not aList, if not aList means it checks for True/False, but how is it True/False here?
I understand that return 0 causes the recursion to stop.
As a side note, executing without if not aList throws IndexError: list index out of range.
Also, timeit-1million says my function is slower. It takes 3.32 seconds while the proposed takes 2.26. Which means I gotta understand the proposed solution.
On the call of the function, aList will have no elements. Or in other words, the only element it has is null. A list is like a string or array. When you create a variable you reserve some space in the memory for it. Lists and such have a null on the very last position which marks the end so nothing can be stored after that point. You keep cutting the first element in the list, so the only thing left is the null. When you reach it you know you're done.
If you don't use that condition the function will try to take a number that doesn't exist, so it throws that error.
You are counting the items in the list, and the proposed one check if it's empty with if not aList this is equals to len(aList) == 0, so both of you use the same logic.
But, you're doing count -= 1, this has no sense since when you use recursion, you pass the list quiting one element, so here you lose some time.
According to PEP 8, this is the proper way:
• For sequences, (strings, lists, tuples), use the fact that empty
sequences are false.
Yes: if not seq:
if seq:
No: if len(seq)
if not len(seq)
Here is my amateur thougts about why:
This implicit check will be faster than calling len, since len is a function to get the length of a collection, it works by calling an object's __len__ method. This will find up there is no item to check __len__.
So both will find up there is no item there, but one does it directly.
not aList
return True if there is no elements in aList. That if statement in the solution covers edge case and checks if input parameter is not empty list.
For understand this function, let's run it step by step :
step 0 :
L=[0,1,2,3,4]
proposed_sum([0,1,2,3,4])
L != []
return l[0] + proposed_sum([1,2,3,4])
step 1 calcul proposed_sum([1,2,3,4]):
proposed_sum([1,2,3,4])
L != []
return l[0] + sum([2,3,4])
step 2 calcul proposed_sum([2,3,4]):
proposed_sum([2,3,4])
L != []
return l[0] + sum([3,4])
step 3 calcul proposed_sum([3,4]):
proposed_sum([3,4])
L != []
return l[0] + sum([4])
step 4 calcul proposed_sum([4]):
proposed_sum([4])
L != []
return l[0] + sum([])
step 5 calcul proposed_sum([]):
proposed_sum([])
L == []
return 0
step 6 replace:
proposed_sum([0,1,2,3,4])
By
proposed_sum([]) + proposed_sum([4]) + proposed_sum([3,4]) + proposed_sum([2,3,4]) + proposed_sum([1,2,3,4])+ proposed_sum([0,1,2,3,4])
=
(0) + 4 + 3 + 2 + 1 + 0
Python considers as False multiple values:
False (of course)
0
None
empty collections (dictionaries, lists, tuples)
empty strings ('', "", '''''', """""", r'', u"", etc...)
any other object whose __nonzero__ method returns False
in your case, the list is evaluated as a boolean. If it is empty, it is considered as False, else it is considered as True. This is just a shorter way to write if len(aList) == 0:
in addition, concerning your new question in the comments, consider the last line of your function:
return aList[0] + proposed_sum(aList[1:])
This line call a new "instance" of the function but with a subset of the original list (the original list minus the first element). At each recursion, the list passed in argument looses an element and after a certain amount of recursions, the passed list is empty.

Python - How to ensure all lengths of elements in a nested list are the same?

I have a function here:
def evenlengthchecker(nestedlist):
length = len(nestedlist[0])
for element in nestedlist:
if len(element) != length:
return False
This actually does work when the given nested list contains values. However, when I try something like evenlengthchecker([]), IndexErrors everywhere!
The problem is that your code starts by checking the list at position 0, which is an index error with an empty list. Here's an alternative method that won't give an error:
return (len(set(len(elt) for elt in nestedlist)) <= 1)
This just checks if there is more than one list length in the set of lengths; if you end up with the empty set, no harm done.
def evenlengthchecker(nestedlist):
a = [len(i) for i in nestedlist]
return len(set(a)) ==1
you can use all:
return all(len(x)==len(my_list[0]) for x in my_list)

Categories