class Individual attr_reader :dna, :fitness def initialize(dna) @dna = dna @fitness = nil end def fit(fitnessFunc) @fitness = fitnessFunc.call(dna) end def produce(secondParent, dnaCombinator) Individual.new(dnaCombinator.call(@dna, secondParent.dna)) end def to_s "[#{@dna}: #{@fitness}]" end end class Generation def initialize(size, maxSurvivors, minFitness, fitnessFunc, dnaCombinator) @size = size @maxSurvivors = maxSurvivors @minFitness = minFitness @fitnessFunc = fitnessFunc @dnaCombinator = dnaCombinator @individuals = [] @survivors = nil end def add(individual) raise "Generation overflow" unless has_more_space ensure_fitness_defined individual @individuals.push individual end def has_more_space return @individuals.length + 1 < @size end def next nextGeneration = Generation.new(@size, @maxSurvivors, @minFitness, @fitnessFunc, @dnaCombinator) individualsLeft = @maxSurvivors survivors.each { |individual| nextGeneration.add individual; break if (individualsLeft -= 1) <= 0 } bestParents = RandomItemSelector.new(survivors, proc { |individual| individual.fitness }) while nextGeneration.has_more_space firstParent = bestParents.next secondParent = survivors[rand(survivors.length)] child = firstParent.produce(secondParent, @dnaCombinator) nextGeneration.add child end return nextGeneration end def survivors if @survivors.nil? @survivors = [] @individuals.each { |individual| ensure_fitness_defined individual @survivors.push individual if individual.fitness >= @minFitness } raise "No more survivors" if @survivors.length == 0 @survivors = best_first @survivors end return @survivors end def best @individuals.max { |a,b| a.fitness <=> b.fitness } end def ensure_fitness_defined(individual) individual.fit(@fitnessFunc) if individual.fitness.nil? end def best_first(items) items.sort { |a,b| b.fitness <=> a.fitness } end def to_s temp = "Generation(#{best.fitness}):" samplesLeft = 10 sorted = best_first @individuals sorted.each { |item| temp += " " + item.to_s; break if (samplesLeft -= 1) <= 0 } return temp end end class Universe def initialize(firstGeneration) @generation = firstGeneration end def next_generation @generation = @generation.next puts @generation end def run(count) for i in 1..count next_generation end end def best @generation.best end end Helper functions: def combine_random_bits(dna1, dna2, len) mask = rand(1 << len) return (dna1 & mask) | (dna2 & ~mask) end def mutate_random_bits(dna, len) mask = 1 << rand(len) return dna ^ mask end Usage sample. Resolving equation X - 80 = 0. seed = Generation.new(100, 70, 0.1, proc { |dna| 1 - (dna - 80).abs / 256.0 }, proc { |dna1, dna2| len = 8 dna = combine_random_bits(dna1, dna2, len) dna = mutate_random_bits(dna, len) if rand(10) == 1 return dna }) while seed.has_more_space seed.add Individual.new(rand(256)) end universe = Universe.new(seed) universe.run 100 puts "Best indivdual: #{universe.best}"