From de935b700ab32492791dbd9771a3fc841ad4044c Mon Sep 17 00:00:00 2001 From: Kelvin Lau Date: Tue, 5 Mar 2019 22:37:29 -0500 Subject: [PATCH] Updates README.markdown * now using new `random` API from the Swift collection protocols, instead of creating a new function that uses `arc4random` * updates code to Swift 4.2 * fixes some inconsistencies between terms begin used - such as `MAX_GENERATIONS` and `GENERATIONS` --- Genetic/README.markdown | 60 ++++++++++++++++------------------------- 1 file changed, 23 insertions(+), 37 deletions(-) diff --git a/Genetic/README.markdown b/Genetic/README.markdown index 18afb9639..5dc4747f2 100644 --- a/Genetic/README.markdown +++ b/Genetic/README.markdown @@ -5,7 +5,7 @@ A genetic algorithm (GA) is process inspired by natural selection to find high quality solutions. Most commonly used for optimization. GAs rely on the bio-inspired processes of natural selection, more specifically the process of selection (fitness), crossover and mutation. To understand more, let's walk through these processes in terms of biology: ### Selection ->**Selection**, in biology, the preferential survival and reproduction or preferential elimination of individuals with certain genotypes (genetic compositions), by means of natural or artificial controlling factors. [Britannica](britannica) +>**Selection**, in biology, the preferential survival and reproduction or preferential elimination of individuals with certain genotypes (genetic compositions), by means of natural or artificial controlling factors. In other words, survival of the fittest. Organisms that survive in their environment tend to reproduce more. With GAs we generate a fitness model that will rank individuals and give them a better chance for reproduction. @@ -60,35 +60,23 @@ extension String { let OPTIMAL:[UInt8] = "Hello, World".unicodeArray let DNA_SIZE = OPTIMAL.count let POP_SIZE = 50 -let MAX_GENERATIONS = 5000 +let GENERATIONS = 5000 let MUTATION_CHANCE = 100 ``` - The last piece we need for set up is a function to give us a random unicode value from our lexicon: - - ```swift - func randomChar(from lexicon: [UInt8]) -> UInt8 { - let len = UInt32(lexicon.count-1) - let rand = Int(arc4random_uniform(len)) - return lexicon[rand] - } - ``` - - **Note**: `arc4random_uniform` is strictly used in this example. It would be fun to play around with some of the [randomization in GameKit](https://developer.apple.com/library/content/documentation/General/Conceptual/GameplayKit_Guide/RandomSources.html) - ### Population Zero Before selecting, crossover and mutation, we need a population to start with. Now that we have the universe defined we can write that function: ```swift func randomPopulation(from lexicon: [UInt8], populationSize: Int, dnaSize: Int) -> [[UInt8]] { - + guard lexicon.count > 1 else { return [] } var pop = [[UInt8]]() (0.. Int { +func calculateFitness(dna: [UInt8], optimal: [UInt8]) -> Int { + guard dna.count == optimal.count else { return -1 } var fitness = 0 - (0...dna.count-1).forEach { c in - fitness += abs(Int(dna[c]) - Int(optimal[c])) + for index in dna.indices { + fitness += abs(Int(dna[index]) - Int(optimal[index])) } return fitness } @@ -121,13 +110,11 @@ Let's take a second and ask why on this one. Why would you not always want to se With all that, here is our weight choice function: -```swift -func weightedChoice(items:[(dna:[UInt8], weight:Double)]) -> (dna:[UInt8], weight:Double) { - - let total = items.reduce(0.0) { return $0 + $1.weight} - - var n = Double(arc4random_uniform(UInt32(total * 1000000.0))) / 1000000.0 - +func weightedChoice(items: [(dna: [UInt8], weight: Double)]) -> (dna: [UInt8], weight: Double) { + + let total = items.reduce(0) { $0 + $1.weight } + var n = Double.random(in: 0..<(total * 1000000)) / 1000000.0 + for item in items { if n < item.weight { return item @@ -136,25 +123,24 @@ func weightedChoice(items:[(dna:[UInt8], weight:Double)]) -> (dna:[UInt8], weigh } return items[1] } -``` -The above function takes a list of individuals with their calculated fitness. Then selects one at random offset by their fitness value. The horrible 1,000,000 multiplication and division is to insure precision by calculating decimals. `arc4random` only uses integers so this is required to convert to a precise Double, it's not perfect, but enough for our example. + +The above function takes a list of individuals with their calculated fitness. Then selects one at random offset by their fitness value. The horrible 1,000,000 multiplication and division is to insure precision by calculating decimals. `Double.random` only uses integers so this is required to convert to a precise Double, it's not perfect, but enough for our example. ## Mutation The all powerful mutation, the thing that introduces otherwise non existent fitness variance. It can either hurt of improve a individuals fitness but over time it will cause evolution towards more fit populations. Imagine if our initial random population was missing the charachter `H`, in that case we need to rely on mutation to introduce that character into the population in order to achieve the optimal solution. ```swift -func mutate(lexicon: [UInt8], dna:[UInt8], mutationChance:Int) -> [UInt8] { +func mutate(lexicon: [UInt8], dna: [UInt8], mutationChance: Int) -> [UInt8] { var outputDna = dna - (0.. [UInt8] { - let pos = Int(arc4random_uniform(UInt32(dnaSize-1))) - +func crossover(dna1: [UInt8], dna2: [UInt8], dnaSize: Int) -> [UInt8] { + let pos = Int.random(in: 0..