There are many, many different ways to do this. If I had to pick a favourite for legibility, it would be to apply a single max
to a generator that reverses the key and value (max_gen
, below). If you need to squeeze out every last microsecond of performance, writing your own loop is faster. Whatever you do, don't call max
twice.
Side-by-side
from functools import partial
from operator import itemgetter
from random import shuffle
from timeit import timeit
from typing import Dict, Tuple
from uuid import uuid4
AgeDict = Dict[
str, # name
int, # age, years
]
AgeTuple = Tuple[str, int]
def max_getter(people: AgeDict) -> AgeTuple:
return max(people.items(), key=itemgetter(1))
def max_lambda(people: AgeDict) -> AgeTuple:
return max(people.items(), key=lambda kv: kv[1])
def max_value(people: AgeDict) -> AgeTuple:
max_v = max(people.values())
max_k = next(k for k,v in people.items() if v==max_v)
return max_k, max_v
def max_gen(people: AgeDict) -> AgeTuple:
# My favourite for legibility
age, name = max((age, name) for name, age in people.items())
return name, age
def max_loop(people: AgeDict) -> AgeTuple:
items_iter = iter(people.items())
max_name, max_age = next(items_iter)
for name, age in items_iter:
if max_age < age:
max_age = age
max_name = name
return max_name, max_age
def max_invert(people: AgeDict) -> AgeTuple:
inverted = {v: k for k, v in people.items()}
max_age = max(inverted.keys())
return inverted[max_age], max_age
METHODS = (
max_loop,
max_value,
max_getter,
max_gen,
max_lambda,
max_invert,
)
def test():
d = {'a': 3, 'b': 1, 'c': 4, 'd': 2}
for method in METHODS:
assert method(d) == ('c', 4)
def time():
n = 10_000
ages = list(range(n))
shuffle(ages)
people = {uuid4(): age for age in ages}
reps = 200
for method in METHODS:
print(f'{method.__name__:16}', end=' ')
f = partial(method, people)
t = timeit(f, number=reps) / reps
print(f'{t*1e6:>4.0f} us')
if __name__ == '__main__':
test()
time()
Output
max_loop 388 us
max_value 458 us
max_getter 571 us
max_gen 976 us
max_lambda 976 us
max_invert 961 us