Welcome, guest | Sign In | My Account | Store | Cart
"""\
This class implements polynomial functions over a single variable. It
represents the polynomial as a list of numbers and allows most
arithmetic operations, using conventional Python syntax. It does not
do symbolic manipulations. Instead, you can do things like this:

>>> x = SimplePolynomial()
>>> quadratic = (x+1)*(x-1)
>>> str(quadratic)
'X**2 - 1'
>>> quadratic(4)
15
>>> for i in range(4):
...     polynomial = (x+1)**i
...     i, str(polynomial), polynomial(1)
(0, '1', 1)
(1, 'X + 1', 2)
(2, 'X**2 + 2*X + 1', 4)
(3, 'X**3 + 3*X**2 + 3*X + 1', 8)
"""

from __future__ import division, generators

from operator import add

try:
    from itertools import izip_longest
except ImportError:
    # The izip_longest function was added in version 2.6
    # If we can't find it, use an equivalent implementation.
    from itertools import chain, repeat
    class ZipExhausted(Exception):
        pass
    def izip_longest(*args, **kwds):
        # izip_longest('ABCD', 'xy', fillvalue='-') --> Ax By C- D-
        fillvalue = kwds.get('fillvalue')
        counter = [len(args) - 1]
        def sentinel():
            if not counter[0]:
                raise ZipExhausted
            counter[0] -= 1
            yield fillvalue
        fillers = repeat(fillvalue)
        iterators = [chain(it, sentinel(), fillers) for it in args]
        try:
            while iterators:
                yield tuple(map(next, iterators))
        except ZipExhausted:
            pass

try:
    from numbers import Number
except ImportError:
    # The numbers module was added in version 2.6
    # If we can't find it, use an equivalent implementation.
    Number = (int, float, long, complex)


class SimplePolynomial(object):

    def __init__(self, terms=[0,1]):
        """\
>>> str(SimplePolynomial())
'X'
"""
        try:
            while terms[-1] == 0:
                del terms[-1]
        except IndexError:
            pass
        self.terms = list(terms)

    def __str__(self):
        """\
Needs some work, but adequate.
"""
        l = len(self.terms)
        if l == 0:
            return '0'
        if l == 1:
            return str(self.terms[0])
        result = []
        for i, c in reversed(list(enumerate(self.terms))):
            if c == 0:
                continue
            if c < 0:
                result.append('-')
                c = - c
            else:
                result.append('+')
                
            if c == 1:
                if i == 0:
                    result.append('1')
                elif i == 1:
                    result.append('X')
                else:
                    result.append('X**%g' % i)
            else:
                if i == 0:
                    result.append('%g' % c)
                elif i == 1:
                    result.append('%g*X' % c)
                else:
                    result.append('%g*X**%d' % (c, i))
        if len(result) == 0:
            return '0'
        if result[0] == '-':
            result[1] = '-'+result[1]
        del result[0]
        return ' '.join(result)

    def __add__(self, other):
        """\
>>> str(SimplePolynomial() + 1)
'X + 1'
>>> str(1 + SimplePolynomial())
'X + 1'
>>> str(SimplePolynomial() + SimplePolynomial())
'2*X'
"""
        if len(self.terms) == 0:
            return other
        if isinstance(other, Number):
            terms = self.terms[:]
            terms[0] += other
            return SimplePolynomial(terms)
        return SimplePolynomial([
            add(*pair)
            for pair in izip_longest(self.terms, other.terms, fillvalue=0)
            ])

    # Since addition is commutative, reuse __add__
    __radd__ = __add__

    def __neg__(self):
        """\
>>> str(- SimplePolynomial())
'-X'
"""
        return SimplePolynomial([-c for c in self.terms])

    def __sub__(self, other):
        """\
>>> str(SimplePolynomial() - 1)
'X - 1'
>>> str(SimplePolynomial() - SimplePolynomial())
'0'
"""
        return self + -other

    def __rsub__(self, other):
        """\
>>> str(1 - SimplePolynomial())
'-X + 1'
"""
        return -self + other

    def __mul__(self, other):
        """\
>>> str(SimplePolynomial() * 2)
'2*X'
>>> str(2 * SimplePolynomial())
'2*X'
>>> str(SimplePolynomial() * SimplePolynomial())
'X**2'
"""
        if isinstance(other, Number):
            return SimplePolynomial([c * other for c in self.terms])
        terms = [0]*(len(self.terms)+len(other.terms))
        for i1, c1 in enumerate(self.terms):
            for i2, c2 in enumerate(other.terms):
                terms[i1+i2] += c1*c2
        return SimplePolynomial(terms)

    # Since multiplication is commutative, reuse __mul__
    __rmul__ = __mul__

    def __truediv__(self, other):
        """\
Implements some simple forms of division.  See
http://en.wikipedia.org/wiki/Synthetic_division
for details.

>>> str(SimplePolynomial() / 2)
'0.5*X'
>>> x = SimplePolynomial()
>>> quotient, remainder  = (x**3 - 12*x**2 - 42) / (x - 3)
>>> str(quotient), str(remainder)
('X**2 - 9*X - 27', '-123')
"""
        if isinstance(other, Number):
            return SimplePolynomial([c / other for c in self.terms])
        if len(other.terms) == 1:
            return self/other.terms[0]
        assert len(other.terms) == 2
        dividend = self.terms[:]
        divisor = other.terms[:]
        assert divisor[-1] == 1
        xi = 0
        result = []
        for i in xrange(-1, -len(dividend) - 1, -1):
            xi = dividend[i] - xi * divisor[0]
            result.insert(0, xi)
        return SimplePolynomial(result[1:]), result[0]
        raise NotImplementedError('synthetic division')

    __div__ = __truediv__

    def __eq__(self, other):
        """\
>>> SimplePolynomial() == SimplePolynomial()
True
>>> SimplePolynomial() - SimplePolynomial() == 0
True
"""
        if isinstance(other, SimplePolynomial):
            return self.terms == other.terms
        if isinstance(other, Number):
            if len(self.terms) > 1:
                return False
            try:
                self.terms[0] == other
            except IndexError:
                return other == 0
        return False

    def __ne__(self, other):
        return not self == other

    def copy(self):
        return SimplePolynomial(self.terms[:])

    def __pow__(self, exponent):
        """\
Uses the Russian Peasant Multiplication algorithm.

>>> str(SimplePolynomial() ** 2)
'X**2'
>>> str(SimplePolynomial() ** 0.5)
Traceback (most recent call last):
...
NotImplementedError: exponent is not an integer
>>> str(SimplePolynomial() ** -1)
Traceback (most recent call last):
...
NotImplementedError: exponent is less than zero
"""
        if not isinstance(exponent, int):
            raise NotImplementedError('exponent is not an integer')
        if exponent < 0:
            raise NotImplementedError('exponent is less than zero')
        tmp = self.copy()
        result = SimplePolynomial([1])
        while exponent > 0:
            if exponent & 1:
                result *= tmp
            tmp *= tmp
            exponent >>= 1
        return result

    def __call__(self, x):
        """\
Evaluate the polynomial for the given value using the Horner scheme.

>>> SimplePolynomial()(1)
1
"""
        result = 0
        for c in reversed(self.terms):
            result = result * x + c
        return result

    def derivative(self):
        """\
>>> str(SimplePolynomial().derivative())
'1'
"""
        terms = [i*c for i, c in enumerate(self.terms)]
        return SimplePolynomial(terms[1:])

    def integrate(self, const=0):
        """\
>>> str(SimplePolynomial().integrate())
'0.5*X**2'
"""
        terms = [const]
        terms.extend([c/(i+1) for i, c in enumerate(self.terms)])
        return SimplePolynomial(terms)


if __name__ == '__main__':
    import doctest
    doctest.testmod()

Diff to Previous Revision

--- revision 6 2010-01-24 05:22:57
+++ revision 7 2014-02-13 20:55:55
@@ -1,65 +1,79 @@
-import operator
-
-Number = (int, float, long, complex)
-#sum_v = lambda *a: sum(a)
+"""\
+This class implements polynomial functions over a single variable. It
+represents the polynomial as a list of numbers and allows most
+arithmetic operations, using conventional Python syntax. It does not
+do symbolic manipulations. Instead, you can do things like this:
+
+>>> x = SimplePolynomial()
+>>> quadratic = (x+1)*(x-1)
+>>> str(quadratic)
+'X**2 - 1'
+>>> quadratic(4)
+15
+>>> for i in range(4):
+...     polynomial = (x+1)**i
+...     i, str(polynomial), polynomial(1)
+(0, '1', 1)
+(1, 'X + 1', 2)
+(2, 'X**2 + 2*X + 1', 4)
+(3, 'X**3 + 3*X**2 + 3*X + 1', 8)
+"""
+
+from __future__ import division, generators
+
+from operator import add
+
+try:
+    from itertools import izip_longest
+except ImportError:
+    # The izip_longest function was added in version 2.6
+    # If we can't find it, use an equivalent implementation.
+    from itertools import chain, repeat
+    class ZipExhausted(Exception):
+        pass
+    def izip_longest(*args, **kwds):
+        # izip_longest('ABCD', 'xy', fillvalue='-') --> Ax By C- D-
+        fillvalue = kwds.get('fillvalue')
+        counter = [len(args) - 1]
+        def sentinel():
+            if not counter[0]:
+                raise ZipExhausted
+            counter[0] -= 1
+            yield fillvalue
+        fillers = repeat(fillvalue)
+        iterators = [chain(it, sentinel(), fillers) for it in args]
+        try:
+            while iterators:
+                yield tuple(map(next, iterators))
+        except ZipExhausted:
+            pass
+
+try:
+    from numbers import Number
+except ImportError:
+    # The numbers module was added in version 2.6
+    # If we can't find it, use an equivalent implementation.
+    Number = (int, float, long, complex)
+
 
 class SimplePolynomial(object):
+
     def __init__(self, terms=[0,1]):
+        """\
+>>> str(SimplePolynomial())
+'X'
+"""
         try:
             while terms[-1] == 0:
                 del terms[-1]
         except IndexError:
             pass
-        self.terms = terms
-    def __add__(self, other):
-        if len(self.terms) == 0:
-            return other
-        if isinstance(other, Number):
-            terms = self.terms[:]
-            terms[0] += other
-        elif len(self.terms) <= len(other.terms):
-            terms = self.terms[:]
-            terms.extend([0]*(len(other.terms) - len(self.terms)))
-            terms = map(operator.add, other.terms, terms)
-        else:
-            return other + self
-        return SimplePolynomial(terms)
-    def __radd__(self, other):
-        "addition is commutative, so we can just switch the arguments"
-        return self + other
-    def __neg__(self):
-        return SimplePolynomial([-c for c in self.terms])
-    def __sub__(self, other):
-        return self + -other
-    def __rsub__(self, other):
-        return -self + other
-    def __mul__(self, other):
-        if isinstance(other, Number):
-            return SimplePolynomial([other*c for c in self.terms])
-        terms = [0]*(len(self.terms)+len(other.terms))
-        for i1, c1 in enumerate(self.terms):
-            for i2, c2 in enumerate(other.terms):
-                terms[i1+i2] += c1*c2
-        return SimplePolynomial(terms)
-    def __rmul__(self, other):
-        "multiplication is commutative, so we can just switch the arguments"
-        return self * other
-    def copy(self):
-        return SimplePolynomial(self.terms[:])
-    def __pow__(self, exponent):
-        "uses the Russian Peasant Multiplication algorithm"
-        if not isinstance(exponent, int):
-            raise NotImplementedError
-        tmp = self.copy()
-        result = SimplePolynomial([1])
-        while exponent > 0:
-            if exponent & 1:
-                result *= tmp
-            tmp *= tmp
-            exponent >>= 1
-        return result
+        self.terms = list(terms)
+
     def __str__(self):
-        "needs some work, but adequate"
+        """\
+Needs some work, but adequate.
+"""
         l = len(self.terms)
         if l == 0:
             return '0'
@@ -95,25 +109,186 @@
             result[1] = '-'+result[1]
         del result[0]
         return ' '.join(result)
+
+    def __add__(self, other):
+        """\
+>>> str(SimplePolynomial() + 1)
+'X + 1'
+>>> str(1 + SimplePolynomial())
+'X + 1'
+>>> str(SimplePolynomial() + SimplePolynomial())
+'2*X'
+"""
+        if len(self.terms) == 0:
+            return other
+        if isinstance(other, Number):
+            terms = self.terms[:]
+            terms[0] += other
+            return SimplePolynomial(terms)
+        return SimplePolynomial([
+            add(*pair)
+            for pair in izip_longest(self.terms, other.terms, fillvalue=0)
+            ])
+
+    # Since addition is commutative, reuse __add__
+    __radd__ = __add__
+
+    def __neg__(self):
+        """\
+>>> str(- SimplePolynomial())
+'-X'
+"""
+        return SimplePolynomial([-c for c in self.terms])
+
+    def __sub__(self, other):
+        """\
+>>> str(SimplePolynomial() - 1)
+'X - 1'
+>>> str(SimplePolynomial() - SimplePolynomial())
+'0'
+"""
+        return self + -other
+
+    def __rsub__(self, other):
+        """\
+>>> str(1 - SimplePolynomial())
+'-X + 1'
+"""
+        return -self + other
+
+    def __mul__(self, other):
+        """\
+>>> str(SimplePolynomial() * 2)
+'2*X'
+>>> str(2 * SimplePolynomial())
+'2*X'
+>>> str(SimplePolynomial() * SimplePolynomial())
+'X**2'
+"""
+        if isinstance(other, Number):
+            return SimplePolynomial([c * other for c in self.terms])
+        terms = [0]*(len(self.terms)+len(other.terms))
+        for i1, c1 in enumerate(self.terms):
+            for i2, c2 in enumerate(other.terms):
+                terms[i1+i2] += c1*c2
+        return SimplePolynomial(terms)
+
+    # Since multiplication is commutative, reuse __mul__
+    __rmul__ = __mul__
+
+    def __truediv__(self, other):
+        """\
+Implements some simple forms of division.  See
+http://en.wikipedia.org/wiki/Synthetic_division
+for details.
+
+>>> str(SimplePolynomial() / 2)
+'0.5*X'
+>>> x = SimplePolynomial()
+>>> quotient, remainder  = (x**3 - 12*x**2 - 42) / (x - 3)
+>>> str(quotient), str(remainder)
+('X**2 - 9*X - 27', '-123')
+"""
+        if isinstance(other, Number):
+            return SimplePolynomial([c / other for c in self.terms])
+        if len(other.terms) == 1:
+            return self/other.terms[0]
+        assert len(other.terms) == 2
+        dividend = self.terms[:]
+        divisor = other.terms[:]
+        assert divisor[-1] == 1
+        xi = 0
+        result = []
+        for i in xrange(-1, -len(dividend) - 1, -1):
+            xi = dividend[i] - xi * divisor[0]
+            result.insert(0, xi)
+        return SimplePolynomial(result[1:]), result[0]
+        raise NotImplementedError('synthetic division')
+
+    __div__ = __truediv__
+
+    def __eq__(self, other):
+        """\
+>>> SimplePolynomial() == SimplePolynomial()
+True
+>>> SimplePolynomial() - SimplePolynomial() == 0
+True
+"""
+        if isinstance(other, SimplePolynomial):
+            return self.terms == other.terms
+        if isinstance(other, Number):
+            if len(self.terms) > 1:
+                return False
+            try:
+                self.terms[0] == other
+            except IndexError:
+                return other == 0
+        return False
+
+    def __ne__(self, other):
+        return not self == other
+
+    def copy(self):
+        return SimplePolynomial(self.terms[:])
+
+    def __pow__(self, exponent):
+        """\
+Uses the Russian Peasant Multiplication algorithm.
+
+>>> str(SimplePolynomial() ** 2)
+'X**2'
+>>> str(SimplePolynomial() ** 0.5)
+Traceback (most recent call last):
+...
+NotImplementedError: exponent is not an integer
+>>> str(SimplePolynomial() ** -1)
+Traceback (most recent call last):
+...
+NotImplementedError: exponent is less than zero
+"""
+        if not isinstance(exponent, int):
+            raise NotImplementedError('exponent is not an integer')
+        if exponent < 0:
+            raise NotImplementedError('exponent is less than zero')
+        tmp = self.copy()
+        result = SimplePolynomial([1])
+        while exponent > 0:
+            if exponent & 1:
+                result *= tmp
+            tmp *= tmp
+            exponent >>= 1
+        return result
+
     def __call__(self, x):
-        "evaluate the polynomial for the given value using the Horner scheme"
+        """\
+Evaluate the polynomial for the given value using the Horner scheme.
+
+>>> SimplePolynomial()(1)
+1
+"""
         result = 0
         for c in reversed(self.terms):
-            result = result*x + c
+            result = result * x + c
         return result
+
     def derivative(self):
+        """\
+>>> str(SimplePolynomial().derivative())
+'1'
+"""
         terms = [i*c for i, c in enumerate(self.terms)]
         return SimplePolynomial(terms[1:])
+
     def integrate(self, const=0):
+        """\
+>>> str(SimplePolynomial().integrate())
+'0.5*X**2'
+"""
         terms = [const]
-        terms.extend([c/float(i+1) for i, c in enumerate(self.terms)])
+        terms.extend([c/(i+1) for i, c in enumerate(self.terms)])
         return SimplePolynomial(terms)
 
+
 if __name__ == '__main__':
-    print 'testing...'
-    x = SimplePolynomial()
-    for expression in 'x', 'x+1', '1+x', 'x+x', '-x', 'x-1', '1-x', '2*x', 'x*2', 'x*x', '(x+1)*(x-1)':
-        print expression, '\t=>', eval(expression)
-    print 'testing exponentiation...'
-    for i in range(4):
-        print '(x+1)**%d' % i, '\t=>', (x+1)**i
+    import doctest
+    doctest.testmod()

History