import numpy as np

class Perceptron :
	
	def __init__(self, num_inputs) :
		self.weights = [2 * np.random.rand() - 1 for i in range(0, num_inputs + 1)]
		self.learn_rate = 1
		
	def out(self, inp) :
		return self.bias_out([1.0] + inp)
		
	def bias_out(self, biased_inp) :
		if self.weighted_sum(biased_inp) >= 0 : return 1
		return 0
						
	def train(self, inp, expected) :
		print "Current weights: {}".format(self.weights)
		biased_inp = [1.0] + inp
		pred = self.bias_out(biased_inp)
		error = (expected - pred)
		self.weights = [old_weight + self.learn_rate * error * inp_val for old_weight, inp_val in zip(self.weights, biased_inp)]
		print "Updated weights: {}".format(self.weights)
			
	def weighted_sum(self, biased_inp) :
		return np.dot(biased_inp, self.weights)
	
class Trainer :
	def __init__(self, num_inputs, ref_func) :
		self.ref_func = ref_func
		self.perceptron = Perceptron(num_inputs)
		self.num_inputs = num_inputs
		
	def step(self) :
		inputs = [Trainer.randomInput() for i in range(0, self.num_inputs)]
		expected_out = apply(self.ref_func, inputs)
		actual_out = self.perceptron.out(inputs)
		print "Inputs: {}".format(inputs)
		print "Expected: {}, Actual: {}".format(expected_out, actual_out)
		#train
		self.perceptron.train(inputs, expected_out)
	
	def validate(self) :
		inputs = [0] * self.num_inputs
		for i in xrange(0, 2 ** self.num_inputs) :
			print "Test input: {}".format(inputs)
			print "Expected output: {}".format(apply(self.ref_func, inputs))
			print "Actual output: {}".format(self.perceptron.out(inputs))
			for j in range(0, self.num_inputs) :
				inputs[j] = 1 - inputs[j]
				if inputs[j] == 1 :
					break
			
	@staticmethod
	def randomInput() :
		i = np.random.rand()
		return 0 if i < 0.5 else 1
		
def my_and(a, b) :
	if (a != 0 and b != 0) : return 1
	return 0
	
def my_or(a, b) :
	if (a != 0 or b != 0) : return 1
	return 0
	
def my_nand(a, b) :
	if (a != 0 and b != 0) : return 0
	return 1
	
def my_nor(a, b) :
	if (a != 0 or b != 0) : return 0
	return 1	
	
def my_xor(a, b) :
	if (a == 1 and b == 0) : return 1
	if (a == 0 and b == 1) : return 1
	return 0
	
def my_inv(a) :
	if (a == 0) : return 1
	return 0
	
t = Trainer(2, my_and)
for i in range(0, 1000) :
	t.step()
	
t.validate()