Back to Hobby Projects

2023

Genetic Algorithm

C implementation of a genetic algorithm that evolves candidate strings toward a user-defined target. Rather than presenting itself as a generic optimization framework, it is designed as a clear and approachable demonstration of evolutionary search, with an emphasis on readability, traceability, and understanding how the core mechanics of a genetic algorithm work in practice. The program takes two command-line inputs: a target string and the number of offspring to generate in each generation. At startup, it validates that the target contains only characters from the predefined gene pool, checks that the offspring count is within the allowed range, seeds the random number generator, and initializes the target genome.

Architecturally, the project is intentionally compact and well separated into small functional layers. The app_init module handles argument parsing and validation, the main.c application layer drives the generational loop, and the genetic_algorithm_utils module contains the core genome operations such as initialization, crossover, mutation, fitness calculation, sorting, copying, printing, and cleanup. A gene is represented as a single char, while a genome is represented by a structure containing a dynamically managed gene array, its length, and a cached fitness value. This makes the data model simple enough to follow directly while still being structured enough to support clean separation between orchestration and algorithmic logic.

The evolutionary process follows a straightforward cycle. Two parent genomes are first created with random genes drawn from the project's character pool. In each generation, the parents are repeatedly mated to produce the configured number of offspring. Mating uses single-point crossover, and the order of the two parents is randomly flipped so that neither parent always contributes the leading segment. After crossover, a slight mutation is applied, implemented as either zero or one random gene replacement. This mutation step is included specifically to reduce the risk of the population settling into a local maximum too early. Once created, each offspring is scored against the target using a fitness function based on exact positional character matches.

Selection is equally direct: the offspring population is sorted by fitness, and the two fittest offspring are deep-copied into the parent slots for the next generation. In this implementation, a perfect match has a fitness of 0, while less accurate genomes have increasingly negative scores. The loop continues until convergence is achieved or an upper iteration bound is reached. As the README notes, the design intentionally favors clarity over optimization, even keeping some routines, such as fitness evaluation and sorting, simple for ease of understanding and debugging. Overall, the project serves as a readable, self-contained example of parent selection, crossover, mutation, survival of the fittest, and generational convergence in C.

Genetic Algorithm preview image