FAQ Hint: Be aware of nesting iteratives and loops

Concept

As per this lesson, you may find yourself wanting to combine loops like for and while. However in doing so, it is important to understand how they will interact when nested.

Mechanics

There are a few key issues that I have seen pop up regularly in questions:
The first if you have two for loops, the inner for loop will run completely for each step in the outer for loop. Example:

list1 = [1,2,3]
list2 = ['a', 'b', 'c']
for n in list1:
  for L in list2:
    print(str(n) + L)
#output: 1a, 1b, 1c, 2a, 2b, 2c, 3a, 3b, 3c

As you can see, it cycles through all of the second loop before moving on to #2 in the first loop. This also demonstrates that a variable used in the outer loop can be used in the inner loop. Variables from the inner loop can be used in the outer loop but only after being defined (this could be useful if you were creating a list for example).
On the other hand, an outer while will not be checked during the running of an inner loop.

i = 0
while i < 2:
  for n in range(0,6):
    i = n
    print(i)
#output: 0, 1, 2, 3, 4, 5

As you can see, the inner for loop will run to completion ignoring the while loop even though you may have expected it to stop once i was 2.

These are just a couple examples that will hopefully help solve issues with your own code. Please discuss below and help each other to further solve challenges that arise.

1 Like

@catower, great comments!.

It’s also well to note that (unlike in some other languages) you can’t adjust the value of the iteration variable from within a for loop, as you can from within a while loop.

for i in range(0,3):
    print(i)
    i = i + 10
    print(i)

Output:

0
10
1
11
2
12

Here, at least within the loop, range() keeps an iron grip on the value of i.

8 Likes

Indeed. :+1:

This is because the for implementation in Python leverages an internal counter to track which item from the iterable sequence comes next, and overwrites your target variable (i in your example) with that value each iteration until it reaches the end of the iterator.

This is why you get odd behaviour in some cases, for example if you remove items from a list while iterating over it.

9 Likes

So I completely misinterpreted the question and thought I was supposed to delete all the elements which are even numbered, and it sent me down a rabbit whole for a few days. I feel like the more I progress, the more the questions become more obtuse.

Anyway, I would like to share all my failures in order to help those who come after. Ultimately, I don’t think I solved it the way CA intended it to be solved. But hey, it works

#Write your function here
def delete_starting_evens(lst):
  index = 0
  for element in range(len(lst)):    
    if lst[element] % 2 == 0:
      index += 1
    else:
      break
  del lst[:index]
  return lst
  
      

"""def delete_starting_evens(lst):
  index = -1
  while index >= 0:
    for element in range(len(lst)):
      print(lst)
      if lst[element] % 2 == 0:
        del lst[element]        
      index +=1
    return lst"""

"""def delete_starting_evens(lst):
  index = -1
  for element in range(len(lst)):
    #print(lst[element])   
    if lst[element] % 2 == 0:
      #print(lst[element])
      index += 1
      #print(index)
  print(index)
  print(lst[:index])
  del lst[:index]
  return lst"""
      
      
"""
  element += 1
  del lst[:element]
  return lst"""


"""def delete_starting_evens(lst):
  for element in range(len(lst)):
    if lst[element] % 2 == 0:
      print(lst[element])
      continue
    else:      
      return lst"""

"""def delete_starting_evens(lst):
  while lst:
    for element in range(len(lst)):
      if lst[element] % 2 == 0:
        del lst[element]
      else:
        break
  return lst"""

"""def delete_starting_evens(lst):
  for element in reversed(range(len(lst))):
    if lst[element] % 2 == 0:
      del lst[element]
      # print(lst[element])
  return lst"""

#Uncomment the lines below when your function is done
print(delete_starting_evens([4, 8, 10, 11, 12, 15]))
print(delete_starting_evens([4, 8, 10]))







1 Like

The word “delete” is a bit misleading.
Generally a function or method should either modify something, or return a new something. Not both. If it does both then the user might use one but not account for the other, and it’s pretty weird to present the result in two ways when there is only one result.

Unless the intention is specifically to modify an existing value, then you’ll want to create a new value and leave the input unchanged.

So the spirit of the function then is to create a list that is a copy of the original, but without leading even values.

Your function is easily amended by changing this:

  del lst[:index]
  return lst

To not modify the original, and instead return a copy from index and onwards.

It’s also worth noting that this easily breaks into two separate and composable concepts, dropwhile, and a condition, so this would be one implementation:

from functools import partial

def dropwhile(p, xs):
    res = list(reversed(xs))
    while res and p(res[-1]):
        res.pop()
    return list(reversed(res))

def even(x):
    return x % 2 == 0

delete_starting_evens = partial(dropwhile, even)
1 Like

Oh, oh, I spend alot of time to solve this task.
In most I hade an Empty value
And I got it to return of a list to the funktion and move the indent to

def delete_starting_evens(lst):
    while len(lst) > 0 and lst[0] % 2 == 0:
      lst = lst[1:]
    return lst  
3 Likes

Your post helped clear things up. I think I was under the same impression as
@kurthamm5378307535.

Why doesn’t this work?

def delete_starting_evens(lst):
  for i in lst:
    if i % 2 == 0:
      lst.remove(i)
    else:
      break
  return lst

if you change the thing you’re looking at then it’ll look different. you’d need to account for those changes when looking at it or change a separate value
list.remove isn’t what you want anyway

Can please somebody kindly explain why do we need to know the length of the list in order to make this function to work properly? Thank you!

1 Like

It depends a little on which solution you are looking at (if it’s not the while loop then my apologies, a short piece of the code that’s troubling would help).
If it’s a while loop, len(lst) is used to avoid throwing errors. This would occur in an instance where every value in lst is even. Without checking to see if the list is still populated with len(lst) > 0, slicing and methods such as .pop() would throw index errors.

You could skip the len() calls as with Python empty lists along with similar types have a false boolean value and you could use this to check if the list was still populated such that-

lst =
bool(lst) == False
if lst: print(‘test’)

Would print nothing, whereas

lst = [‘b’]
bool(lst) == True
if lst: print(‘test’)

Would print ‘test’.

Whether this is more or less readable is a little up for debate-

2 Likes

That’s exactly the answer I was looking for. I think i get it now, thanks! Will also read through the link you shared

1 Like

I could not grasp the logic on this example.

i = 0
while i < 2:
  for n in range(0,6):
    i = n
    print(i)
#output: 0, 1, 2, 3, 4, 5

I thought if n=i and when n gets to 2, n should stop

Hi @jay4jay4.

Check the nesting on these loops. The for loop is separate to the while statement whilst it runs. The conditional i < 2 is checked once at the start of the while loop when i == 0 which evaluates to True. Code inside the while loop is then executed (it does not check the value of i again until this has finished).

For loop runs and prints 0 through to 5 (i does change but it is never checked against anything). Once the for loop has finished i == 5. As the code inside the while loop has now been executed it starts it’s next iteration and the the check for the while loop is performed again i < 2 which is now False since i == 5.

So I think your issue is that you expect the while loop conditional i < 2 is checked more often than it is.

1 Like

its cleared now thx a lot

1 Like

So the code I wrote for this was

def delete_starting_evens(lst):
  
for num in lst:

    if num % 2 == 0:

      lst = lst[1:]

      return lst

[8, 10, 11, 12, 15]
[8, 10]

I got really stuck and use the give me the solution feature. I have a few questions with the solution.
1, why do we have to use the len( ) feature?
2, what was going on with my solution to cause it to not work properly?
3, why do we have to use the while loop?

I think the issue here is misunderstanding the use of return. Once that line is executed your function will completely finish at that point. Consider what happens for example with that first list, [8, 10, 11, 12, 15]. Where does the code reach before exiting? Perhaps this could be rearranged to avoid this issue.

Using len and while is one method of solving this problem. With a for loop the loop will run through every element in the iterable, you’d need to make sure iteration ended at a certain point, with return or break for example. Consider how that solution matches up against a while loop.

1 Like

So I took out ‘return’, and it returns none for both. The ‘for’ loop goes through each item in the list. My ‘if’ statement then checks to see if it is even. If so, it removes it from the list. (I may be interpreting the ‘lst[1:]’ wrong. If so please tell me.) What do I have to put in so it will show each odd number?

The issue I described with return is that it exits the function at that exact point in the function. Without a valid return the function will always return None. Take the time in your head or on a piece of paper (ideally) to work out how your original loop works with the return statement you had. When you work out how the logic of the program actually proceeds then you may decide to alter it.

def myfunc():
    for x in range(10):
        return x
        ... other bits

print(myfunc())
1 Like