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

Peter Otten __peter__ at web.de
Sat Jan 7 06:55:12 EST 2017

```Steve D'Aprano wrote:

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

Must test for stop here.

>     if key is None:
>         for x in iterable:

Replace `iterable` with `it` in the for loops.

>             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

The whole distraction started when I wanted to avoid the straight-forward
thing ;)

As it turns out there is not as much legwork as I expected, particularly as
I'm not interested in the no-key branch.

> 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

```