Skip to content

Commit 71ab3f8

Browse files
author
Blaine Rothrock
committed
algorithm proposal
1 parent df5adbb commit 71ab3f8

File tree

2 files changed

+181
-0
lines changed

2 files changed

+181
-0
lines changed

Genetic/README.markdown

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# todo
2+
refactoring https://gist.github.com/blainerothrock/efda6e12fe10792c99c990f8ff3daeba for swift 4. Creating a tutorial and writing in more playground friendly format

Genetic/gen.swift

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/*
2+
base .. to be refactored
3+
*/
4+
5+
import Foundation
6+
7+
// HELPERS
8+
/*
9+
String extension to convert a string to ascii value
10+
*/
11+
extension String {
12+
var asciiArray: [UInt8] {
13+
return unicodeScalars.filter{$0.isASCII}.map{UInt8($0.value)}
14+
}
15+
}
16+
17+
/*
18+
helper function to return a random character string
19+
*/
20+
func randomChar() -> UInt8 {
21+
22+
let letters : [UInt8] = " !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~".asciiArray
23+
let len = UInt32(letters.count-1)
24+
25+
let rand = Int(arc4random_uniform(len))
26+
return letters[rand]
27+
}
28+
29+
// END HELPERS
30+
31+
let OPTIMAL:[UInt8] = "Hello, World".asciiArray
32+
let DNA_SIZE = OPTIMAL.count
33+
let POP_SIZE = 50
34+
let GENERATIONS = 5000
35+
let MUTATION_CHANCE = 100
36+
37+
/*
38+
calculated the fitness based on approximate string matching
39+
compares each character ascii value difference and adds that to a total fitness
40+
optimal string comparsion = 0
41+
*/
42+
func calculateFitness(dna:[UInt8], optimal:[UInt8]) -> Int {
43+
44+
var fitness = 0
45+
for c in 0...dna.count-1 {
46+
fitness += abs(Int(dna[c]) - Int(optimal[c]))
47+
}
48+
return fitness
49+
}
50+
51+
/*
52+
randomly mutate the string
53+
*/
54+
func mutate(dna:[UInt8], mutationChance:Int, dnaSize:Int) -> [UInt8] {
55+
var outputDna = dna
56+
57+
for i in 0..<dnaSize {
58+
let rand = Int(arc4random_uniform(UInt32(mutationChance)))
59+
if rand == 1 {
60+
outputDna[i] = randomChar()
61+
}
62+
}
63+
64+
return outputDna
65+
}
66+
67+
/*
68+
combine two parents to create an offspring
69+
parent = xy & yx, offspring = xx, yy
70+
*/
71+
func crossover(dna1:[UInt8], dna2:[UInt8], dnaSize:Int) -> (dna1:[UInt8], dna2:[UInt8]) {
72+
let pos = Int(arc4random_uniform(UInt32(dnaSize-1)))
73+
74+
let dna1Index1 = dna1.index(dna1.startIndex, offsetBy: pos)
75+
let dna2Index1 = dna2.index(dna2.startIndex, offsetBy: pos)
76+
77+
return (
78+
[UInt8](dna1.prefix(upTo: dna1Index1) + dna2.suffix(from: dna2Index1)),
79+
[UInt8](dna2.prefix(upTo: dna2Index1) + dna1.suffix(from: dna1Index1))
80+
)
81+
}
82+
83+
84+
/*
85+
returns a random population, used to start the evolution
86+
*/
87+
func randomPopulation(populationSize: Int, dnaSize: Int) -> [[UInt8]] {
88+
89+
let letters : [UInt8] = " !\"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~".asciiArray
90+
let len = UInt32(letters.count)
91+
92+
var pop = [[UInt8]]()
93+
94+
for _ in 0..<populationSize {
95+
var dna = [UInt8]()
96+
for _ in 0..<dnaSize {
97+
let rand = arc4random_uniform(len)
98+
let nextChar = letters[Int(rand)]
99+
dna.append(nextChar)
100+
}
101+
pop.append(dna)
102+
}
103+
return pop
104+
}
105+
106+
107+
/*
108+
function to return random canidate of a population randomally, but weight on fitness.
109+
*/
110+
func weightedChoice(items:[(item:[UInt8], weight:Double)]) -> (item:[UInt8], weight:Double) {
111+
var weightTotal = 0.0
112+
for itemTuple in items {
113+
weightTotal += itemTuple.weight;
114+
}
115+
116+
var n = Double(arc4random_uniform(UInt32(weightTotal * 1000000.0))) / 1000000.0
117+
118+
for itemTuple in items {
119+
if n < itemTuple.weight {
120+
return itemTuple
121+
}
122+
n = n - itemTuple.weight
123+
}
124+
return items[1]
125+
}
126+
127+
func main() {
128+
129+
// generate the starting random population
130+
var population:[[UInt8]] = randomPopulation(populationSize: POP_SIZE, dnaSize: DNA_SIZE)
131+
// print("population: \(population), dnaSize: \(DNA_SIZE) ")
132+
var fittest = [UInt8]()
133+
134+
for generation in 0...GENERATIONS {
135+
print("Generation \(generation) with random sample: \(String(bytes: population[0], encoding:.ascii)!)")
136+
137+
var weightedPopulation = [(item:[UInt8], weight:Double)]()
138+
139+
// calulcated the fitness of each individual in the population
140+
// and add it to the weight population (weighted = 1.0/fitness)
141+
for individual in population {
142+
let fitnessValue = calculateFitness(dna: individual, optimal: OPTIMAL)
143+
144+
let pair = ( individual, fitnessValue == 0 ? 1.0 : 1.0/Double( fitnessValue ) )
145+
146+
weightedPopulation.append(pair)
147+
}
148+
149+
population = []
150+
151+
// create a new generation using the individuals in the origional population
152+
for _ in 0...POP_SIZE/2 {
153+
let ind1 = weightedChoice(items: weightedPopulation)
154+
let ind2 = weightedChoice(items: weightedPopulation)
155+
156+
let offspring = crossover(dna1: ind1.item, dna2: ind2.item, dnaSize: DNA_SIZE)
157+
158+
// append to the population and mutate
159+
population.append(mutate(dna: offspring.dna1, mutationChance: MUTATION_CHANCE, dnaSize: DNA_SIZE))
160+
population.append(mutate(dna: offspring.dna2, mutationChance: MUTATION_CHANCE, dnaSize: DNA_SIZE))
161+
}
162+
163+
fittest = population[0]
164+
var minFitness = calculateFitness(dna: fittest, optimal: OPTIMAL)
165+
166+
// parse the population for the fittest string
167+
for indv in population {
168+
let indvFitness = calculateFitness(dna: indv, optimal: OPTIMAL)
169+
if indvFitness < minFitness {
170+
fittest = indv
171+
minFitness = indvFitness
172+
}
173+
}
174+
if minFitness == 0 { break; }
175+
}
176+
print("fittest string: \(String(bytes: fittest, encoding: .ascii)!)")
177+
}
178+
179+
main()

0 commit comments

Comments
 (0)