News for hackers. Stuff that matters.
Re:Pythonic monotonic by eesmith on Friday August 13, 2021 @10:41AM (#28169477)
My updated version is more like a state machine. Your function and mine give the same answers both with the manual test cases and with a cross-comparison using random inputs:

    def monotonic_direct(seq):
        prev_value = None
        prev_dir = 0  # 0 for increasing or decreasing, 1 for increasing, -1 for decreasing
        group = []
        #print("Run", seq)
        for value in seq:
            #print(prev_dir, prev_value, value)
            if not group:
                # Can only get here with the first element
                prev_value = value
                group.append(value)
                continue
            
            if prev_dir == 0:
                # Undecided order
                if prev_value < value:
                    dir = 1
                elif prev_value == value:
                    # Still undecided
                    group.append(value)
                    continue
                else:
                    dir = -1
                    
            elif prev_dir == 1:
                # Monotonic increasing
                if prev_value <= value:
                    dir = 1
                else:
                    dir = 0 # reset for the next run
                    
            elif prev_dir == -1:
                if prev_value < value:
                    dir = 0
                else:
                    dir = -1 # reset for the next run

            if prev_dir == 0 or dir == prev_dir:
                # Still in the same direction
                group.append(value)
            else:
                # New direction
                if prev_dir == -1:
                    group.reverse()
                yield group
                group = [value]
                
            prev_value = value
            prev_dir = dir

        if group:
            if prev_dir == -1:
                group.reverse()
            yield group

    def run_test_cases():
        for dir_case, expected in (
                ([1], [[1]]),
                ([1, 2], [[1, 2]]),
                ([2, 1], [[1, 2]]), # because of the reverse
                ([1, 2, 3], [[1, 2, 3]]),
                ([1, 3, 2], [[1, 3], [2]]),
                ([1, 1, 2], [[1, 1, 2]]),
                ([1, 1, 0], [[0, 1, 1]]),
                ([1, 1, 2, 0], [[1, 1, 2], [0]]),
                ([1, 1, 0, 0], [[0, 0, 1, 1]]),
                ([3, 3, 2, 2, 1, 1], [[1, 1, 2, 2, 3, 3]]),
                
                #([3, 3, 2, 3, 1, 1], [[2, 3, 3], [3], [1, 1]]),
                ([3, 3, 2, 3, 1, 1], [[2, 3, 3], [1, 1, 3]]),

                ([1, 2, 3, 2, 1, 4, 5, 6, 7], [[1, 2, 3], [1, 2], [4, 5, 6, 7]]),
                ("AABCBADCABC", [["A", "A", "B", "C"], ["A", "B"], ["A", "C", "D"], ["B", "C"]]),
                ):
            got = list(monotonic_direct(dir_case))
            assert got == expected, (dir_case, got, expected)
        print("All test cases passed.")

    def run_cross_test():
        import random
        for i in range(10000):
            n = random.randrange(2, 10)
            values = [random.randrange(10) for _ in range(n)]
            result1 = list(monotonic_direct(values))
            result2 = list(monotonic_increasing(values))
            assert result1 == result2, (values, result1, result2)
        print("Random cross-comparison tests passed.")
    
    if __name__ == "__main__":
        run_test_cases()
        run_cross_test()
Re:Pythonic monotonic by klyrs on Friday August 13, 2021 @03:31PM (#28173469)
I came up with a similar solution; using True/None/False instead of 1/0/-1 allows for more self-documenting code

    def monotonics(seq):
        run = []
        last_v = None
        increasing = None
        for v in seq:
            if last_v is None:
                pass
            elif increasing is None:
                if v != last_v:
                    increasing = v > last_v
            elif increasing:
                if v < last_v:
                    increasing = None
                    yield run
                    run = []
            else:
                if v > last_v:
                    increasing = None
                    yield run[::-1]
                    run = []
            last_v = v
            run.append(v)

        if run:
            yield run
and, I'd add a two more test cases: [], and [1, 2, 3, 1, 2, 3]