## A407
## Albert Puente Encinas

## General testing function
from random import randint as rand

''' 
    This function can be used to perform random tests.
    COD     Codification function.
    DEC     Decodification function.
    nTests  Number of tests.
    testLen Length of each test.
    func    Function to be used (optional).
    verbose Print the tests (default is false).
'''
def testCodification(COD, DEC, nTests, testLen, func = 0, verbose = False):
    for i in range(0, nTests):
        X = [rand(0, 1000000) for x in range(0, testLen)]
        if func == 0: 
            C = COD(X)
            D = DEC(C)
        else: 
            C = COD(X, func)
            D = DEC(C, func)
        if verbose: print ('X:', X, '\nC:', C, '\nD:',  D)
        if X != D:
            print ('Codification/decodification error.')
            break
        elif i == nTests - 1: print ('Working properly.')

## 1.

def RC(X, f): return [x - f(y) for x, y in zip(X, range(1, len(X)+1))]

def DC(Y, f): return [x + f(y) for x, y in zip(Y, range(1, len(Y)+1))]

# Test from page T5.9
X = [41, 43, 46, 52, 62, 71, 82, 98, 115, 131]

def f(n): return n**2 - n + 41

Y = RC(X, f); print (Y)

D = DC(Y, f); print (D)

if X == D: print ('Working properly.')
else: print ('Codification/decodification error.')

# Random tests
print ('Residual coding tests:')
testCodification(RC, DC, 1000, 6, f)
#testCodification(RC, DC, 1, 6, f, verbose = True)

## 2.

def PC(X): return [a-(b+c)/2 for a,b,c in zip(X+[0,0], [0]+X+[0], [0,0]+X)][:-2]

from functools import reduce

def PD(Y): return reduce(lambda x, y: x + [y + (x[-2] + x[-1])/2], Y, [0,0])[2:]
    
print ('Predictive coding and decoding random tests:')
testCodification(PC, PD, 1000, 6)
#testCodification(PC, PD, 1, 6, verbose = True)
