Skip to content

Instantly share code, notes, and snippets.

@rhettinger
Created November 18, 2021 01:50
Show Gist options
  • Star 14 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rhettinger/beb2f4a1d6d2ada70a468f51592e58cd to your computer and use it in GitHub Desktop.
Save rhettinger/beb2f4a1d6d2ada70a468f51592e58cd to your computer and use it in GitHub Desktop.
Type annotated pure python implementation of the builtin max() function
'Emulate max() as fully as possible in pure Python.'
# https://stackoverflow.com/questions/69997857/implementation-of-max-function-in-python/69997876#69997876
# https://github.com/python/mypy/issues/7231
from typing import TypeVar, Any, Iterator, Iterable, Optional
from typing import Union, Protocol, Callable, cast, Tuple, overload
class SupportsGT(Protocol):
def __gt__(self, other: Any) -> bool:
...
T = TypeVar('T', bound=SupportsGT)
D = TypeVar('D')
class Sentinel(object):
pass
sentinel = Sentinel()
def mymax_backend(
first_value: T,
it: Iterator[T],
key: Optional[Callable[[T], Any]],
) -> T:
largest = first_value
if key is None:
for x in it:
if x > largest:
largest = x
return largest
largest_key = key(largest)
for x in it:
kx = key(x)
if kx > largest_key:
largest = x
largest_key = kx
return largest
def mymax_argsonly(*args: T, key: Optional[Callable[[T], Any]] = None) -> T:
if not args:
raise TypeError('max expected at least 1 argument, got 0')
it = iter(args)
largest = next(it)
return mymax_backend(largest, it, key)
def mymax_iterable_only(
iterable: Iterable[T],
/,
*,
default: Union[Sentinel, D] = sentinel,
key: Optional[Callable[[T], Any]] = None,
) -> Union[D, T]:
it = iter(iterable)
largest = next(it, sentinel)
if isinstance(largest, Sentinel):
if not isinstance(default, Sentinel):
return default
raise ValueError('max() arg is an empty sequence')
return mymax_backend(largest, it, key)
@overload
def mymax(
iterable: Iterable[T],
/,
*,
default: Union[Sentinel, D] = sentinel,
key: Optional[Callable[[T], Any]] = None,
) -> Union[D, T]:
...
@overload
def mymax(
arg1: T,
arg2: T,
/,
*args: T,
default: Sentinel = sentinel,
key: Optional[Callable[[T], Any]] = None,
) -> T:
...
def mymax(
*args: Union[T, Iterable[T]],
default: Union[Sentinel, D] = sentinel,
key: Optional[Callable[[T], Any]] = None,
) -> Union[D, T]:
"""max(iterable, *[, default=obj, key=func]) -> value
max(arg1, arg2, *args, *[, key=func]) -> value
With a single iterable argument, return its biggest item. The
default keyword-only argument specifies an object to return if
the provided iterable is empty.
With two or more arguments, return the largest argument.
"""
if not args:
raise TypeError('max expected at least 1 argument, got 0')
if len(args) == 1:
iterable = cast(Iterable[T], args[0])
return mymax_iterable_only(iterable, default=default, key=key)
if default is not sentinel:
raise TypeError(
'Cannot specify a default for max() with multiple positional arguments'
)
values = cast(Tuple[T, ...], args)
return mymax_argsonly(*values, key=key)
assert mymax_argsonly('bob', 'adam') == 'bob'
assert mymax_argsonly('adam', 'bob') == 'bob'
assert mymax_argsonly('bob', 'adam', key=len) == 'adam'
assert mymax_argsonly('adam', 'bob', key=len) == 'adam'
assert mymax_iterable_only(['bob', 'adam']) == 'bob'
assert mymax_iterable_only(['adam', 'bob']) == 'bob'
assert mymax_iterable_only([], default=10) == 10
assert mymax_iterable_only(['bob', 'adam'], key=len) == 'adam'
assert mymax_iterable_only(['adam', 'bob'], key=len) == 'adam'
assert mymax('bob', 'adam') == 'bob'
assert mymax('adam', 'bob') == 'bob'
assert mymax('bob', 'adam', key=len) == 'adam'
assert mymax('adam', 'bob', key=len) == 'adam'
assert mymax(['bob', 'adam']) == 'bob'
assert mymax(['adam', 'bob']) == 'bob'
assert mymax([], default=10) == 10
assert mymax(['bob', 'adam'], key=len) == 'adam'
assert mymax(['adam', 'bob'], key=len) == 'adam'
assert mymax([10, 20]) == 20
assert mymax([20, 10]) == 20
assert mymax(10, 20) == 20
assert mymax(20, 10) == 20
assert mymax([10, 20, 30]) == 30
assert mymax([30, 20, 10]) == 30
assert mymax(10, 30, 20) == 30
assert mymax(20, 10, 30) == 30
@hXtreme
Copy link

hXtreme commented Nov 18, 2021

Alternative to line 32-36

if key is None:
    key = lambda x: x

Looks cleaner but probably has some performance impact which could be alleviated if python had a identity function built-in or in standard library.

@hXtreme
Copy link

hXtreme commented Nov 18, 2021

Possible issue on line 29

key: Optional[Callable[[T], Any]]

We would want the key to support __gt__ so the type should probably be something like following unless I'm missing something (very likely)

key: Optional[Callable[[T], T]]

@ZeeD
Copy link

ZeeD commented Nov 18, 2021

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment