
import random
class Genetic:
	def __init__(self,max_num):
		self.max_num = max_num
		#self.seed = seed
	
	def generateLcgList(self,seed,num):   
		"""Genetate random list with maximum number provide by class 
			constructor and returns the list with random value 
			#Lcg list is genetated using Xn+1 = Xn*A + B (mod M)"""
		num_of_elements = num
		glist = []
		xn = seed
		for i in range(num_of_elements):
			next_num = (xn*1140671485 + 12820163)% (self.max_num)
			glist.append(next_num)
			xn = next_num
		return glist

	def generatePopulation(self,plist):  
		""" #divide list into two list which is the new offspring"""
		parent_list = plist
		plength = len(parent_list)
		child1_list = []
		child2_list = []
		for i in range(plength):
			if (i%2 == 0):
				child1_list.append(parent_list[i])
			else:
				child2_list.append(parent_list[i])
		return child1_list, child2_list

	def fitness(self,child1,child2): 
		"""#measure the difference between the two sublist which is the measure of the fitness"""
		#child1 = child1
		#child2 = child2
		sum_of_child1 = sum(child1)   
		sum_of_child2 = sum(child2)
		fitnessv = abs(sum_of_child1-sum_of_child2)
		return fitnessv

	def crossover(self, child1, child2): 
		"""change some of the elements of child1 with child2, cross over point
		is generated using random number generator"""
		new_child1 = []
		new_child2 = []	
		len_of_c1 = len(child1)	
		len_of_c2 = len(child2)	
		if(len_of_c1<len_of_c2):
			min_length = len_of_c1
		else:
			min_length = len_of_c2
		cross_over_point = random.randint(1,min_length)
		for i in range(cross_over_point):       #cross over the elements of child1 and child2 up to the cross over point 
			new_child1.append(child2[i])
			new_child2.append(child1[i])	
		for i in range(cross_over_point, len_of_c1):
			new_child1.append(child1[i])
		for i in range(cross_over_point, len_of_c2):
			new_child2.append(child2[i])
		return new_child1, new_child2

	def mutation(self,child1,child2):
		"""Change the specific element of one child with the element of  another child
		Specific element position is determined by random number generated by random number generator """
		new_child1 = []
		new_child2 = []	
		len_of_c1 = len(child1)	
		len_of_c2 = len(child2)	
		if(len_of_c1<len_of_c2):
			min_length = len_of_c1
		else:
			min_length = len_of_c2
		mutation_element = random.randint(1,(min_length-1))
	 	#mutate the element in the position "mutation_element"
		for i in range(0, len_of_c1):
			if(i == mutation_element):
				new_child1.append(child2[i])
			else:
				new_child1.append(child1[i])
		
		for i in range(0, len_of_c2):
			if(i == mutation_element):
				new_child2.append(child1[i])
			else:
				new_child2.append(child2[i])
		return new_child1,new_child2

	def calculateFinalList(self,seed,total_element_oflist):    # main function
		seed = seed
		total_element_oflist = total_element_oflist
		parent_list = self.generateLcgList(seed,total_element_oflist)     #first seed second number of elements ** call this function with different 
							# seed and different number for  different list	
		child1_res, child2_res = self.generatePopulation(parent_list)
		fitness_res = self.fitness(child1_res, child2_res)
		dict_res = {'child1_res': child1_res, 'child2_res':child2_res,'fitness_res':fitness_res}
		count = 0
		while(True):
			if(dict_res.get('fitness') == 0):
				break	
			else:
				child1_opt,child2_opt = self.crossover(child1_res,child2_res)
				child1_opt,child2_opt = self.mutation(child1_opt, child2_opt)     #####
				fitness_opt = self.fitness(child1_opt,child2_opt)
				#Gradient descent 
				if(fitness_res>fitness_opt):
					dict_res.update({'child1_res': child1_opt, 'child2_res':child2_opt,'fitness_res':fitness_opt})
				else:
					count = count + 1
					if(count >=20):
						break
		final_child1 = dict_res.get('child1_res')
		final_child2 = dict_res.get('child2_res')
		final_fitness = dict_res.get('fitness_res')				
		return parent_list,child1_res,child2_res,fitness_res,final_child1,final_child2,final_fitness

if __name__ =="__main__":
	genetic = Genetic(10000)   # this constructor determines the maximum value of the element in the list
	
	def display(listp,pchild1,pchild2,pfitness,fchild1,fchild2,ffitness,list_name):
		print("Parent list of {} is:".format(list_name), listp)
		print("Child1 child list of {} is:".format(list_name), pchild1)
		print("Child2 child list of {} is:".format(list_name), pchild2)
		print("Differences of the sums of the numbers in the parent child1 and parent child2 of {} is:".format(list_name), pfitness)
		print("\n")
		print("Offspring list of child1 of {} is:".format(list_name), fchild1)
		print("Offspring list of child2 of {} is:".format(list_name), fchild2)
		print("Differences of the sums of the numbers in the offspring child1 and offspring child2 of {} is:".format(list_name), ffitness)
	
	#first seed second number of elements ** call this function with different seed and different number for  different list
	listp_a,pchild1_a,pchild2_a,pfitness_a,fchild1_a,fchild2_a,ffitness_a = genetic.calculateFinalList(5,20)   
	display(listp_a,pchild1_a,pchild2_a,pfitness_a,fchild1_a,fchild2_a,ffitness_a,"A")              
	"""	#-----------------------------------List B------------------------------------------------------------------#
	print("\n\n")
	listp_b,pchild1_b,pchild2_b,pfitness_b,fchild1_b,fchild2_b,ffitness_b = genetic.calculateFinalList(13,50)   #first seed second number of elements 
	display(listp_b,pchild1_b,pchild2_b,pfitness_b,fchild1_b,fchild2_b,ffitness_b,"B")
		#------------------------------------List C--------------------------------------------------------------------#
	print("\n\n")
	listp_c,pchild1_c,pchild2_c,pfitness_c,fchild1_c,fchild2_c,ffitness_c = genetic.calculateFinalList(17,250)   #first seed second number of elements 
	display(listp_c,pchild1_c,pchild2_c,pfitness_c,fchild1_c,fchild2_c,ffitness_c,"C")
		#-------------------------------------List D------------------------------------------------------------------#
	print("\n\n")
	listp_d,pchild1_d,pchild2_d,pfitness_d,fchild1_d,fchild2_d,ffitness_d = genetic.calculateFinalList(214013,1000)   #first seed second number of elements 
	display(listp_d,pchild1_d,pchild2_d,pfitness_d,fchild1_d,fchild2_d,ffitness_d,"D")"""
	

