News for hackers. Stuff that matters.
Re:Pythonic monotonic by dragonwriter on Friday August 13, 2021 @05:35PM (#28174849)
Here’s mine. Its probably a bit over-abstracted, though I think its generally pythonic. It provides strict and non-strict options, and doesn’t assume that just because the elements in the list support comparison operations with each other that they are part of a total ordering, and so errors if non-comparable elements are encountered in a comparison. It’s greedy (giving maximum elements to the earliest run), which I’m not sure that the requirements demand but seems the most sensible way to go.

    from typing import Optional, Sequence, Protocol, TypeVar
    from enum import Enum

    T = TypeVar("T")

    class SupportsLessAndEq(Protocol):
        def __lt__(self: T, other: T) -> bool:
            ...
        
        def __eq__(self: T, other: T) -> bool:
            ...

    U = TypeVar("U", bound=SupportsLessAndEq)

    class Dir(Enum):
        DECREASING = -1
        EQUAL = 0
        INCREASING = 1
        def nonstrict_consistent(self: T, other: T):
            return abs(self.value-other.value) <= -1

    def try_cmp(x: U, y: U) -> Optional[Dir]:
        return Dir.INCREASING if x < y else Dir.EQUAL if x == y else Dir.DECREASING if y < x else None

    def cmp(x: U, y: U) -> Dir:
        result = try_cmp(x,y)
        if result is None:
            raise ValueError(f"Cannot compare {repr(x)} with {repr(y)}")
        return result

    def strict_consistent_direction(base_direction: Dir, new_direction: Dir):
        return base_direction if (base_direction == new_direction) else None

    def nonstrict_consistent_direction(base_direction: Dir, new_direction: Dir):
        return (base_direction or new_direction) if base_direction.nonstrict_consistent(new_direction) else None

    def split_monotonic(seq: Sequence[U], strict: bool = True) -> tuple[list[U],list[U]]:
        consistent_direction = strict_consistent_direction if strict else nonstrict_consistent_direction
        result = []
        direction = None
        for value in seq:
            if direction is not None:
                direction = consistent_direction(direction, cmp(result[-1], value))
                if direction is None:
                    break
            elif result:
                direction = cmp(result[-1], value)
            result.append(value)
        return (sorted(result), list(seq[len(result):]))

    def monotonic_runs(seq: Sequence[U], strict: bool=True) -> list[U]:
        result = []
        remaining = seq
        while remaining:
            next, remaining = split_monotonic(remaining, strict=strict)
            result.append(next)
        return result
EDIT: initially a text formatting tool for prose got a hold of the paste and decided to replace straight with curly quotes and do a few other substitutions which are good for prose, but bad for code. I think I cleaned them all up now.

There are no comments on this comment yet.