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:
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)
>>> 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.
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)
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.