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 07:15:22 EST 2017


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:

Thanks everyone who contributed to this thread!
For those interested here's my collection of stopmin() implementations so 
far. Remember, there should be one ... *obvious* way to do it (emphasis 
mine).
$ cat stopmin_simple.py 
import functools
import operator
import re
from itertools import groupby
from functools import lru_cache
def steal_doc(f):
 def set_doc(g):
 sub = re.compile(r"\b{}\b".format(f.__name__)).sub
 g.__doc__ = sub(g.__name__, f.__doc__)
 return g
 return set_doc
class Stop(Exception):
 pass
def stopmin(items, *, key, stop):
 """
 >>> def g():
 ... for i in reversed(range(10)):
 ... print(10*i)
 ... yield str(i)
 >>> stopmin(g(), key=int, stop=5)
 90
 80
 70
 60
 50
 '5'
 >>> stopmin(g(), key=int, stop=8.5)
 90
 80
 '8'
 >>> stopmin(g(), key=int, stop=9)
 90
 '9'
 >>> stopmin([10, 9, -11, 7, -12], key=abs, stop=0)
 7
 >>> try: stopmin([], key=abs, stop=0)
 ... except ValueError: print('OK')
 OK
 >>> stopmin("a", key=lambda x: print(x) or "c", stop="b")
 a
 'a'
 """
 def key2(value):
 result = key(value)
 if result <= stop:
 raise Stop(value)
 return result
 try:
 return min(items, key=key2)
 except Stop as stop:
 return stop.args[0]
def takeuntil(data, pred):
 '''Take values from data until and including the first that
 satisfies pred (until data is exhausted if none does).'''
 for kind, group in groupby(data, pred):
 if kind:
 yield next(group)
 break
 else:
 yield from group
@steal_doc(stopmin)
def stopmin_jussi(data, *, key, stop):
 key = lru_cache(maxsize=1)(key)
 return min(
 takeuntil(data, lambda o: key(o) <= stop),
 key=key
 )
@steal_doc(stopmin)
def stopmin_wolfgang(iterable, *, key, stop):
 key = lru_cache(maxsize=1)(key)
 def take_until():
 for e in iterable:
 yield e
 if key(e) <= stop:
 break
 return min(take_until(), key=key)
firstitem = operator.itemgetter(0)
@steal_doc(stopmin)
def stopmin_du(items, *, key, stop):
 # decorate, process, undecorate
 pairs = ((key(item), item) for item in items)
 pairs = takeuntil(pairs, lambda pair: pair[0] <= stop)
 return min(pairs, key=firstitem)[1]
@functools.total_ordering
class Pair:
 __slots__ = ["key", "value"]
 def __init__(self, key, value):
 self.key = key
 self.value = value
 def __lt__(self, other):
 return self.key < other.key
@steal_doc(stopmin)
def stopmin_du2(items, *, key, stop):
 pairs = (Pair(key(item), item) for item in items)
 pairs = takeuntil(pairs, lambda p: p.key <= stop)
 return min(pairs).value
@steal_doc(stopmin)
def stopmin_steve(iterable, *, key, stop):
 it = iter(iterable)
 try:
 smallest = next(it)
 except StopIteration:
 raise ValueError('empty iterable has no minimum')
 keyed_smallest = key(smallest)
 if keyed_smallest <= stop:
 return smallest
 for x in it:
 y = key(x)
 if y < keyed_smallest:
 keyed_smallest = y
 smallest = x
 if y <= stop:
 break
 return smallest
@steal_doc(stopmin)
def stopmin2(items, key, stop):
 pairs = ((key(item), item) for item in items)
 try:
 minkey, minval = next(pairs)
 except StopIteration:
 raise ValueError("stopmin2() arg is an empty sequence")
 if minkey <= stop:
 return minval
 for k, v in pairs:
 if k < minkey:
 if k <= stop:
 return v
 minkey = k
 minval = v
 return minval
$ pep8 stopmin_simple.py
$ python3 -m doctest stopmin_simple.py


More information about the Python-list mailing list

AltStyle によって変換されたページ (->オリジナル) /