forked from davecom/ClassicComputerScienceProblemsInJava
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGeneticAlgorithm.java
More file actions
139 lines (128 loc) · 4.45 KB
/
GeneticAlgorithm.java
File metadata and controls
139 lines (128 loc) · 4.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
// GeneticAlgorithm.java
// From Classic Computer Science Problems in Java Chapter 5
// Copyright 2020 David Kopec
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package chapter5;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
public class GeneticAlgorithm<C extends Chromosome<C>> {
public enum SelectionType {
ROULETTE, TOURNAMENT;
}
private ArrayList<C> population;
private double mutationChance;
private double crossoverChance;
private SelectionType selectionType;
private Random random;
public GeneticAlgorithm(List<C> initialPopulation,
double mutationChance, double crossoverChance, SelectionType selectionType) {
this.population = new ArrayList<>(initialPopulation);
this.mutationChance = mutationChance;
this.crossoverChance = crossoverChance;
this.selectionType = selectionType;
this.random = new Random();
}
// Use the probability distribution wheel to pick numPicks individuals
private List<C> pickRoulette(double[] wheel, int numPicks) {
List<C> picks = new ArrayList<>();
for (int i = 0; i < numPicks; i++) {
double pick = random.nextDouble();
for (int j = 0; j < wheel.length; j++) {
pick -= wheel[j];
if (pick <= 0) { // we had one that took us over, leads to a pick
picks.add(population.get(j));
break;
}
}
}
return picks;
}
// Pick a certain number of individuals via a tournament
private List<C> pickTournament(int numParticipants, int numPicks) {
// Find numParticipants random participants to be in the tournament
Collections.shuffle(population);
List<C> tournament = population.subList(0, numParticipants);
// Find the numPicks highest fitnesses in the tournament
Collections.sort(tournament, Collections.reverseOrder());
return tournament.subList(0, numPicks);
}
// Replace the population with a new generation of individuals
private void reproduceAndReplace() {
ArrayList<C> nextPopulation = new ArrayList<>();
// keep going until we've filled the new generation
while (nextPopulation.size() < population.size()) {
// pick the two parents
List<C> parents;
if (selectionType == SelectionType.ROULETTE) {
// create the probability distribution wheel
double totalFitness = population.stream()
.mapToDouble(C::fitness).sum();
double[] wheel = population.stream()
.mapToDouble(C -> C.fitness()
/ totalFitness)
.toArray();
parents = pickRoulette(wheel, 2);
} else { // tournament
parents = pickTournament(population.size() / 2, 2);
}
// potentially crossover the 2 parents
if (random.nextDouble() < crossoverChance) {
C parent1 = parents.get(0);
C parent2 = parents.get(1);
nextPopulation.addAll(parent1.crossover(parent2));
} else { // just add the two parents
nextPopulation.addAll(parents);
}
}
// if we have an odd number, we'll have 1 exra, so we remove it
if (nextPopulation.size() > population.size()) {
nextPopulation.remove(0);
}
// replace the reference/generation
population = nextPopulation;
}
// With mutationChance probability, mutate each individual
private void mutate() {
for (C individual : population) {
if (random.nextDouble() < mutationChance) {
individual.mutate();
}
}
}
// Run the genetic algorithm for maxGenerations iterations
// and return the best individual found
public C run(int maxGenerations, double threshold) {
C best = Collections.max(population).copy();
for (int generation = 0; generation < maxGenerations; generation++) {
// early exit if we beat threshold
if (best.fitness() >= threshold) {
return best;
}
// Debug printout
System.out.println("Generation " + generation +
" Best " + best.fitness() +
" Avg " + population.stream()
.mapToDouble(C::fitness).average().orElse(0.0));
reproduceAndReplace();
mutate();
C highest = Collections.max(population);
if (highest.fitness() > best.fitness()) {
best = highest.copy();
}
}
return best;
}
}