# Reflections on “Pythonic monotonic”

Ned Batchelder published an interesting blog post about “pythonic” code. It was about splitting a list of numbers into monotonic series in a sorted way. For Ned surprise was that the “pythonic” version of the solution was not clear for him. Below you have this version.

```def mono_runs_pythonic(seq):
class Monotonic:
def __init__(self):
self._last = float("-inf")

def __call__(self, curr):
res = curr < self._last
self._last = curr
return res

return [
list(group)[::-1 if is_decreasing else 1]
for is_decreasing, group in itertools.groupby(seq, Monotonic())
]```

So Ned to prepare something clearer for him. And here is this version below.

```def mono_runs_simpler(seq):
seqit = iter(seq)
run = [next(seqit)]
up = True
for v in seqit:
good = (v > run[-1]) if up else (v < run[-1])
if good:
run.append(v)
else:
yield run if up else run[::-1]
run = [v]
up = not up
if run:
yield run```

## Reflections

My first reaction to both of these examples was that I have no idea what is expected result in both of them. One is using pseudo object just to persist the state of processing. Second if very procedural describing steps of processing, but even variable names give me no knowledge about the domain of the problem. In comments under the blog post, there are many proposals with a functional approach, but none of them are concentrating on readability for someone who just has a code, and want to understand what is expected result.

## Proposal

So let’s try to give some proper language into this code and some context. I found out from the Wikipedia page that the monotonic function is preserving ordering. It can be increasing or decreasing. In our case, we want to split the list of numbers into monotonous in a weak manner. Weakly monotonic means that the next element in the list can be equal to the previous.

So for me, we have some domain policies about Monotonic list, and processing function that is splitting the list into sorted monotonous.

Below you have my try on this. Hope it tells a little more about context and domain.

```class Monotonic(UserList):
@property
def populated(self) -> bool:
return len(self.data) >= 2

@property
def increasing(self) -> bool:
if not self.populated:
return True
first, *rest = self.data
not_equals = (item for item in rest if item != first)
to_compare = next(not_equals, None)

def order_preserving(self, item: float) -> bool:
if not self.populated:
return True

last = self.data and self.data[-1] or float('-inf')
if item == last:
return True

if item > last if self.increasing else item < last:
return True

return False

def split_into_monotonous(seq: Sequence[float]) -> List[List[float]]:
"""
>>> split_into_monotonous([1, 2, 3, 2, 1, 4, 5, 6, 7])
[[1, 2, 3], [1, 2], [4, 5, 6, 7]]
>>> split_into_monotonous([1, 2, 3, 1, 2, 3, 1, 2, 3])
[[1, 2, 3], [1, 2, 3], [1, 2, 3]]
>>> split_into_monotonous([1, 2, 3, 2, 1, 8, 4, 5, 6, 7])
[[1, 2, 3], [1, 2], [4, 8], [5, 6, 7]]
"""
def _split() -> Iterator[List[float]]:
processing = []
for item in seq:
if Monotonic(processing).order_preserving(item):
processing.append(item)
else:
yield processing
processing = [item]
yield processing

return [sorted(monotonic) for monotonic in _split()]``` 