Search a sequence for its minimum and stop as soon as the lowest possible value is found

Steve D'Aprano steve+python at pearwood.info
Sat Jan 7 03:01:27 EST 2017


On Sat, 7 Jan 2017 01:04 am, Peter Otten wrote:

> Example: you are looking for the minimum absolute value in a series of
> integers. As soon as you encounter the first 0 it's unnecessary extra work
> to check the remaining values, but the builtin min() will continue.
> 
> The solution is a minimum function that allows the user to specify a stop
> value:
> 
>>>> from itertools import count, chain
>>>> stopmin(chain(reversed(range(10)), count()), key=abs, stop=0)
> 0
> 
> How would you implement stopmin()?

That depends on whether you want to stop when you find a value *equal* to
the stop value, or *less than* the stop value, or both.

It depends on whether you want to check for equality or check for any
arbitrary condition.

It depends on whether you want the stop value to be included or not. (It
looks like you do want it included.)

But I'd start with this:


# not tested
def stopmin(iterable, key=None, stop=None):
    it = iter(iterable)
    try:
        smallest = next(it)
    except StopIteration:
        raise ValueError('empty iterable has no minimum')
    else:
        if key is not None:
            keyed_smallest = key(smallest)
    if key is None:
        for x in iterable:
            if x < smallest:
                smallest = x
            if x == stop:
                break
    else:
        for x in iterable:
            y = key(x)
            if y < keyed_smallest:
                keyed_smallest = y
                smallest = x
            if y == stop:
                break
    return smallest


Another possibility is to create a variant of itertools takewhile:

def takeuntil(predicate, iterable):
    # takeuntil(lambda x: x<5, [1,4,6,4,1]) --> 1 4 6
    for x in iterable:
        yield x
        if predicate(x):
            break

min(takeuntil(lambda x: x == 0, iterable), key=abs)


py> from itertools import count, chain
py> iterable = chain(reversed(range(10)), count())
py> min(takeuntil(lambda x: x == 0, iterable), key=abs)
0
py> iterable = chain(range(-9, 10), count())
py> min(takeuntil(lambda x: x == 0, iterable), key=abs)
0




-- 
Steve
“Cheer up,” they said, “things could be worse.” So I cheered up, and sure
enough, things got worse.



More information about the Python-list mailing list