diff --git a/asyncstdlib/builtins.py b/asyncstdlib/builtins.py index 5c8f54c..0ea160c 100644 --- a/asyncstdlib/builtins.py +++ b/asyncstdlib/builtins.py @@ -312,14 +312,14 @@ async def _min_max( raise ValueError(f"{name}() arg is an empty sequence") elif key is None: async for item in item_iter: - if invert ^ bool(item < best): + if best < item if invert else item < best: best = item else: key = _awaitify(key) best_key = await key(best) async for item in item_iter: item_key = await key(item) - if invert ^ (item_key < best_key): + if best_key < item_key if invert else item_key < best_key: best = item best_key = item_key return best diff --git a/unittests/test_builtins.py b/unittests/test_builtins.py index 382fddd..e11fb27 100644 --- a/unittests/test_builtins.py +++ b/unittests/test_builtins.py @@ -1,4 +1,5 @@ import random +from functools import total_ordering from typing import Any, Callable, Coroutine, TypeVar import pytest @@ -343,3 +344,47 @@ def collision_key(x: int) -> int: assert await a.sorted(values, key=awaitify(collision_key)) == sorted( values, key=collision_key ) + + +@total_ordering +class ComparableRecord: + def __init__(self, priority): + self.priority = priority + + def __eq__(self, other): + if not isinstance(other, ComparableRecord): + return NotImplemented + return self.priority == other.priority + + def __lt__(self, other): + if not isinstance(other, ComparableRecord): + return NotImplemented + return self.priority < other.priority + + +def record_priority(record): + return record.priority + + +@pytest.mark.parametrize( + "key, reference_key", + [ + (None, None), + (record_priority, record_priority), + (awaitify(record_priority), record_priority), + ], +) +@pytest.mark.parametrize("iterable", [iter, asyncify]) +@sync +async def test_max_keeps_first_equal_item(key, reference_key, iterable): + values = [ComparableRecord(value) for value in (1, 3, 3, 2)] + assert await a.max(iterable(values), key=key) is max(values, key=reference_key) + + +@pytest.mark.parametrize( + "values", + [[float("nan"), 3.0], [3.0, float("nan"), 2.0], [2.0, float("nan"), 3.0]], +) +@sync +async def test_max_does_not_replace_with_unordered_values(values): + assert await a.max(asyncify(values)) is max(values)