Atualmente estou estudando Clojure e agora quase finalizando o sementre da universidade estão surgindo os trabalhos de implementação e resolvi fazê-los usando Clojure, por que não uma boa nota e a aprovação na disciplina para dar uma motivada não é não? 8D, e pretendo descrever alguns por aqui até para deixar meio documentado também, então vamos lá.
Search A*
O algoritmo A* (A estrela), é um algoritmo de busca, de caminho em grafo, heurística, algoritmos de busca heurística utilizam informações extras sobre o domínio problema para guiar o processo de busca.
http://maratonapuc.wikidot.com/apostilas:a-star
http://www.dainf.ct.utfpr.edu.br/~fabro/IA_I/busca/IA_Estrategias_Busca_Inf.pdf
http://www.cos.ufrj.br/~ines/courses/cos740/leila/cos740/apres_ia.pdf
https://pt.wikipedia.org/wiki/Algoritmo_A*
Função Heurística (h(n))
Utilizando uma função estima-se o custo de um determinado nó(estado) até o objetivo final para ver o quanto esse estado é bom o suficiente para atingir o objetivo em relação aos outros estados, essa função é conhecida como função heurística (h). Essa função é específica para cada problema e deve ser analisada e criada de acordo com o mesmo.
Função de custo (g(n))
Usando outra função (g), avalia-se o custo total para chagar do estado inicial até o estado que esta sendo avaliado.
Função de avaliação (f(n))
A função global de avaliação (f(n)) dá-se pela soma da função heurística h(n) e a função de custo g(n), f(n) = g(n) + h(n).
Para esse exercício utilizei um grafo como na imagem a seguir, o objetivo
é ir do estado A até o estado G pelo menor caminho.
Criando o projeto
Caso não saiba como criar um projeto em clojure, tem um artigo com uma
introdução para criação projetos e dependências em Clojure:
Vou criar um projeto chamado astar-algorithm-clojure
utilizando o
Liningen:
lein new astar-algorithm-clojure
Após isso, teremos a estrutura de dirétório do projeto, irei criar todas
as funções no arquivo padrão core.clj
no diretório src
.
Usando Map criei a estrutura que irá representar o grafo da
imagem, onde temos os estados pais e os estados filhos com o valor de
custo para ir do estado pai até o respectivo estado filho.
Onde :A
é o estado pai e :C
e :B
são seus estado filhos,
14
e 12
são os custos para ir de :A
até os estados filhos
respectivos e assim é com os outros estados também.
Utilizarei valores já processados para a função heurística de cada estado, também usando Map para representar essa função.
Nesse exemplo, aplicando a função heurística para o estado :A
retorna
o valor 30
, para :B
retorna 26
. Esse é o valor heurístico
dos estados, que será somado ao custo do estado para calcular o custo
total do caminho por esse estado.
Pode-se usar diversas formas para essa
função heurística, como por exemplo Manhattan Distance
, e
Euclidean Distance
.
Vamos criar duas funções, state-children
e children-values
onde
state-children
recebe um estado e retorna seus estados filhos em uma lista
e children-values
recebe um estado e retorna o custo associado aos
estados filhos.
Passando o estado :A
para a função state-children
retorna o seus
estado filhos (:C :B)
e para a função children-values
retorna o
custo associado aos seus filhos (14 12)
.
Agora criaremos nossas funções de custo (g) e heurística (h), a função
h
recebe um estado e retorna seu valor heurístico, e a função g
recebe o custo total atual e o custo de um estado filho o qual se esta
avaliando, e retorna o somatório de ambos.
Para nosso exemplo com o estado :A
passando para a função h
retornará o valor 30
e a função g
, pegando como exemplo o estado
:B
para avaliar e passando o custo total atual como 0
teremos
(0 + 12)
.
A nossa função de avaliação recebera o custo do estado que esta sendo
avaliado, o estado que esta sendo avaliado, e o custo total atual e irá
aplica a soma da função g
e h
.
A proxíma função get-costs
é responsável por calcular o custo, usando
a função de avaliação, para ir a cada estado filho do estado atual,
retornando todos os custos em uma lista.
Tomando como custo total atual 10
e o estado atual :A
, a função
retornará (54 52)
.
A função get-next-state
recebe o estado atual e os custos de seus
estados filhos, e retorna o estado filho que teve o menor custo calculado
pela função get-costs
, como teve o menor custo, esse estado será o
próximo do caminho.
A função get-real-cost
soma o custo atual do caminho com o custo do
próximo estado retornado pela get-next-state
, para ir guardando o custo
total do caminho.
E agora a função goal-test
recebe um estado e apenas verifica se o
estado é o objetivo.
A função principal do algoritmo, search-A*
, recebendo apenas um estado
como parâmetro, que será o estado inicial :A
, irá realizar uma
chamada recursiva passando o estado, o custo total atual como 0, e um
vetor vazio onde irá guardar os estado que farão parte do caminho (linha 2)
.
Primeiro testa se o estado atual é o ojetivo (linha 4)
, se não for,
verifica se o estado possui estados filhos (linha 8)
, caso possui,
irá pegar o custo para ir para cada um dos estado filhos em uma lista
costs
(linha 9)
, após isso, irá pegar o estado filho com menor
custo get-nest-state
(linha 10)
.
Depois de descobrir o estado de menor custo, faz uma chamada recursiva
passando o estado retornado de menor custo, next-state
, como estado
atual, o calculo do custo como custo total atual, e o vetor com o estado
inserido, para o próximo processamento (linha 11)
.
Executando essas ações recursivamente até encontrar o estado objetivo
(linha 4)
, ao encontrar o estado objetivo apenas vai inseri-lo no
vetor onde estão os estados do caminho, imprimir o caminho encontrado e
o custo total do caminho.
E a função inicial do algoritmo que vai chamar a search-A*
Se rodarmos o algoritmo com o exemplo feito, teremos a seguinte saida:
[:A :C :E :D :G]
43
Que é o caminho mais curto encontrado pelo algoritmo e o custo total do caminho.
Uma questão que ficou em aberto nesse exemplo é quando o algoritmo encontrar um estado sem filhos, simplesmente irá parar, uma solução pode ser implmentar uma forma de retroceder até um estado que tenha estados filhos para continuar o processo.
Conclusão
O objetivo é só demostrar uma alternativa de implementação e como resolvi os trabalhos em Clojure, além de praticar o desenvolvimento nessa tecnologia que estou estudando. Não entrei em detalhes sobre a linguagem, espero escrever mais sobre futuramente, também como irá surgir novos trabalhos do curso tentarei resolvê-los utilizando Clojure e se possível escrever algo sobre também como forma de estudar.
O código completo podem ver aqui
Dúvidas, sugestões… podem deixar comentários, e compartilhar caso gostou :D.