Don't forget to also checkout my second blog containing articles to all other related ICT topics!!

Monday, May 28, 2012

final exam CS212: Polynomials

This is the third question from the Udacity CS212 final exam. I will update this article with my solution in 1 week. Please do not comment anything during the final exam week !!
```
"""
UNIT 3: Functions and APIs: Polynomials

A polynomial is a mathematical formula like:

30 * x**2 + 20 * x + 10

More formally, it involves a single variable (here 'x'), and the sum of one
or more terms, where each term is a real number multiplied by the variable
raised to a non-negative integer power. (Remember that x**0 is 1 and x**1 is x,
so 'x' is short for '1 * x**1' and '10' is short for '10 * x**0'.)

We will represent a polynomial as a Python function which computes the formula
when applied to a numeric value x.  The function will be created with the call:

p1 = poly((10, 20, 30))

where the nth element of the input tuple is the coefficient of the nth power of x.
(Note the order of coefficients has the x**n coefficient neatly in position n of
the list, but this is the reversed order from how we usually write polynomials.)
poly returns a function, so we can now apply p1 to some value of x:

p1(0) == 10

Our representation of a polynomial is as a callable function, but in addition,
we will store the coefficients in the .coef attribute of the function, so we have:

p1.coef == (10, 20, 30)

And finally, the name of the function will be the formula given above, so you should
have something like this:

>>> p1

>>> p1.__name__
'30 * x**2 + 20 * x + 10'

Make sure the formula used for function names is simplified properly.
No '0 * x**n' terms; just drop these. Simplify '1 * x**n' to 'x**n'.
Simplify '5 * x**0' to '5'.  Similarly, simplify 'x**1' to 'x'.
For negative coefficients, like -5, you can use '... + -5 * ...' or
'... - 5 * ...'; your choice. I'd recommend no spaces around '**'
and spaces around '+' and '*', but you are free to use your preferences.

is_poly, add, sub, mul, power, deriv, integral

They are described below; see the test_poly function for examples.
"""

def poly(coefs):
"""Return a function that represents the polynomial with these coefficients.
For example, if coefs=(10, 20, 30), return the function of x that computes
'30 * x**2 + 20 * x + 10'.  Also store the coefs on the .coefs attribute of
the function, and the str of the formula on the .__name__ attribute.'"""
# your code here (I won't repeat "your code here"; there's one for each function)

def test_poly():
global p1, p2, p3, p4, p5, p9 # global to ease debugging in an interactive session

p1 = poly((10, 20, 30))
assert p1(0) == 10
for x in (1, 2, 3, 4, 5, 1234.5):
assert p1(x) == 30 * x**2 + 20 * x + 10
assert same_name(p1.__name__, '30 * x**2 + 20 * x + 10')

assert is_poly(p1)
assert not is_poly(abs) and not is_poly(42) and not is_poly('cracker')

p3 = poly((0, 0, 0, 1))
assert p3.__name__ == 'x**3'
p9 = mul(p3, mul(p3, p3))
assert p9 == poly([0,0,0,0,0,0,0,0,0,1])
assert p9(2) == 512
assert same_name(p4.__name__, 'x**3 + 30 * x**2 + 20 * x + 10')

assert same_name(poly((1, 1)).__name__, 'x + 1')
assert (power(poly((1, 1)), 10).__name__ ==
'x**10 + 10 * x**9 + 45 * x**8 + 120 * x**7 + 210 * x**6 + 252 * x**5 + 210' +
' * x**4 + 120 * x**3 + 45 * x**2 + 10 * x + 1')

assert add(poly((10, 20, 30)), poly((1, 2, 3))) == poly((11, 22, 33))
assert sub(poly((10, 20, 30)), poly((1, 2, 3))) == poly((9, 18, 27))
assert mul(poly((10, 20, 30)), poly((1, 2, 3))) == poly((10, 40, 100, 120, 90))
assert power(poly((1, 1)), 2) == poly((1, 2, 1))
assert power(poly((1, 1)), 10) == poly((1, 10, 45, 120, 210, 252, 210, 120, 45, 10, 1))

assert deriv(p1) == poly((20, 60))
assert integral(poly((20, 60))) == poly((0, 20, 30))
p5 = poly((0, 1, 2, 3, 4, 5))
assert same_name(p5.__name__, '5 * x**5 + 4 * x**4 + 3 * x**3 + 2 * x**2 + x')
assert p5(1) == 15
assert p5(2) == 258
assert same_name(deriv(p5).__name__,  '25 * x**4 + 16 * x**3 + 9 * x**2 + 4 * x + 1')
assert deriv(p5)(1) == 55
assert deriv(p5)(2) == 573

def same_name(name1, name2):
"""I define this function rather than doing name1 == name2 to allow for some
variation in naming conventions."""
def canonical_name(name): return name.replace(' ', '').replace('+-', '-')
return canonical_name(name1) == canonical_name(name2)

def is_poly(x):
"Return true if x is a poly (polynomial)."
## For examples, see the test_poly function

"Return a new polynomial which is the sum of polynomials p1 and p2."

def sub(p1, p2):
"Return a new polynomial which is the difference of polynomials p1 and p2."

def mul(p1, p2):
"Return a new polynomial which is the product of polynomials p1 and p2."

def power(p, n):
"Return a new polynomial which is p to the nth power (n a non-negative integer)."

"""
If your calculus is rusty (or non-existant), here is a refresher:
The deriviative of a polynomial term (c * x**n) is (c*n * x**(n-1)).
The derivative of a sum is the sum of the derivatives.
So the derivative of (30 * x**2 + 20 * x + 10) is (60 * x + 20).

The integral is the anti-derivative:
The integral of 60 * x + 20 is  30 * x**2 + 20 * x + C, for any constant C.
Any value of C is an equally good anti-derivative.  We allow C as an argument
to the function integral (withh default C=0).
"""

def deriv(p):
"Return the derivative of a function p (with respect to its argument)."

def integral(p, C=0):
"Return the integral of a function p (with respect to its argument)."

"""
Now for an extra credit challenge: arrange to describe polynomials with an
expression like '3 * x**2 + 5 * x + 9' rather than (9, 5, 3).  You can do this
in one (or both) of two ways:

(1) By defining poly as a class rather than a function, and overloading the
__add__, __sub__, __mul__, and __pow__ operators, etc.  If you choose this,
call the function test_poly1().  Make sure that poly objects can still be called.

(2) Using the grammar parsing techniques we learned in Unit 5. For this
approach, define a new function, Poly, which takes one argument, a string,
as in Poly('30 * x**2 + 20 * x + 10').  Call test_poly2().
"""

def test_poly1():
# I define x as the polynomial 1*x + 0.
x = poly((0, 1))
# From here on I can create polynomials by + and * operations on x.
newp1 =  30 * x**2 + 20 * x + 10 # This is a poly object, not a number!
assert p1(100) == newp1(100) # The new poly objects are still callable.
assert p1.__name__ == newp1.__name__
assert (x + 1) * (x - 1) == x**2 - 1 == poly((-1, 0, 1))

def test_poly2():
newp1 = Poly('30 * x**2 + 20 * x + 10')
assert p1(100) == newp1(100)
assert p1.__name__ == newp1.__name__
assert Poly('x + 1') * Poly('x - 1') == Poly('x**2 - 1')
```

My solution:
```
"""
UNIT 3: Functions and APIs: Polynomials

A polynomial is a mathematical formula like:

30 * x**2 + 20 * x + 10

More formally, it involves a single variable (here 'x'), and the sum of one
or more terms, where each term is a real number multiplied by the variable
raised to a non-negative integer power. (Remember that x**0 is 1 and x**1 is x,
so 'x' is short for '1 * x**1' and '10' is short for '10 * x**0'.)

We will represent a polynomial as a Python function which computes the formula
when applied to a numeric value x.  The function will be created with the call:

p1 = poly((10, 20, 30))

where the nth element of the input tuple is the coefficient of the nth power of x.
(Note the order of coefficients has the x**n coefficient neatly in position n of
the list, but this is the reversed order from how we usually write polynomials.)
poly returns a function, so we can now apply p1 to some value of x:

p1(0) == 10

Our representation of a polynomial is as a callable function, but in addition,
we will store the coefficients in the .coef attribute of the function, so we have:

p1.coef == (10, 20, 30)

And finally, the name of the function will be the formula given above, so you should
have something like this:

>>> p1

>>> p1.__name__
'30 * x**2 + 20 * x + 10'

Make sure the formula used for function names is simplified properly.
No '0 * x**n' terms; just drop these. Simplify '1 * x**n' to 'x**n'.
Simplify '5 * x**0' to '5'.  Similarly, simplify 'x**1' to 'x'.
For negative coefficients, like -5, you can use '... + -5 * ...' or
'... - 5 * ...'; your choice. I'd recommend no spaces around '**'
and spaces around '+' and '*', but you are free to use your preferences.

is_poly, add, sub, mul, power, deriv, integral

They are described below; see the test_poly function for examples.
"""

def poly(coefs):
"""Return a function that represents the polynomial with these coefficients.
For example, if coefs=(10, 20, 30), return the function of x that computes
'30 * x**2 + 20 * x + 10'.  Also store the coefs on the .coefs attribute of
the function, and the str of the formula on the .__name__ attribute.'"""
# your code here (I won't repeat "your code here"; there's one for each function)
formula = getFormula(coefs)
def p(x):
return eval(formula.replace('x', str(x)))
p.coefs = coefs
p.__name__ = formula
return p

def getFormula(coefs):
coefs_list = list(coefs)
coefs_list.reverse()
#(10, 20, 30) results in '30 * x**2 + 20 * x + 10'
return ' + '.join(map_coef(coef, len(coefs_list) - index - 1) for index, coef in enumerate(coefs_list) if map_coef(coef, len(coefs_list) - index - 1) != '')

def map_coef(coef, index):
"""
map_coef(7, 3)  = '7 * x**3'
map_coef(10, 2) = '10 * x**2'
map_coef(5, 1)  = '5 * x'
map_coef(0, 2)  = ''      <-- 0 * x**2
map_coef(1, 2)  = 'x**2'  <-- 1 * x**2
map_coef(3, 0)  = '3'
"""
if index == 0:
return str(coef) if coef != 0 else ''
elif index == 1:
if coef == 0:
return ''
elif coef == 1:
return 'x'
else:
return str(coef) + ' * x'
else:
if coef == 0:
return ''
if coef == 1:
return 'x**' + str(index)
else:
return str(coef) + ' * x**' + str(index)

def coefsDictionaryToTuple(coefs_dict):
dict_to_list = coefs_dict.items()
dict_to_list.sort()
return tuple((ele for ele in dict_to_list))

def coefs_product(coefs1, coefs2):
#coef1=(10, 20, 30), coefs2=(1, 2, 3)
prod = []
for index1,coef1 in enumerate(coefs1):
for index2, coef2 in enumerate(coefs2):
prod.append((index1 + index2, coef1 * coef2))
#prod => [(0, 10), (1, 20), (2, 30), (1, 20), (2, 40), (3, 60), (2, 30), (3, 60), (4, 90)]
coefs_dict = {} #we will store tuples of same format in this dict but add up coefs with same index
for ele in prod:
if ele not in coefs_dict:
#we create entry
coefs_dict[ele] = ele
else:
#we update entry by adding current coef
coefs_dict[ele] = coefs_dict[ele] + ele
return coefsDictionaryToTuple(coefs_dict)

return coefs_op(coefs1, coefs2, lambda x,y: x + y)

def coefs_sub(coefs1, coefs2):
return coefs_op(coefs1, coefs2, lambda x,y: x - y)

def coefs_op(coefs1, coefs2, op):
coefs_dict = {}
for index, coef in enumerate(coefs1):
coefs_dict[index] = coef
for index, coef in enumerate(coefs2):
if index not in coefs_dict:
#we create entry
coefs_dict[index] = coef
else:
#we update entry by adding current coef
coefs_dict[index] = op(coefs_dict[index], coef)
return coefsDictionaryToTuple(coefs_dict)

def test_poly():
global p1, p2, p3, p4, p5, p9 # global to ease debugging in an interactive session

p1 = poly((10, 20, 30))
assert p1(0) == 10
for x in (1, 2, 3, 4, 5, 1234.5):
assert p1(x) == 30 * x**2 + 20 * x + 10
assert same_name(p1.__name__, '30 * x**2 + 20 * x + 10')

assert is_poly(p1)
assert not is_poly(abs) and not is_poly(42) and not is_poly('cracker')

p3 = poly((0, 0, 0, 1))
assert p3.__name__ == 'x**3'
p9 = mul(p3, mul(p3, p3))
assert p9(2) == 512
assert same_name(p4.__name__, 'x**3 + 30 * x**2 + 20 * x + 10')

assert same_name(poly((1, 1)).__name__, 'x + 1')
assert (power(poly((1, 1)), 10).__name__ ==
'x**10 + 10 * x**9 + 45 * x**8 + 120 * x**7 + 210 * x**6 + 252 * x**5 + 210' +
' * x**4 + 120 * x**3 + 45 * x**2 + 10 * x + 1')

assert add(poly((10, 20, 30)), poly((1, 2, 3))).coefs == (11,22,33)
assert sub(poly((10, 20, 30)), poly((1, 2, 3))).coefs == (9,18,27)
assert mul(poly((10, 20, 30)), poly((1, 2, 3))).coefs == (10, 40, 100, 120, 90)
assert power(poly((1, 1)), 2).coefs == (1, 2, 1)
assert power(poly((1, 1)), 10).coefs == (1, 10, 45, 120, 210, 252, 210, 120, 45, 10, 1)

assert deriv(p1).coefs == (20, 60)
assert integral(poly((20, 60))).coefs == (0, 20, 30)
p5 = poly((0, 1, 2, 3, 4, 5))
assert same_name(p5.__name__, '5 * x**5 + 4 * x**4 + 3 * x**3 + 2 * x**2 + x')
assert p5(1) == 15
assert p5(2) == 258
assert same_name(deriv(p5).__name__,  '25 * x**4 + 16 * x**3 + 9 * x**2 + 4 * x + 1')
assert deriv(p5)(1) == 55
assert deriv(p5)(2) == 573

def same_name(name1, name2):
"""I define this function rather than doing name1 == name2 to allow for some
variation in naming conventions."""
def canonical_name(name): return name.replace(' ', '').replace('+-', '-')
return canonical_name(name1) == canonical_name(name2)

def is_poly(x):
"Return true if x is a poly (polynomial)."
## For examples, see the test_poly function
return hasattr(x, '__call__') and isinstance(x(1), int)  and x.__name__.find('x') != -1

"Return a new polynomial which is the sum of polynomials p1 and p2."

def sub(p1, p2):
"Return a new polynomial which is the difference of polynomials p1 and p2."
return poly(coefs_sub(p1.coefs, p2.coefs))

def mul(p1, p2):
"Return a new polynomial which is the product of polynomials p1 and p2."
return poly(coefs_product(p1.coefs, p2.coefs))

def power(p, n):
"Return a new polynomial which is p to the nth power (n a non-negative integer)."
if n in [0,1]:
return p
else:
return mul(p, power(p,n-1))

"""
If your calculus is rusty (or non-existant), here is a refresher:
The deriviative of a polynomial term (c * x**n) is (c*n * x**(n-1)).
The derivative of a sum is the sum of the derivatives.
So the derivative of (30 * x**2 + 20 * x + 10) is (60 * x + 20).

The integral is the anti-derivative:
The integral of 60 * x + 20 is  30 * x**2 + 20 * x + C, for any constant C.
Any value of C is an equally good anti-derivative.  We allow C as an argument
to the function integral (withh default C=0).
"""

def deriv(p):
"Return the derivative of a function p (with respect to its argument)."
coefs_new = tuple((index * coef for index, coef in enumerate(p.coefs) if index > 0))
return poly(coefs_new)

def integral(p, C=0):
"Return the integral of a function p (with respect to its argument)."
#The integral of 60 * x + 20  ==  30 * x**2 + 20 * x + C, for any constant C.
#assert integral(poly((20, 60))).coefs == (0, 20, 30)
coefs_new = tuple([C] + [coef / (index + 1)  for index, coef in enumerate(p.coefs)])
return poly(coefs_new)

"""
Now for an extra credit challenge: arrange to describe polynomials with an
expression like '3 * x**2 + 5 * x + 9' rather than (9, 5, 3).  You can do this
in one (or both) of two ways:

(1) By defining poly as a class rather than a function, and overloading the
__add__, __sub__, __mul__, and __pow__ operators, etc.  If you choose this,
call the function test_poly1().  Make sure that poly objects can still be called.

(2) Using the grammar parsing techniques we learned in Unit 5. For this
approach, define a new function, Poly, which takes one argument, a string,
as in Poly('30 * x**2 + 20 * x + 10').  Call test_poly2().
"""

def test_poly1():
# I define x as the polynomial 1*x + 0.
x = poly((0, 1))
# From here on I can create polynomials by + and * operations on x.
newp1 =  30 * x**2 + 20 * x + 10 # This is a poly object, not a number!
assert p1(100) == newp1(100) # The new poly objects are still callable.
assert p1.__name__ == newp1.__name__
assert (x + 1) * (x - 1) == x**2 - 1 == poly((-1, 0, 1))

def test_poly2():
newp1 = Poly('30 * x**2 + 20 * x + 10')
assert p1(100) == newp1(100)
assert p1.__name__ == newp1.__name__
assert Poly('x + 1') * Poly('x - 1') == Poly('x**2 - 1')

```