import random


def evolve(seed=7, length=24, population_size=40, generations=200):
    if length < 2 or population_size < 3 or generations < 0:
        raise ValueError("Use length >= 2, population >= 3, generations >= 0")
    rng = random.Random(seed)
    population = [
        [rng.randrange(2) for _ in range(length)]
        for _ in range(population_size)
    ]
    history = []
    for generation in range(generations + 1):
        best = max(population, key=sum)
        history.append(sum(best))
        if sum(best) == length or generation == generations:
            return best[:], generation, history
        # Copy the elite so later changes cannot modify its parent.
        next_population = [best[:]]
        while len(next_population) < population_size:
            parent_a = max(rng.sample(population, 3), key=sum)
            parent_b = max(rng.sample(population, 3), key=sum)
            cut = rng.randrange(1, length)
            child = parent_a[:cut] + parent_b[cut:]
            child = [
                1 - bit if rng.random() < 1 / length else bit
                for bit in child
            ]
            next_population.append(child)
        population = next_population
    raise AssertionError("Unreachable")


if __name__ == "__main__":
    best, generation, history = evolve()
    print("Best:", "".join(map(str, best)))
    print("Fitness:", sum(best), "Generation:", generation)
