FAQ: Code Challenge: Loops - Delete Starting Even Numbers


#43

I’m not sure what you disagree with or what your counter-argument is :^)


#45

You seemed to be asking if it was reasonable.
Upon reading it, there are some things that come to mind about how it could be changed, this is without putting any thought into it, it’s a reaction.

If I write a math expression like so:

((((((1))))))

Then you’d have a similar reaction, you’d want to change it into

1

before doing anything else, there is no reason to write it that way. After cleaning it up, it’ll be easier to continue.

I don’t think it’s inappropriate to note that something could be written simpler, especially when at the same time motivating how it could be done.


#47

Do you intimidate easily? That would be the only reason to shy away.

First rule in learning anything is to be able to take criticism. It’s how we learn from our peers, or future peers (the ones we see as mentors today).

Second rule in learning is never be afraid to be wrong.

Third rule is never be afraid to admit it and take note of others’ contributions in pointing it out.

Not that any accusations are being leveled here by any stretch. Just the ravings of an old man cast upon the waters.


#49

im having a tough time learning loops, could anyone tell me why my solution doesnt work?

#Write your function here
def delete_starting_evens(lst):
if len(lst) > 0:
while lst[0] % 2 == 0:
lst = lst[1:]
return lst

I can see how checking both the len and % in one line is better but im not sure why mine doesnt work as they both look very similar

#def delete_starting_evens(lst):
#while (len(lst) > 0 and lst[0] % 2 == 0):
#lst = lst[1:]
#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]))


#50
>>> [10][1:]
[]
>>> [][0]
Traceback (most recent call last):
  File "<pyshell#0>", line 1, in <module>
    [][0]
IndexError: list index out of range
>>> 

Study this as it relates to your code. The answer is in there.


#51

#Write your function here
def delete_starting_evens(lst):
while (len(lst) > 0 and lst[0] % 2 == 0):
lst.pop(0)
return lst

def dse(lst):
for i in lst:
if len(lst) > 0 and lst[0] % 2 == 0:
lst.pop(0)
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]))
print(dse([4, 8, 10]))

I tried it with the for loop in function defined as dse() and it does not pop out the 10 of the list, could anyone explain why is that? thank you


#52

When i is a positional element and we pop that element, all the elements to the right of that get shifted. The next element to be iterated in the list will be from one more to the right that it was expected to be, hence an element is skipped.

4, 8, 10    # i is 4
pop(0)
8, 10       # i is 10
pop(0)
10          # i is None

The return value will be [10].


#53

I have the same question, if there is only one element in the list why doesn’t lst[1:] throw an IndexError?

# Codecademy's supplied solution

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

print(delete_starting_evens([4, 8, 10, 11, 12, 15])) # [11, 12, 15]
print(delete_starting_evens([4, 8, 10])) # []

With respect to the second invocation, this is what the list looks like after each iteration of the while loop:

[4, 8, 10] # Original list

[8, 10] # After first iteration
[10] # After second iteration
[] # After third iteration

Why does attempting to reference lst[1] not fail after the second iteration?


#54

lst[1] would give an index error, lst[1:] doesn’t. Python does some clever magic to return an empty list.


#55

I see, I didn’t expect to find magic in Python… the lesson on list slicing should really mention this.


#56

i am not sure how under the hood python is managing this, so i designated it magic. But it seems list slicing can handle highest index + 1 to give back an empty list.

maybe @mtf knows?


#57

I don’t think it is magic, or even secret sauce. A slice is a copy, not the actual list. As such it is an empty list until populated.


#58

Because starting at index 1 and iterating to the end results in zero iterations, nothing weird about it

However, you’ll still only get as much as there is if you explicitly specify a range that isn’t there, so the bounds are rather soft/forgiving and I’m not sure if I like that or not, not sure if there are cases where this helps, maybe there is. It hasn’t bothered me or given me any trouble though so I have no complaints. You can think of it as specifying whatever is there, if anything.

One case where you indeed may want to specify locations that don’t exist is when you’re assigning to those locations, maybe symmetry with that is one reason for the soft bounds.


#59

The first solution that I have put #'s before works.
Why does the second solution leave [10] from a list of [4, 8, 10]? Why doesn’t it remove the 10 as well and give me ?

bank = [4]
bank.pop(0)
print(bank)
#the above gives me , so why does .pop(0) in a loop leave one number in the list?


#60

looping over the same list as you are removing from is problematic.

lists can’t have empty spaces/slots, so the empty slot which was caused by the removal of an element is filled, while the loop continues to the next index, causing a value to be skipped


#61

For anybody exploring the multitude of possible solutions for this problem, here is another one to add to the pile…

try/except with `next()`
>>> def delete_starting_evens(lst):
	try:
		return lst[lst.index(next(x for x in lst if x % 2)):]
	except StopIteration:
		return []

	
>>> a = [10,2,30,4,51,60,75]
>>> b = [10,2,30,4,52,60,76]
>>> delete_starting_evens(a)
[51, 60, 75]
>>> delete_starting_evens(b)
[]
>>> 

It breaks down like this,

breakdown
>>> try:
	y = next(x for x in a if x % 2)
	z = a.index(y)
	print (a[z:])
except StopIteration:
	print ([])

	
[51, 60, 75]
>>> try:
	y = next(x for x in b if x % 2)
	z = b.index(y)
	print (b[z:])
except StopIteration:
	print ([])

	
[]
>>> 

Some things above may not yet have surfaced in this course:

  1. list.index()
  2. Handling exceptions with try...except.
  3. The next() function

That being the case, one may wish to cycle back to this solution when those topics come up or on review after completing the course.


#62

Hm. I’m not sure I agree with both using an iterator and then going back to the start to search for the index - I would rather only consume from the iterator, and anything that has been dropped can be forgotten.

If there’s a rope sticking out of a wall (an iterator), then I could pull at it until there’s a non-even value, and then either turn the rest into a list, or let whoever wants the remaining values pull out the rest of the rope when and if they want it.

Sidenote:
Almost cheating, but interesting enough, and possibly not immediately obvious even knowing it exists - itertools.dropwhile implements a more general version of this problem: dropwhile(even, lst)


#63

itertools.dropwhile

Interesting. Thanks for that.

One could use range to expose the indices.

with `range()`
>>> def delete_starting_evens(lst):
	try:
		return lst[next(i for i in range(len(lst)) if lst[i] % 2):]
	except StopIteration:
		return []

	
>>> delete_starting_evens(a)
[51, 60, 75]
>>> delete_starting_evens(b)
[]
>>> 

Or, use enumerate to expose both index and value…

with `enumerate()`
>>> def delete_starting_evens(lst):
	try:
		return lst[next(i for i, x in enumerate(lst) if x % 2):]
	except StopIteration:
		return []

	
>>> delete_starting_evens(a)
[51, 60, 75]
>>> delete_starting_evens(b)
[]
>>> 

Either of which make more sense than the earlier example given your advice.


#64

That’s a smaller amount of loops.
If you’re going to do list slicing, I’d really like to see finding the start location as a separate step (separate line, put result in variable)
And then, if you’re going to use iterators as a strategy of finding the first value - fine.

I'm looking for something more like this for using iterators
def delete_starting_evens(xs):
    xs = iter(xs)
    for x in xs:
        if x % 2 != 0:
            yield x
            yield from xs
            return
Or this, using find and slice
def delete_starting_evens(xs):
    for i, x in enumerate(xs):
        if x % 2 != 0:
            return xs[i:]
    return []
Or after abstracting out some stuff - if the language doesn't come with the kind of words to describe what's wanted then putting it all in one function may make it a bit difficult to read. Defining the words you want allows for expressing things in nicer ways.
def even(n):
    return n % 2 == 0


def not_(f):
    def f_(*args, **kwargs):
        return not f(*args, **kwargs)
    return f_


def find(f, xs):
    for i, x in enumerate(xs):
        if f(x):
            return i
    return i+1


def delete_starting_evens(xs):
    first_non_even = find(not_(even), xs)
    return xs[first_non_even:]
But obviously the right word here already exists in dropwhile, to the point where this whole function shouldn't exist at all, dropwhile covers it.
from itertools import dropwhile


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


def delete_starting_evens(xs):
    return dropwhile(even, xs)

#65

The second example is how I originally solved this one. Love going down the rabbit hole and seeing what pops up.

Thanks again for the great examples; and, I won’t argue with your last point. It makes the most sense. You’d think I had an aversion to libraries, or something the way I never reach for them.