HiveBrain v1.2.0
Get Started
← Back to all entries
patternModerate

Genetic algorithm for "Hello World"

Submitted by: @import:stackexchange-codereview··
0
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.

  • 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

  • Give your constants all uppercase names see Style in reference manual.



  • If you make the pool size a parameter in the run_genetic function, 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_genetic currently 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:split

mutate

You want if your calculated offset is negative probably do not go under MinChar so exchange min with max

My 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.