import random

print("--------SYMDS2024015--------")
print("--------Laukik Nibre--------\n")

# Population size
POPULATION_SIZE = 100

# Valid genes
GENES = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ 1234567890"

# Target string
TARGET = "Laukik Nibre"


class Individual(object):

    def __init__(self, chromosome):
        self.chromosome = chromosome
        self.fitness = self.cal_fitness()

    @classmethod
    def mutated_genes(cls):
        return random.choice(GENES)

    @classmethod
    def create_genome(cls):
        return [cls.mutated_genes() for _ in range(len(TARGET))]

    def mate(self, par2):
        child_chromosome = []

        for gp1, gp2 in zip(self.chromosome, par2.chromosome):

            prob = random.random()

            if prob < 0.45:
                child_chromosome.append(gp1)

            elif prob < 0.90:
                child_chromosome.append(gp2)

            else:
                child_chromosome.append(self.mutated_genes())

        return Individual(child_chromosome)

    def cal_fitness(self):
        fitness = 0
        for gs, gt in zip(self.chromosome, TARGET):
            if gs != gt:
                fitness += 1
        return fitness


def main():

    generation = 1
    found = False
    population = []

    # Create initial population
    for _ in range(POPULATION_SIZE):
        genome = Individual.create_genome()
        population.append(Individual(genome))

    while not found:

        population = sorted(population, key=lambda x: x.fitness)

        if population[0].fitness == 0:
            found = True
            break

        new_generation = []

        # Top 10% survive
        s = int((10 * POPULATION_SIZE) / 100)
        new_generation.extend(population[:s])

        # Remaining 90% produced by mating
        s = int((90 * POPULATION_SIZE) / 100)
        for _ in range(s):
            parent1 = random.choice(population[:50])
            parent2 = random.choice(population[:50])
            child = parent1.mate(parent2)
            new_generation.append(child)

        population = new_generation

        print("Generation:", generation,
              "String:", "".join(population[0].chromosome),
              "Fitness:", population[0].fitness)

        generation += 1

    print("\nFinal Solution:")
    print("Generation:", generation,
          "String:", "".join(population[0].chromosome),
          "Fitness:", population[0].fitness)


if __name__ == "__main__":
    main()