FAQ: Code Challenge: Loops - Delete Starting Even Numbers

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)`

1 Like

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.

1 Like

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)
``````
2 Likes

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.

1 Like

A post was merged into an existing topic: Why doesn’t iterating with `for` work while removing items from a list?

27 posts were merged into an existing topic: Why doesn’t iterating with `for` work while removing items from a list?

3 posts were split to a new topic: Infinite loop [solved]

4 posts were split to a new topic: How does break work? [solved]

8 posts were split to a new topic: Code challenge lists - improving my solution [solved]

3 posts were split to a new topic: Using continue in a while loop [solved]

I have a question
The following code below worked for me but i’m not exactly sure how.

def delete_starting_evens(lst):
for first_number in lst:
if lst[0] % 2 == 0:
lst.pop(0)
delete_starting_evens(lst)

return lst

It would be great if you explained to me how this worked and if this is a “good” way to solve problems like these.

the code above is the same code with indentation

Removing from the front of a list is as much work as making a copy of the whole list.
If you’re going to remove from a list, do it from the end. Otherwise, create a new list with the values you wish to keep.
Your function would be more efficient if you reversed the list, popped values from the end, then reversed it again. But it would be better yet to make a copy.

my first question is how do I reverse a list, and my second question is, I don’t exactly get what recalling my function inside of the function did other than the fact that it worked. Thanks for the reply though!

Code doesn’t accidentally do what I want. Code does what I want because I carefully describe what I want by combining smaller things that I know what they do.

In this case you are doing something with a list. What at all is a list, what can be done with a list in a sensible way? What takes a lot of work to do, and what can be done with almost no work at all?

The overall task. How much work does it take to do it manually? How would you represent it, what actions do you carry out? How is the result represented?

Does the incoming representation match this, can the same actions be carried out on it at the same cost? If yes, then do it, if not, then reconsider what you’re able to do with it.

If you draw a list starting from the left side of the edge of your paper. Then, how do you obtain another list that also starts at the left side of the paper, without the starting even values in the list? You clearly can’t just cross out the starting numbers, because that wouldn’t leave the start of the list at the left edge of the paper. You would have to move the values to the start of the left edge of the paper, staring from the first non-even value. How would you do that with the least amount of work? You’d have to copy them all over. Does a python list support those operations with the same amount of work used? If so, then that is what you would do.

Use things that you understand, and if you don’t understand something, learn it before using it. Keep firm grip on what you’re doing. Figuring out what the code should be comes from reasoning about what the things are and what can be done with them, and you can’t do that when you’re guessing.

Experimenting is fine and all but you need some firm ground to stand on for reasoning about what you’re doing, and it’s up to you to make sure that you’re standing on firm ground.

2 Likes

Thank you for replying! I get what your saying and next time i’ll be sure to stick with what i’m learning and if I do want to use another method i’ll be sure to learn if and have a soild grasp on it before I use it so I know what i’m doing. The only reason I came up with what I had is because I got help from my cousin but they never had time to fully explain what they did and how it works. I still do need to work on finding the most efficient way to solve these challenges. Thanks for all the help

A function might want to call itself if it encounters a subproblem that has the same shape as the overall problem. It’s important that the subproblem is smaller, or otherwise this would be a never-ending cycle.

After removing the first element, you would indeed have a problem of the same shape that is smaller. But if the function solves “the rest” by calling itself, then why would it continue looping after that? It should be one or the other.

Without the loop, that would be … not a great solution, but one that makes sense. It’s still not great because it is still removing from the front, and just like with the list-on-paper-starting-on-the-left-edge, that isn’t a cheap operation.
Like mentioned though, you could remove from the back instead, that doesn’t cause the start of the list to move, this is a cheap operation.
You’d still run into yet another problem, which is that python will only keep track of about 1000 calls in progress (can be increased, but not to large amounts like 100k, much less hundreds of millions)

Stick to incredibly basic operations. Fancy things are built from smaller things, you can build yourself up from what you understand to more powerful operations (and then you understand those too). If you for example need to reverse something, then you could do that by copying the whole list to get another list of the same size (or insert zeros or whatever, only the size matters since you’ll overwrite all of it), then iterating again but this time copying to the opposite end. Or, iterate backwards through the original list, and add each value to an initially empty list. After that you will have implemented reversing, and if you implemented it in a separate function then you’ve got a reusable function for reversing.

Then, when you understand how to write something you might start looking for shorter code doing the same thing, but that comes after understanding.

You said somewhere that the concepts make sense but that you didn’t know how to turn them into code. Well, I think you understand them at the wrong abstraction level. Break them down further until they are small enough problems that you know how to solve, and then join the solutions of those small problems together into increasingly more powerful solutions.

Getting the sum of digits might seem like something difficult to implement, but there are two obvious subproblems in that. Sum, and digits. Solve them separately, then combine `sum(digits(x))`
digits may also be opaque, but again, it breaks down. What is the number at the lowest digit? Get that, divide the rest by 10, do it again. Repeat (combine) to get all digits. Or, what is the ascii value of ‘9’? What values do ‘0’ ‘1’ … etc have, is there a pattern, can the integer digits be obtained from those values? Isn’t that what you do manually to compute digit sum? Look at each character and add up its corresponding value.

How you manually solve a problem is usually a good reference, or another way to put is that you should understand how to solve the problem before writing the code for the solution, otherwise you can’t possibly be writing code that solves the problem.

1 Like

FIrst of all. thank you so much for the detailed response. I’m glad they’re people like you that help other coders. I think I understand what i’m doing wrong now, whenever I approach a problem I start right away by trying to understand what i’m doing and then just try to put it in code. Next time i’ll try breaking the problem down and try figuring out the exact steps I would do to solve the problem and then start working on getting it down in code using the concepts I know, instead of finding a short-cut. I also just need to start “thinking like a programmer” and just not try to solve the problem but solve it efficiently using what I know. It might take a little bit and some effort to remember going through that thought process but I’m sure when I do get it a lot of these things that i’m missing will make alot more sense. I really appreciate all your help and i’m glad you responded and took time to answer

why does the loop skips 8 goes directly to 12(the element) and removes it?

When iterating a list we must keep in mind the effect of removing an element. If the element at the current position is removed, those to the right of it shift over to fill the vacancy, thus shortening the list, but also skipping the element that slid into the current position (which position has already been evaluated). The next element in the loop is two over from the one that was removed.

2 Likes

One of the condition in my while loop is len(lst) > 0 then how can we have [4,8,10] as empty list? If that condition is to have at least one element