Reputation: 65
I'm writing a genetic algorithm as a project for my artificial intelligence class. I'm familiar with the concept behind a GA but my experience of Haskell is limited. There is only one thing that's left to do in the program and that's to make a function which loops my other functions. I will present my function and explain the problem more in detail:
This is the function for the second generation. I get the parents, mate them and mutate the genomes, and then pass the new genomes into a list:
generation1 = initialPopulation
generation2 = [(mutate (mate (fTTS generation1) (sTTS generation1))) | x <- [1..100]]
This works great. I can create a new generation and repeat:
generation3 = [(mutate (mate (fTTS generation2) (sTTS generation2))) | x <- [1..100]]
So for each new generation I'm one step closer to the target genome (which is a String in my case). I want to generate a new generation until the target String is reached. I thought that a basic recursion would solve this, as in:
g 0 = initialPopulation
g n = [(mutate (mate (fTTS (g (n - 1))) (sTTS (g (n - 1))))) | x <- [1..100]]
This works on my laptop up to g (3), but then the computation takes ages. My problem is that I don't really understand why. I thought that Haskell recursion worked like following:
-- g 0 = [(mutate (mate (fTTS initialPopulation) (sTTS initialPopulation))) | x <- [1..100]] = ["N4kiT","Tu1RT","Tu1R<"]
-- g 1 = [(mutate (mate (fTTS (g 0)) (sTTS (g 0)))) | x <- [1..100]]
-- g 2 = [(mutate (mate (fTTS (g 1)) (sTTS (g 1)))) | x <- [1..100]]
-- g 3 = [(mutate (mate (fTTS (g 2)) (sTTS (g 2)))) | x <- [1..100]]
Which in my head should be the same as the generation3 function above. I would appreciate if someone who knows more about Haskell could explain why I'm able to run a "generation15"-function without any trouble but not more than a "g (3)"-function before I have to force-exit in the console.
Thanks!
Upvotes: 3
Views: 117
Reputation: 52270
the problem is that g n
is not memoized - it will be re-calculated 2 * 1000 in your list-comprehension
g 0 = initialPopulation
g n =
let prev = g (n-1)
in [(mutate (mate (fTTS prev) (sTTS prev))) | x <- [1..100]]
should improve things (I guess it would be a good problem to get strict values too - but that's probably another question)
but I would not use it that way - instead write a:
nextGen prev = [(mutate (mate (fTTS prev) (sTTS prev))) | x <- [1..100]]
function and then you could do something like:
find gen = if goodEnough gen then gen else find (nextGen gen)
with a hopeful
best = find initialPopulation
(note that maybe you should have a exit-strategy after to much generations too - so you might want to include a generation counter or something similar)
Upvotes: 5