Created
November 20, 2016 20:28
-
-
Save naezith/ef9841e1607cc444f94bd373a45fab61 to your computer and use it in GitHub Desktop.
Genetic Algorithm Solution for Expression Finding
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <vector> | |
#include <ctime> | |
#include <queue> | |
#include <stack> | |
#include <memory> | |
int MAX_GENERATION = 10000; | |
int POPULATION_SIZE; | |
int TARGET_NUM; | |
int NUM_COUNT; | |
int NODE_COUNT; | |
int SIGN_COUNT; | |
std::vector<int> numbers; | |
char ops[] = {'+', '-', '*', '/'}; | |
class Node { | |
public: | |
Node() {}; | |
Node(char _op) : op(_op) {}; | |
Node(int _num) : num(_num) {}; | |
Node& operator=(Node& n) { | |
for(int r = 0; r < 2; ++r) l[r] = std::move(n.l[r]); | |
copy(n); | |
return *this; | |
} | |
void copy(const Node& n) { | |
op = n.op; | |
num = n.num; | |
} | |
float calc() { | |
if(op) { | |
switch(op){ | |
case '+': return l[0]->calc() + l[1]->calc(); | |
case '-': return l[0]->calc() - l[1]->calc(); | |
case '*': return l[0]->calc() * l[1]->calc(); | |
case '/': return l[0]->calc() / l[1]->calc(); | |
} | |
} | |
return num; | |
} | |
bool isSign() { | |
return op != 0; | |
} | |
std::string infoAsStr() { | |
std::string str = ""; | |
if(isSign()) return str + op; | |
return std::to_string(num); | |
} | |
std::string toString() { | |
if(op) { | |
switch(op){ | |
case '+': return "(" + l[0]->toString() + " + " + l[1]->toString() + ")"; | |
case '-': return "(" + l[0]->toString() + " - " + l[1]->toString() + ")"; | |
case '*': return "(" + l[0]->toString() + " * " + l[1]->toString() + ")"; | |
case '/': return "(" + l[0]->toString() + " / " + l[1]->toString() + ")"; | |
} | |
} | |
return std::to_string(num); | |
} | |
char op = 0; | |
int num = 0; | |
std::unique_ptr<Node> l[2]; | |
Node* p = nullptr; | |
}; | |
class Tree { | |
public: | |
Tree(bool auto_generate = true) { if(auto_generate) generateRandomly(); }; | |
Tree(const Tree& t) { *this = t; } | |
Tree& operator=(const Tree& t) { | |
root.reset(t.root.get()); | |
fitness = t.fitness; | |
return *this; | |
} | |
float getFitness() { return fitness; } | |
void calcFitness() { | |
fitness = abs(TARGET_NUM - root->calc()); | |
} | |
std::unique_ptr<Node> root; | |
private: | |
float fitness; | |
std::unique_ptr<Node> getRandomNode(std::vector<std::unique_ptr<Node>>& pool) { | |
if(pool.empty()) return nullptr; | |
int r = std::rand() % pool.size(); | |
std::unique_ptr<Node> n = std::move(pool[r]); | |
pool.erase(pool.begin() + r); | |
return n; | |
}; | |
void generateRandomly() { | |
std::vector<std::unique_ptr<Node>> pool; | |
// Initialize leafs | |
for(unsigned i = 0; i < numbers.size(); ++i) pool.push_back(std::make_unique<Node>(numbers[i])); | |
// Operators | |
while(!pool.empty()) { | |
std::unique_ptr<Node> sign = std::make_unique<Node>(ops[rand() % 4]); | |
for(int r = 0; r < 2; ++r) { | |
sign->l[r] = getRandomNode(pool); | |
sign->l[r]->p = sign.get(); | |
} | |
if(pool.empty()) root = std::move(sign); | |
else pool.push_back(std::move(sign)); | |
} | |
calcFitness(); | |
}; | |
}; | |
Tree* randomSelection(std::vector<std::unique_ptr<Tree>>& p) { | |
float sum_fitness = 0; | |
for(unsigned i = 0; i < p.size(); ++i) sum_fitness += p[i]->getFitness(); | |
float val = std::rand() % std::max(1, (int)sum_fitness); | |
for(unsigned i = 0; i < p.size(); ++i) { | |
val -= p[i]->getFitness(); | |
if(val <= 0) return p[i].get(); | |
} | |
return p[p.size() - 1].get(); | |
} | |
std::unique_ptr<Tree> crossOver(Tree* x, Tree* y) { | |
// Pick the cut nodes randomly | |
Tree* trees[2] = { x, y }; | |
std::unique_ptr<Tree> child = std::make_unique<Tree>(); | |
Node genome_signs[3][SIGN_COUNT]; // 2 parents, 1 children | |
Node genome_nums[3][NUM_COUNT]; // 2 parents, 1 children | |
Node genome[3][NODE_COUNT]; // 2 parents, 1 children | |
// Copy parent genomes | |
{ | |
for(unsigned k = 0; k < 2; ++k) { | |
std::stack<Node*> q; | |
q.push(trees[k]->root.get()); | |
int g = 0, g_s = 0, g_n = 0; | |
genome[k][g++].copy(*q.top()); | |
if(q.top()->isSign()) genome_signs[k][g_s++].copy(*q.top()); | |
else genome_nums [k][g_n++].copy(*q.top()); | |
while(!q.empty()) { | |
Node* n = q.top(); | |
q.pop(); | |
// Push leafs | |
for(int r = 0; r < 2; ++r){ | |
if(n->l[r] != nullptr) { | |
q.push(n->l[r].get()); | |
genome[k][g++].copy(*q.top()); | |
if(q.top()->isSign()) genome_signs[k][g_s++].copy(*q.top()); | |
else genome_nums [k][g_n++].copy(*q.top()); | |
} | |
} | |
} | |
} | |
} | |
// Crossover numbers and signs separately, signs first, Uniform Crossover. | |
{ | |
for(int i = 0; i < SIGN_COUNT; ++i) { | |
genome_signs[2][i].copy(rand() % 2 ? genome_signs[0][i] : genome_signs[1][i]); | |
} | |
} | |
// Now crossover the numbers, Order-based Crossover | |
{ | |
bool temp[NUM_COUNT]; // Fill this array with 0, 1 randomly | |
std::vector<Node*> temp0s; | |
for(int i = 0; i < NUM_COUNT; ++i) { | |
temp[i] = rand() % 2; | |
// Copy the 1 ones directly | |
if(temp[i] == 1) genome_nums[2][i].copy(genome_nums[0][i]); | |
else { // Save the 0 ones in temp0s, we will add them with the order of parent-2 later. | |
temp0s.push_back(&genome_nums[0][i]); | |
} | |
} | |
for(int i = 0; i < NUM_COUNT && temp0s.size() > 0; ++i) { | |
if(temp[i] == 0) { // Fill these empty places with temp0s in parent-2 order | |
// Find the first in parent-2 | |
bool found = false; | |
for(int j = 0; j < NUM_COUNT && !found; ++j) { // Loop parent-2 | |
for(unsigned k = 0; k < temp0s.size() && !found; ++k) { // Check if current number is in temp0s list | |
// Found! | |
if(genome_nums[1][j].infoAsStr() == temp0s[k]->infoAsStr()){ | |
genome_nums[2][i].copy(genome_nums[1][j]); | |
temp0s.erase(temp0s.begin() + k); | |
found = true; | |
} | |
} | |
} | |
} | |
} | |
} | |
// Now merge numbers and signs with the parent-1 order | |
{ | |
int g_s = 0, g_n = 0; | |
for(int i = 0; i < NODE_COUNT; ++i) { | |
if(genome[0][i].isSign()) { | |
// Mutate sign | |
if(rand() % 100 < 20) { | |
int s; | |
do { s = rand() % 4; } while(ops[s] == genome_signs[2][g_s].op); | |
genome_signs[2][g_s].op = ops[s]; | |
} | |
genome[2][i].copy(genome_signs[2][g_s++]); | |
} | |
else genome[2][i].copy(genome_nums [2][g_n++]); | |
} | |
} | |
// Construct child | |
{ | |
std::stack<std::unique_ptr<Node>> st; | |
for(int i = NODE_COUNT - 1; i >= 0; --i) { | |
Node* n = &genome[2][i]; | |
if(n->isSign()) { | |
std::unique_ptr<Node> t = std::make_unique<Node>(n->op); | |
for(int r = 0; r < 2; ++r){ | |
t->l[r] = std::move(st.top()); | |
st.pop(); | |
} | |
st.push(std::move(t)); | |
} | |
else { | |
st.push(std::make_unique<Node>(n->num)); | |
} | |
} | |
child->root = std::move(st.top()); | |
child->calcFitness(); | |
} | |
return child; | |
} | |
int main() { | |
std::srand(std::time(NULL)); | |
while(1){ | |
std::cout << "\n\n*** GENETIC ALGORITHM SOLUTION FOR EXPRESSION FINDING *** Tolga Ay 13011057" << std::endl; | |
std::cout << "\n Will you enter inputs? (y/n): "; | |
char ans = ' '; | |
do { | |
ans = std::cin.get(); | |
} while(ans != 'y' && ans != 'n'); | |
if(ans == 'y') { | |
std::cout << "\n Population Size: (e.g. 100): "; | |
std::cin >> POPULATION_SIZE; | |
std::cout << "\n Target Number: "; | |
std::cin >> TARGET_NUM; | |
std::cout << "\n Input Number Count: "; | |
std::cin >> NUM_COUNT; | |
for(int i = 0; i < NUM_COUNT; ++i) { | |
std::cout << "\n Num #" << i << ": "; | |
int num; | |
std::cin >> num; | |
numbers.push_back(num); | |
} | |
} | |
else { | |
POPULATION_SIZE = 20; | |
TARGET_NUM = 72; | |
NUM_COUNT = 6; | |
numbers = {1, 3, 4, 7, 8, 9}; | |
} | |
std::cout << "\n" << "Target number: " << TARGET_NUM << std::endl; | |
POPULATION_SIZE = 5000; | |
NODE_COUNT = 2*NUM_COUNT - 1; | |
SIGN_COUNT = NUM_COUNT - 1; | |
// Initialize the population | |
std::vector<std::unique_ptr<Tree>> population(POPULATION_SIZE); | |
std::cout << "Population size " << population.size() << "\n"<< std::endl; | |
for(unsigned i = 0; i < population.size(); ++i) population[i] = std::make_unique<Tree>(); | |
// Create new generations | |
bool found = false; | |
for(int t = 0; t < MAX_GENERATION && !found; ++t) { | |
std::vector<std::unique_ptr<Tree>> new_population; | |
// Create new children | |
for(unsigned i = 0; i < population.size(); ++i) { | |
//std::cout << "\nCreating new child..."; | |
std::unique_ptr<Tree> child = crossOver(randomSelection(population), randomSelection(population)); | |
if(child->root->calc() == TARGET_NUM) { | |
std::cout << "Generation: " << t << "\t\tChild #" << i << " Found answer: " << child->root->toString() << " = " << child->root->calc() << std::endl; | |
found = true; break; | |
} | |
new_population.push_back(std::move(child)); | |
} | |
if(found) break; | |
// Make the new population, current population | |
for(unsigned i = 0; i < population.size(); ++i) population[i] = std::move(new_population[i]); | |
// Find the best child | |
float max_fitness = 0; | |
Tree* best = population[0].get(); | |
for(unsigned i = 0; i < population.size(); ++i) { | |
if(population[i]->getFitness() > max_fitness) { | |
max_fitness = population[i]->getFitness(); | |
best = population[i].get(); | |
} | |
} | |
std::cout << "Generation: " << t << "\t\tBest Child: " << best->root->toString() << " = " << best->root->calc() << std::endl; | |
} | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment