patternModerate
Genetic algorithm for "Hello World"
Viewed 0 times
geneticalgorithmworldforhello
Problem
I've written an Erlang implementation of the genetic algorithm for a "Hello World" program as described here.
This is my first time writing any code in Erlang and also my first time writing code in a functional language, so I'd really like some feedback on whether I'm doing things the "functional" way, whether I'm doing things the Erlang way, and what useful functional/Erlang features could make this code better. I'm not interested in general programming style feedback or whether there are bugs in this implementation (I'm sure there are). I'm slightly interested in efficiency, but not too much.
Let me describe the basic algorithm for those of you who aren't planning on reading the article (I don't think you need to understand all the details to be able to review the code). The idea is to "breed" strings that closer and closer to the target string.
With this process, eventually one of the strings will be the target string you are searching for. This depends on having a good fitness function, which I define as the sum of the difference of each character in a candidate string with the respective characters in the target string. So if a candidate string is "abc" and the target string was "acd", the first character has a difference of 0, second a difference of 1, and third a difference of one so its fitness score is 2. A lower fitness score is better.
```
-module(genetic).
-author("Gulshan Singh").
-export([run_genetic/1]).
-define(PoolSize, 400).
-define(CrossoverProb, 0.9).
-define(MutateProb, 0.2).
-define(Min
This is my first time writing any code in Erlang and also my first time writing code in a functional language, so I'd really like some feedback on whether I'm doing things the "functional" way, whether I'm doing things the Erlang way, and what useful functional/Erlang features could make this code better. I'm not interested in general programming style feedback or whether there are bugs in this implementation (I'm sure there are). I'm slightly interested in efficiency, but not too much.
Let me describe the basic algorithm for those of you who aren't planning on reading the article (I don't think you need to understand all the details to be able to review the code). The idea is to "breed" strings that closer and closer to the target string.
- Create a large pool of random strings of the target length.
- Choose four strings and pick the "fittest" two to breed (fit is defined later).
- Breed the two strings by either doing nothing, or by picking a point in both strings, and swapping substrings at that point.
- With the resulting two strings, either insert them directly into the pool of strings, or mutate them by moving a random character in the string up or down a bit, and then insert them.
With this process, eventually one of the strings will be the target string you are searching for. This depends on having a good fitness function, which I define as the sum of the difference of each character in a candidate string with the respective characters in the target string. So if a candidate string is "abc" and the target string was "acd", the first character has a difference of 0, second a difference of 1, and third a difference of one so its fitness score is 2. A lower fitness score is better.
```
-module(genetic).
-author("Gulshan Singh").
-export([run_genetic/1]).
-define(PoolSize, 400).
-define(CrossoverProb, 0.9).
-define(MutateProb, 0.2).
-define(Min
Solution
General Remarks
run_genetic
-
When compute a possible crossover, you can compute the result and store the result of the if as value.
-
When you perform mutation, you don't store the result of the call of the
-
As you should check also if the mutated string matches your target, this should be split in another function
check_pool / rand_string
You could write those simpler as
But this solution is potentially not as fast as you create a list from 1 to N first.
crossover
You could create the sublists with
mutate
You want if your calculated offset is negative probably do not go under
My changed version of run_genetic
- Give your constants all uppercase names see Style in reference manual.
- If you make the pool size a parameter in the
run_geneticfunction, you should match it with an unbound variable. If you always match it only with the constant, you could omit this parameter and only use the constant.
run_genetic
run_geneticcurrently does three different jobs which should be split in functions with different names:
- main entry point and initialization
- runs over all generations
- genetic algorithm steps for one generation (test fitness, mutation, crossover)
-
When compute a possible crossover, you can compute the result and store the result of the if as value.
-
When you perform mutation, you don't store the result of the call of the
mutate(Result1)-
As you should check also if the mutated string matches your target, this should be split in another function
check_pool / rand_string
You could write those simpler as
create_pool(Len, Num) ->
[rand_string(Len) || _ <- lists:seq(1,Num)].But this solution is potentially not as fast as you create a list from 1 to N first.
crossover
You could create the sublists with
lists:splitmutate
You want if your calculated offset is negative probably do not go under
MinChar so exchange min with maxMy changed version of run_genetic
run_genetic(Target) ->
random:seed(now()),
FirstGen = create_pool(length(Target), ?POOLSIZE),
produce_generations(FirstGen, Target, ?POOLSIZE, 1).
produce_generations(CurrentGen, Target, PoolSize, Iteration) ->
io:format("Starting iteration ~B~n", [Iteration]),
case populate(CurrentGen,[],Target, PoolSize) of
{not_found,NextGen} ->
produce_generations(NextGen,Target,PoolSize,Iteration+1);
_->
io:format("Found result at iteration ~B~n", [Iteration])
end.
populate(_LastGen,CurrentGen,_Target,0)->
{not_found,CurrentGen};
populate(LastGen,CurrentGen,Target,NumberMissingInPopulation) ->
{P1, P2} = choose_parents(LastGen, Target),
io:format("Breeding ~s (~B) and ~s (~B)~n",
[P1, fitness(P1, Target), P2, fitness(P2, Target)]),
CrossoverProb = random:uniform(),
if CrossoverProb =
{Result1, Result2} = crossover(P1, P2);
CrossoverProb > ?CROSSOVERPROB ->
{Result1, Result2} = {P1, P2}
end,
io:format("Result strings are ~s (~B) and ~s (~B)~n",
[Result1, fitness(Result1, Target),
Result2, fitness(Result2, Target)]),
Result=case check_add_and_continue(Result1,Result2,CurrentGen,Target) of
R={not_found,_Gen} ->
case MutateProb = random:uniform() of
MutateProb when MutateProb =
MutatedResult1=mutate(Result1),
MutatedResult2=mutate(Result2),
check_add_and_continue(MutatedResult1,MutatedResult2,CurrentGen,Target);
_-> R
end;
Found -> Found
end,
case Result of
%%include min here if population size can be uneven
{not_found,NewCurrentGen}->
populate(LastGen,NewCurrentGen,Target,NumberMissingInPopulation-2);
_-> Result
end.
check_add_and_continue(Child1,Child2,Generation,Target)->
NewCurrentGen=[Child1, Child2|Generation],
Found=case check_result(Child1, Target) or check_result(Child2, Target) of
true -> found;
false ->not_found
end,
{Found,NewCurrentGen}.Code Snippets
create_pool(Len, Num) ->
[rand_string(Len) || _ <- lists:seq(1,Num)].run_genetic(Target) ->
random:seed(now()),
FirstGen = create_pool(length(Target), ?POOLSIZE),
produce_generations(FirstGen, Target, ?POOLSIZE, 1).
produce_generations(CurrentGen, Target, PoolSize, Iteration) ->
io:format("Starting iteration ~B~n", [Iteration]),
case populate(CurrentGen,[],Target, PoolSize) of
{not_found,NextGen} ->
produce_generations(NextGen,Target,PoolSize,Iteration+1);
_->
io:format("Found result at iteration ~B~n", [Iteration])
end.
populate(_LastGen,CurrentGen,_Target,0)->
{not_found,CurrentGen};
populate(LastGen,CurrentGen,Target,NumberMissingInPopulation) ->
{P1, P2} = choose_parents(LastGen, Target),
io:format("Breeding ~s (~B) and ~s (~B)~n",
[P1, fitness(P1, Target), P2, fitness(P2, Target)]),
CrossoverProb = random:uniform(),
if CrossoverProb =< ?CROSSOVERPROB ->
{Result1, Result2} = crossover(P1, P2);
CrossoverProb > ?CROSSOVERPROB ->
{Result1, Result2} = {P1, P2}
end,
io:format("Result strings are ~s (~B) and ~s (~B)~n",
[Result1, fitness(Result1, Target),
Result2, fitness(Result2, Target)]),
Result=case check_add_and_continue(Result1,Result2,CurrentGen,Target) of
R={not_found,_Gen} ->
case MutateProb = random:uniform() of
MutateProb when MutateProb =< ?MutateProb ->
MutatedResult1=mutate(Result1),
MutatedResult2=mutate(Result2),
check_add_and_continue(MutatedResult1,MutatedResult2,CurrentGen,Target);
_-> R
end;
Found -> Found
end,
case Result of
%%include min here if population size can be uneven
{not_found,NewCurrentGen}->
populate(LastGen,NewCurrentGen,Target,NumberMissingInPopulation-2);
_-> Result
end.
check_add_and_continue(Child1,Child2,Generation,Target)->
NewCurrentGen=[Child1, Child2|Generation],
Found=case check_result(Child1, Target) or check_result(Child2, Target) of
true -> found;
false ->not_found
end,
{Found,NewCurrentGen}.Context
StackExchange Code Review Q#41622, answer score: 10
Revisions (0)
No revisions yet.