| #!/usr/bin/env xcrun swift |
| |
| /* |
| gen.swift is a direct port of cfdrake's helloevolve.py from Python 2.7 to Swift 3 |
| -------------------- https://gist.github.com/cfdrake/973505 --------------------- |
| |
| gen.swift implements a genetic algorithm that starts with a base |
| population of randomly generated strings, iterates over a certain number of |
| generations while implementing 'natural selection', and prints out the most fit |
| string. |
| The parameters of the simulation can be changed by modifying one of the many |
| global variables. To change the "most fit" string, modify OPTIMAL. POP_SIZE |
| controls the size of each generation, and GENERATIONS is the amount of |
| generations that the simulation will loop through before returning the fittest |
| string. |
| |
| This program subject to the terms of The MIT License listed below. |
| ---------------------------------------------------------------------------------- |
| Copyright (c) 2016 Blaine Rothrock |
| |
| Permission is hereby granted, free of charge, to any person obtaining a copy of |
| this software and associated documentation files (the "Software"), to deal in the |
| Software without restriction, including without limitation the rights to use, |
| copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the |
| Software, and to permit persons to whom the Software is furnished to do so, subject |
| to the following conditions: |
| |
| The above copyright notice and this permission notice shall be included in all |
| copies or substantial portions of the Software. |
| |
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, |
| INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A |
| PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT |
| HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
| OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE |
| SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
| """ |
| */ |
| |
| importFoundation |
| |
| let OPTIMAL ="Hello, World" |
| let DNA_SIZE = OPTIMAL.characters.count |
| let POP_SIZE =50 |
| let GENERATIONS =5000 |
| let MUTATION_CHANCE =100 |
| |
| // HELPERS |
| |
| /* |
| String extension to convert a string to ascii value |
| */ |
| extensionString { |
| var asciiArray: [UInt32] { |
| return unicodeScalars.filter{$0.isASCII}.map{$0.value} |
| } |
| } |
| |
| /* |
| extenstion to convert a character to ascii value |
| */ |
| extensionCharacter { |
| var asciiValue:UInt32? { |
| returnString(self).unicodeScalars.filter{$0.isASCII}.first?.value |
| } |
| } |
| |
| /* |
| helper function to return a random character string |
| */ |
| funcrandomChar() ->String { |
| |
| let letters : NSString ="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ,." |
| let len =UInt32(letters.length-1) |
| |
| let rand =arc4random_uniform(len) |
| returnString(Character(UnicodeScalar(letters.character(at: Int(rand)))!)) |
| |
| // NOTE: above solution is faster |
| //return String(letters[letters.index(letters.startIndex, offsetBy: Int(rand))]) |
| } |
| |
| // END HELPERS |
| |
| /* |
| calculated the fitness based on approximate string matching |
| compares each character ascii value difference and adds that to a total fitness |
| optimal string comparsion = 0 |
| */ |
| funccalculateFitness(dna:String, optimal:String) ->Int { |
| |
| var fitness =0 |
| let dnaLetters = dna.characters.map { String($0) } |
| let optimalLetters = optimal.characters.map { String($0) } |
| for c in0...dna.characters.count-1 { |
| fitness +=abs(Int(Character(dnaLetters[c]).asciiValue!) -Int(Character(optimalLetters[c]).asciiValue!)) |
| } |
| return fitness |
| } |
| |
| /* |
| randomly mutate the string |
| */ |
| funcmutate(dna:String, mutationChance:Int, dnaSize:Int) ->String { |
| var outputDna ="" |
| |
| let dnaLetters = dna.characters.map { returnString($0) } |
| |
| for i in0..<dnaSize { |
| let rand =Int(arc4random_uniform(UInt32(mutationChance))) |
| if rand ==1 { |
| let ranchar =String(randomChar())! |
| outputDna += ranchar |
| } else { |
| outputDna += dnaLetters[i] |
| } |
| } |
| |
| return outputDna |
| } |
| |
| /* |
| combine two parents to create an offspring |
| parent = xy & yx, offspring = xx, yy |
| */ |
| funccrossover(dna1:String, dna2:String, dnaSize:Int) -> (dna1:String, dna2:String) { |
| let pos =Int(arc4random_uniform(UInt32(dnaSize-1))) |
| |
| let dna1Index1 = dna1.index(dna1.startIndex, offsetBy: pos) |
| let dna2Index1 = dna2.index(dna2.startIndex, offsetBy: pos) |
| |
| return (dna1.substring(to: dna1Index1) + dna2.substring(from: dna2Index1), dna2.substring(to: dna2Index1) + dna1.substring(from: dna1Index1)) |
| } |
| |
| |
| /* |
| returns a random population, used to start the evolution |
| */ |
| funcrandomPopulation(populationSize: Int, dnaSize: Int) -> [String] { |
| |
| let letters : NSString ="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ,." |
| let len =UInt32(letters.length) |
| |
| var randomString ="" |
| |
| var pop = [String]() |
| |
| for_in0..<populationSize { |
| var dna ="" |
| for_in0..<dnaSize { |
| let rand =arc4random_uniform(len) |
| var nextChar = letters.character(at: Int(rand)) |
| dna +=NSString(characters: &nextChar, length: 1) asString |
| } |
| pop.append(dna) |
| } |
| return pop |
| } |
| |
| |
| /* |
| function to return random canidate of a population randomally, but weight on fitness. |
| */ |
| funcweightedChoice(items:[(item:String, weight:Double)]) -> (item:String, weight:Double) { |
| var weightTotal =0.0 |
| for itemTuple in items { |
| weightTotal += itemTuple.weight; |
| } |
| |
| var n =Double(arc4random_uniform(UInt32(weightTotal *1000000.0))) /1000000.0 |
| |
| for itemTuple in items { |
| if n < itemTuple.weight { |
| return itemTuple |
| } |
| n = n - itemTuple.weight |
| } |
| return items[1] |
| } |
| |
| funcmain() { |
| |
| // generate the starting random population |
| var population:[String] =randomPopulation(populationSize: POP_SIZE, dnaSize: DNA_SIZE) |
| // print("population: \(population), dnaSize: \(DNA_SIZE) ") |
| |
| var fittestString ="" |
| |
| for generation in0...GENERATIONS { |
| print("Generation \(generation) with random sample: \(population[0])") |
| |
| var weightedPopulation = [(item:String, weight:Double)]() |
| |
| // calulcated the fitness of each individual in the population |
| // and add it to the weight population (weighted = 1.0/fitness) |
| for individual in population { |
| let fitnessValue =calculateFitness(dna: individual, optimal: OPTIMAL) |
| |
| var pair = ("",0.0) |
| if fitnessValue ==0 { |
| pair = (individual, 1.0) |
| } else { |
| pair = (individual, 1.0/Double(fitnessValue)) |
| } |
| |
| weightedPopulation.append(pair) |
| } |
| |
| population = [] |
| |
| // create a new generation using the individuals in the origional population |
| for i in0...POP_SIZE/2 { |
| let ind1 =weightedChoice(items: weightedPopulation) |
| let ind2 =weightedChoice(items: weightedPopulation) |
| |
| var offspring =crossover(dna1: ind1.item, dna2: ind2.item, dnaSize: DNA_SIZE) |
| |
| // append to the population and mutate |
| population.append(mutate(dna: offspring.dna1, mutationChance: MUTATION_CHANCE, dnaSize: DNA_SIZE)) |
| population.append(mutate(dna: offspring.dna2, mutationChance: MUTATION_CHANCE, dnaSize: DNA_SIZE)) |
| } |
| |
| let fitnessString = population[0] |
| var minFitness =calculateFitness(dna: fitnessString, optimal: OPTIMAL) |
| |
| // parse the population for the fittest string |
| for indv in population { |
| let indvFitness =calculateFitness(dna: indv, optimal: OPTIMAL) |
| if indvFitness < minFitness { |
| fittestString = indv |
| minFitness = indvFitness |
| } |
| } |
| } |
| print("fittest string: \(fittestString)") |
| } |
| |
| main() |