I was looking recently at the Python module
toolz, a collection of convenience functions. A lot of these functions don’t do that much. They don’t save you much code, but they do make your code more readable by making it more declarative. You may not realize need them until you see them.
For example, there is a function
partitionby that breaks up a sequence at the points where a given function’s value changes. I’m pretty sure that function would have improved some code I’ve written recently, making it more declarative than procedural, but I can’t remember what that was.
Although I can’t think of my previous example, I can think of a new one, and that is Descartes’ rule of signs.
Given a polynomial p(x), read the non-zero coefficients in order and keep note of how many times they change sign, either from positive to negative or vice versa. Call that number n. Then the number of positive roots of p(x) either equals n or n minus a positive even number.
For example, suppose
p(x) = 4x4 + 3.1x3 – x2 – 2x + 6.
The coefficients are 4, 3.1, -1, -2, and 6. The list of coefficients changes signs twice: from positive to negative, and from negative to positive. Here’s a first pass at how you might have Python split the coefficients to look sign changes.
from toolz import partitionby coefficients = [4, 3.1, -1, -2, 6] parts = partitionby(lambda x: x > 0, coefficients) print([p for p in parts])
[(4, 3.1), (-1, -2), (6,)]
The first argument to
partitionby an anonymous function that tests whether its argument is positive. When this function changes value, we have a sign alteration. There are three groups of consecutive coefficients that have the same sign, so there are two times the signs change. So our polynomial either has two positive roots or no positive roots. (It turns out there are no positive roots.)
The code above isn’t quite right though, because Descartes said to only look at non-zero coefficients. If we change our anonymous function to
lambda x: x >= 0
that will work for zeros in the middle of positive coefficients, but it will give a false positive for zeros in the middle of negative coefficients. We can fix the code with a list comprehension. The following example works correctly.
coefficients = [4, 0, 3.1, -1, 0, -2, 6] nonzero = [c for c in coefficients if c != 0] parts = partitionby(lambda x: x > 0, nonzero) print([p for p in parts])
If our coefficients were in a NumPy array rather than a list, we could remove the zeros more succinctly.
from numpy import array c = array(coefficients) parts = partitionby(lambda x: x > 0, c[c != 0])
partitionby returns an iterator rather than a list. That’s why we don’t just print
parts above. Instead we print
[p for p in parts] which makes a list. In applications, it’s often more efficient to have an iterator than a list, generating items if and when they are needed. If you don’t need all the items, you don’t have to generate them all. And even if you do need all the items, you could save memory by not keeping them all in memory at once. I’ll ignore such efficiencies here.
We don’t need the partitions per se, we just need to know how many there are. The example that escapes my mind would have been a better illustration if it needed to do more with each portion than just count it. We could count the number of sign alternations for Descartes rule as follows.
len([p for p in parts]) - 1