DEV Community

Cover image for Como uma conversa no bilhar 🎱 me ensinou a montar agendas com algoritmos genéticos 🧬 em PHP
Rômulo Mendes Soares Junior
Rômulo Mendes Soares Junior

Posted on

Como uma conversa no bilhar 🎱 me ensinou a montar agendas com algoritmos genéticos 🧬 em PHP


Esses dias saí pra descontrair e jogar um bilhar com meu amigo Romane.

No meio do jogo, ele soltou:

“Eu que estava me achando porque fiz uma função de busca genética!”

Achei que ele estava brincando... até me contar que criou uma função chamada buscaGenetica() que ajudava a resolver um problema real: pagar boletos com um valor limitado.


💸 O problema dos boletos

“Imagine que o usuário informa um array de boletos, cada um com id e valor, e diz que possui R\$1.000 para pagar o maior número possível deles.”

Ele usava algoritmos genéticos para encontrar a melhor combinação possível respeitando esse limite.


🧬 Como funciona essa tal de busca genética?

A explicação dele foi tão lúdica que parecia até um roteiro de ficção científica:

  • 💑 Os boletos “casavam” entre si e geravam novas combinações.
  • 🧟 Algumas gerações vinham com mutações, tipo boletos trocados por outros — o que ele chamava carinhosamente de “anomalias genéticas” (ou "cânceres" 😅).
  • 👑 Boas combinações sobreviviam direto para a próxima geração.
  • 🧠 E tudo isso se repetia por 100 gerações, até encontrar a solução ideal: o maior número de boletos pagos com até R\$1.000.

Fiquei vidrado. A ideia era poderosa.


⏰ O meu problema real: disponibilidade de horários

Na volta pra casa, a conversa não saía da minha cabeça.

Eu estava justamente quebrando a cabeça com um problema no meu sistema:

Tenho vários profissionais, com diferentes habilidades (banho, tosa, trimming), e preciso preencher os horários da agenda com os melhores profissionais, sem sobreposição.

E aí veio o clique:

E se eu aplicasse o algoritmo genético do Romane para montar essa agenda?

Transformei a lógica dele em um serviço PHP chamado GeneticAlgorithmService.


💻 A implementação com PHP

A classe GeneticAlgorithmService é genérica e recebe um objeto de configuração. Ela cuida de toda a evolução genética por trás da solução:

use App\Services\GeneticAlgorithmService;

$config = [
    "items" => [
        ["id" => 1, "peso" => 10, "altura" => 50, "valor" => 60],
        ["id" => 2, "peso" => 20, "altura" => 60, "valor" => 100],
        ["id" => 3, "peso" => 30, "altura" => 70, "valor" => 120],
    ],
    "constraints" => [
        ["attribute" => "peso", "max" => 50],
        ["attribute" => "altura", "max" => 110],
    ],
    "objective" => ["attribute" => "valor", "goal" => "maximize"],
    "populationSize" => 50,
    "generations" => 100,
    "mutationRate" => 0.05,
    "eliteRate" => 0.2,
];

$configObject = json_decode(json_encode($config), false);
$service = new GeneticAlgorithmService($configObject);
$result = $service->run();

echo "Melhor combinação encontrada:\n";
print_r($result);
Enter fullscreen mode Exit fullscreen mode

📥 Entrada (configuração)

Você define:

  • items: conjunto de dados (horários, boletos, tarefas, etc)
  • constraints: restrições (ex: peso máximo, limite de tempo, altura)
  • objective: qual atributo será maximizado ou minimizado
  • Parâmetros genéticos: tamanho da população, número de gerações, taxa de mutação e taxa de elite

📤 Saída

A função run() retorna, por exemplo:

[
    ['id' => 2, 'peso' => 20, 'altura' => 60, 'valor' => 100],
    ['id' => 1, 'peso' => 10, 'altura' => 50, 'valor' => 60]
]
Enter fullscreen mode Exit fullscreen mode

Ou seja, a melhor combinação possível segundo os critérios definidos.


⚙️ O que o algoritmo faz por trás

Internamente, o GeneticAlgorithmService executa:

  1. Geração da população inicial (combinações aleatórias de itens)
  2. Avaliação de "fitness" (quão boa é cada combinação)
  3. Seleção dos melhores indivíduos (elitismo)
  4. Cruzamento entre candidatos (reprodução)
  5. Mutação aleatória em descendentes
  6. Repetição por várias gerações

✅ Por que isso funciona tão bem?

Porque é flexível. Com esse padrão, você pode resolver:

  • Agendas de atendimento
  • Seleção de tarefas com prioridades
  • Escalonamento de recursos
  • Qualquer problema de otimização combinatória

⚙️ O algoritmo completo

Abaixo está o código completo da classe GeneticAlgorithmService:

    use Illuminate\Support\Collection;

    class GeneticAlgorithmService
    {
        private array $items;
        private array $constraints;
        private string $objectiveAttribute;
        private string $objectiveGoal; // 'maximize' ou 'minimize'
        private int $populationSize;
        private int $generations;
        private float $mutationRate;
        private float $eliteRate;

        private array $population = [];

        public function __construct(object $config)
        {
            $this->items = $config->items;
            $this->constraints = $config->constraints;
            $this->objectiveAttribute = $config->objective->attribute;
            $this->objectiveGoal = $config->objective->goal;
            $this->populationSize = $config->populationSize;
            $this->generations = $config->generations;
            $this->mutationRate = $config->mutationRate;
            $this->eliteRate = $config->eliteRate;
        }

        // Executa o algoritmo e retorna a lista de itens selecionados
        public function run(): array
        {
            $this->initializePopulation();

            for ($gen = 0; $gen < $this->generations; $gen++) {
                $fitnessScores = $this->evaluatePopulation();

                $elite = $this->selectElite($fitnessScores);

                $newPopulation = $elite;

                while (count($newPopulation) < $this->populationSize) {
                    $parent1 = $this->tournamentSelection($fitnessScores);
                    $parent2 = $this->tournamentSelection($fitnessScores);

                    $child = $this->crossover($parent1, $parent2);
                    $this->mutate($child);

                    if ($this->checkConstraints($child)) {
                        $newPopulation[] = $child;
                    }
                }

                $this->population = $newPopulation;
            }

            $finalFitness = $this->evaluatePopulation();
            $bestIndex = $this->getBestIndex($finalFitness);

            return $this->decodeChromosome($this->population[$bestIndex]);
        }

        // Inicializa população com cromossomos aleatórios
        private function initializePopulation(): void
        {
            $this->population = [];
            $numItems = count($this->items);

            for ($i = 0; $i < $this->populationSize; $i++) {
                $chromosome = [];

                for ($j = 0; $j < $numItems; $j++) {
                    // 0 ou 1 para representar se o item está selecionado
                    $chromosome[] = rand(0, 1);
                }

                // Garante que o cromossomo respeite as restrições
                if ($this->checkConstraints($chromosome)) {
                    $this->population[] = $chromosome;
                } else {
                    // Caso não respeite, tenta gerar outro até encontrar válido
                    $i--;
                }
            }
        }

        // Avalia fitness de cada indivíduo na população
        private function evaluatePopulation(): array
        {
            $fitnessScores = [];

            foreach ($this->population as $chromosome) {
                $fitnessScores[] = $this->fitness($chromosome);
            }

            return $fitnessScores;
        }

        // Função fitness que calcula a pontuação do cromossomo
        private function fitness(array $chromosome): float
        {
            $totalObjective = 0;

            foreach ($chromosome as $index => $gene) {
                if ($gene === 1) {
                    $totalObjective += $this->items[$index][$this->objectiveAttribute];
                }
            }

            return $totalObjective;
        }

        // Seleciona os melhores indivíduos (elite) para a próxima geração
        private function selectElite(array $fitnessScores): array
        {
            $numElite = (int) round($this->populationSize * $this->eliteRate);

            // Associa fitness ao índice para ordenar
            $indexedFitness = array_map(fn($score, $i) => ['score' => $score, 'index' => $i], $fitnessScores, array_keys($fitnessScores));

            usort($indexedFitness, function ($a, $b) {
                if ($this->objectiveGoal === 'maximize') {
                    return $b['score'] <=> $a['score'];
                }
                return $a['score'] <=> $b['score'];
            });

            $elite = [];
            for ($i = 0; $i < $numElite; $i++) {
                $elite[] = $this->population[$indexedFitness[$i]['index']];
            }

            return $elite;
        }

        // Torneio para selecionar um cromossomo (parent)
        private function tournamentSelection(array $fitnessScores): array
        {
            $tournamentSize = 3;
            $candidates = [];

            $populationCount = count($this->population);

            for ($i = 0; $i < $tournamentSize; $i++) {
                $randomIndex = rand(0, $populationCount - 1);
                $candidates[] = ['chromosome' => $this->population[$randomIndex], 'fitness' => $fitnessScores[$randomIndex]];
            }

            usort($candidates, function ($a, $b) {
                if ($this->objectiveGoal === 'maximize') {
                    return $b['fitness'] <=> $a['fitness'];
                }
                return $a['fitness'] <=> $b['fitness'];
            });

            return $candidates[0]['chromosome'];
        }

        // Crossover simples: um ponto de corte
        private function crossover(array $parent1, array $parent2): array
        {
            $length = count($parent1);
            $cut = rand(1, $length - 2);

            return array_merge(
                array_slice($parent1, 0, $cut),
                array_slice($parent2, $cut)
            );
        }

        // Mutação com probabilidade definida
        private function mutate(array &$chromosome): void
        {
            for ($i = 0; $i < count($chromosome); $i++) {
                if (rand() / getrandmax() < $this->mutationRate) {
                    $chromosome[$i] = $chromosome[$i] === 1 ? 0 : 1;
                }
            }
        }

        // Verifica se o cromossomo respeita as restrições
        private function checkConstraints(array $chromosome): bool
        {
            foreach ($this->constraints as $constraint) {
                $attribute = $constraint->attribute;
                $max = $constraint->max;

                $total = 0;
                foreach ($chromosome as $index => $gene) {
                    if ($gene === 1) {
                        $total += $this->items[$index][$attribute];
                    }
                }

                if ($total > $max) {
                    return false;
                }
            }
            return true;
        }

        // Retorna o índice do melhor cromossomo baseado no fitness
        private function getBestIndex(array $fitnessScores): int
        {
            if ($this->objectiveGoal === 'maximize') {
                return array_keys($fitnessScores, max($fitnessScores))[0];
            }
            return array_keys($fitnessScores, min($fitnessScores))[0];
        }

        // Decodifica o cromossomo para retornar os itens selecionados
        private function decodeChromosome(array $chromosome): array
        {
            $selected = [];
            foreach ($chromosome as $index => $gene) {
                if ($gene === 1) {
                    $selected[] = $this->items[$index];
                }
            }
            return $selected;
        }
    }


Enter fullscreen mode Exit fullscreen mode

🤖 Feito em PHP. Mas a inteligência é universal.

Embora eu tenha feito tudo isso com PHP, a lógica do algoritmo genético é agnóstica.

Se quiser usar JavaScript, Python, Go, Rust ou qualquer outra linguagem, você pode traduzir o código manualmente ou usar uma IA como o ChatGPT para converter tudo.

A inteligência está na ideia. A linguagem é só o meio. 😉


🧠 Pronto para evoluir?

Se você já precisou montar uma escala, agenda ou lista de prioridades com muitas variáveis cruzadas, sabe que usar força bruta é lento ou inviável. Algoritmos genéticos simulam a evolução natural para encontrar soluções surpreendentemente boas.

E o melhor: com PHP puro, de forma testável, limpa e extensível.


Top comments (0)