(versão 2, com erros de digitação corrigidos - espero que todos ...)
Olá Habr!
Agora ele estava sentado, fazendo um protótipo do algoritmo genético para um amigo. Inspirado para compartilhar, e alguns outros pensamentos ...
Dado (pedido pelo cliente): em um determinado
reino, o armazém tem 100 células. Os bens estão nele. Como levar a quantidade X para esvaziar todas as células envolvidas até o fim? Bem, você tem um tipo de célula [34, 10, 32, 32, 33, 41, 44, 4, 28, 23, 22, 28, 29, 14, 28, 15, 38, 49, 30, 24, 18, 9, 15, 8, 17, 9, 2, 7, 30, 29, 29, 2, 28, 23, 19, 4, 15, 30, 38, 3, 14, 21, 43, 50, 29, 14, 17, 12, 25, 15, 23, 28, 47, 38, 29, 7, 36, 45, 25, 6, 25, 11, 10, 1, 19, 37, 24, 27, 50, 12, 1, 1, 44, 22, 48, 13, 46, 49, 11, 33, 29, 4, 19, 33, 12, 3, 47, 27, 26, 45, 40, 37, 21, 2, 8, 41, 5, 1, 9, 5] - como discar, digamos, 40
Bem, você pode quebrar, com certeza há algo inteligente, mas você pode resolvê-lo com um algoritmo genético ...
A primeira pergunta: por que secar o cérebro, porque se você passar pela lista, precisará tirar as duas primeiras células para isso - a segunda, no entanto, será o restante. E você não precisa ir muito longe. Mas, para ver, o cliente é um perfeccionista, ele quer que seja como em uma farmácia. Provavelmente em um armazém celular vale seu peso em ouro. Isso acontece
A segunda pergunta: se você a classificar em ordem crescente, certamente poderá deslizar muito mais células ... No nosso exemplo, existem muitas células com menos de dez células. Provavelmente, o cliente também não quer;) Eu também me deparei com essas pessoas: eu sou o chefe aqui. Eles disseram a você - faça, não faça perguntas.
(Bem, não meu cliente, caso contrário eu perderia novamente a fé na mente humana ...)
Bem, que Deus esteja com ele, todo mundo tem suas próprias prioridades ... Então falaremos sobre algoritmos genéticos - de alguma forma precisamos resolver isso ... Vamos escrever em Java.
Para aqueles que nunca ouviram falar sobre eles antes: imite a Mãe Natureza.
- Comportamentos de codificação
- Verificamos quão bem cada opção funciona com a ajuda de uma referência adequada
- A melhor transmissão de seus atributos para a próxima geração
No último passo na natureza, existem dois componentes: cruzamento (troca de caracteres entre as mesmas partes do cromossomo) e mutação (alterações espontâneas no código genético). Olá, ensino médio;)
Provavelmente é tudo. Codificaremos de quais células extrair e das quais não. Temos 100 células, o que significa que nosso cromossomo terá 100 elementos verdadeiros / falsos. Por isso, peguei String, na qual zeros e uns serão escritos. Eles, é claro, serão 100.
A referência é a coisa mais importante nesse processo. A natureza está procurando um nicho, e a natureza do computador procurará um buraco no seu benchmark para enganá-lo e sobreviver. Maravilhoso, honestamente ...
Com tudo isso dito, algo como isto:
private static int benchmark(String chromosome, boolean verbose) { int sum = 0; char[] cArr = chromosome.toCharArray(); for (int i = 0; i < cnt_bins; i++) { if (cArr[i] == '1') { sum += stock[i]; if (verbose) System.out.println("storage bin " + i + ":\t+" + stock[i] + "\t= " + sum); } if (sum == target_qty) { return 0; } } return Math.abs(target_qty - sum); }
A idéia é que quanto mais longe estivermos do número desejado 40, pior. Se marcarmos 40, não avançaremos mais no cromossomo, vencemos. Classificaremos, é claro, em ordem crescente - quanto menos pontos de penalidade, melhor.
A primeira geração é criada aleatoriamente:
Como o algoritmo genético, de fato, se aproxima do objetivo, mas nem sempre o atinge, é importante limitar o número de gerações.
for (int generationNo = 0; generationNo < maxGenerationCnt; generationNo++) {
Classifique, deixe o melhor:
...
Criamos e multiplicamos, restauramos o tamanho da população. Ou seja, selecionamos aleatoriamente os pais, combinamos os mesmos sinais (se você tiver sorte - veja a bandeira da troca), modifique (a bandeira da mutação), espere um milagre ...
Bem, aqui está um milagre para você:Valor alvo: 40
Estoque: [34, 10, 32, 32, 33, 41, 44, 4, 28, 23, 22, 28, 29, 14, 28, 15, 38, 49, 30, 24, 18, 9, 15, 8 , 17, 9, 2, 7, 30, 29, 29, 2, 28, 23, 19, 4, 15, 30, 38, 3, 14, 21, 43, 50, 29, 14, 17, 12, 25 , 15, 23, 28, 47, 38, 29, 7, 36, 45, 25, 6, 25, 11, 10, 1, 19, 37, 24, 27, 50, 12, 1, 1, 44, 22 , 48, 13, 46, 49, 11, 33, 29, 4, 19, 33, 12, 3, 47, 27, 26, 45, 40, 37, 21, 2, 8, 41, 5, 1, 9 5]
Geração 0; Melhor / último pai / pior: 705/991/1580
Geração 1; Melhor / último pai / pior: 576/846/1175
Geração 2; Melhor / último pai / pior: 451/722/1108
Geração 3; Melhor / último pai / pior: 0/613/904
Solução encontrada
compartimento de armazenamento 7: +4 = 4
posição 10: +22 = 26
compartimento de armazenamento 13: +14 = 40
E aqui está o código inteiro package ypk; import java.io.IOException; import java.io.StringWriter; import java.util.AbstractMap.SimpleEntry; import java.util.stream.Collectors; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Random; public class YPK { private static int generation_size = 1000; private static int best_size = 200; private static int cnt_bins = 100; private static int max_stock = 50; private static double exchange_rate = .2; private static double mutation_rate = .01; private static Random rnd = new Random(); private static int target_qty = rnd.nextInt(cnt_bins * max_stock / 5);
E finalmente: se você se atrapalhar com os parâmetros - muitas mutações, tamanho populacional muito pequeno etc. - Estagnará ou até dará os piores resultados.
A propósito, esse problema já foi resolvido com frequência e as próximas gerações não são necessárias :) Se você quiser complicar a vida do computador, remova esse retorno do benchmark:
if (sum == target_qty) { return 0; }
Isso complicará a tarefa e fará com que o computador sofra um pouco ...
Boa sorte
m_OO_m