Re:Pythonic monotonic
by eesmith on Friday August 13, 2021 @09:33AM
(#28168624)
itertools.groupby() groups subsequences by equality of some common value.
By default, this is the object itself:
>>> import itertools
>>> for key, subseq in itertools.groupby("AAAbbCcc"):
... print(key, "->", list(subseq))
...
A -> ['A', 'A', 'A']
b -> ['b', 'b']
C -> ['C']
c -> ['c', 'c']
You can specify how to get an alternative key, like folding everything to lowercase (assuming that makes sense for the target language):
>>> for key, subseq in itertools.groupby("AAAbbCcc", key=str.lower):
... print(key, "->", list(subseq))
...
a -> ['A', 'A', 'A']
b -> ['b', 'b']
c -> ['C', 'c', 'c']
The "key" here is a function which is applied to each subsequent element. You can see that "C" and "c" are now treated equivalently. This is because str.lower("C") == str.lower("c").
The first code create a new callable object, call "Monotonic", which keeps track of the previous value, and uses that for the comparison.
Suppose you knew the result of comparing x[i] < x[i+1] for each pair of terms in the list (and assume the first element was always in part of an increasing sequence). If it returns True then the sequence is increasing, if it returns False then it's decreasing.
Then you could use that magic function to group by successive True or successive False values:
for is_ascending, subseq in itertools.groupby("AAAbbCcc", key=monotonic_key):
...
What a Monotonic instance does is keep track of the previous element, so it the next time it's called it can compute the "<" test, and return the monotonic key, used to group the subsequence by that key.
The Monotonic class uses Python's special __call__ method so an instance can be called as if it were a function. This allows the function-like object to track state over time.
Here's what it looks like when called with successive elements of the example sequence:
>>> m = Monotonic()
>>> [m(i) for i in [1, 2, 3, 2, 1, 4, 5, 6, 7]]
[False, False, False, True, True, False, False, False, False]
When used as the magic key function to groupby, the [1, 2, 3] (all False), the [2, 1] (all True), and the [4, 5, 6, 7] (all False) group together.
Note: This implementation has to special-case the first sequence element. In this case it assumes the previous element is -math.inf, which is the smallest possible value that a Python float may have.
As it happens, this assumes the first element is always part of a monotonically increasing subsequence. This is an invalid assumption, as it could be part of a monotonically decreasing subsequence, like [3, 2, 1].
>>> m = Monotonic()
>>> [m(i) for i in [3, 2, 1]]
[False, True, True]
A correct implementation should return [True, True, True].
By default, this is the object itself:
You can specify how to get an alternative key, like folding everything to lowercase (assuming that makes sense for the target language): The "key" here is a function which is applied to each subsequent element. You can see that "C" and "c" are now treated equivalently. This is because str.lower("C") == str.lower("c").The first code create a new callable object, call "Monotonic", which keeps track of the previous value, and uses that for the comparison.
Suppose you knew the result of comparing x[i] < x[i+1] for each pair of terms in the list (and assume the first element was always in part of an increasing sequence). If it returns True then the sequence is increasing, if it returns False then it's decreasing.
Then you could use that magic function to group by successive True or successive False values:
What a Monotonic instance does is keep track of the previous element, so it the next time it's called it can compute the "<" test, and return the monotonic key, used to group the subsequence by that key.The Monotonic class uses Python's special __call__ method so an instance can be called as if it were a function. This allows the function-like object to track state over time.
Here's what it looks like when called with successive elements of the example sequence:
When used as the magic key function to groupby, the [1, 2, 3] (all False), the [2, 1] (all True), and the [4, 5, 6, 7] (all False) group together.Note: This implementation has to special-case the first sequence element. In this case it assumes the previous element is -math.inf, which is the smallest possible value that a Python float may have.
As it happens, this assumes the first element is always part of a monotonically increasing subsequence. This is an invalid assumption, as it could be part of a monotonically decreasing subsequence, like [3, 2, 1].
A correct implementation should return [True, True, True].