141 lines
3.5 KiB
Julia
141 lines
3.5 KiB
Julia
import ExcelReaders
|
|
import DataFrames
|
|
|
|
POPSIZE = 5
|
|
mu = 1
|
|
lambda = 4
|
|
GENLIMIT = 20
|
|
TARGETCALORIES = 2000
|
|
CANDIDATESIZE = 4
|
|
|
|
data = ExcelReaders.readxlsheet("./data/nutrional_information_5917.xlsx", "Sheet2", skipstartrows=1)
|
|
header = ExcelReaders.readxlsheet("./data/nutrional_information_5917.xlsx", "Sheet2", nrows=1)
|
|
|
|
# Convert to symbols to build header row.
|
|
for i = 1:length(header)
|
|
tmp = header[i]
|
|
tmp = Symbol(tmp)
|
|
header[i] = tmp
|
|
end
|
|
header = dropdims(reshape(header, :, 1), dims=2)
|
|
df = DataFrames.DataFrame()
|
|
|
|
# Finally, construct our dataframe
|
|
for i = 1:length(header)
|
|
df[header[i]] = data[2:end, i]
|
|
end
|
|
|
|
"""
|
|
Our mutator function
|
|
|
|
steps through the parent, and randomly selects the
|
|
allelles to delete. Will replace the alleles with new
|
|
alleles (meals).
|
|
"""
|
|
function mutate(parent)
|
|
|
|
# Copy the parent so we can do some work.
|
|
child = deepcopy(parent)
|
|
toDelete = []
|
|
|
|
for i in 1:size(parent, 1)
|
|
if rand(Float64) > 0.5 # NOTE: make this tunable
|
|
push!(toDelete, i)
|
|
end
|
|
# If we get tails, delete the row and push a new one to it.
|
|
end
|
|
|
|
# Delete all rows we don't want at once.
|
|
DataFrames.deleterows!(child, toDelete)
|
|
|
|
# Add new random rows from the ones we deleted
|
|
for i in 1:length(toDelete)
|
|
push!(child, df[randRow(), :])
|
|
end
|
|
|
|
child
|
|
end
|
|
|
|
"""
|
|
fitness(candidate::DataFrames.DataFrame)
|
|
|
|
Calculate the fitness of the candidate, which is the
|
|
absolute value of the difference of TARGETCALORIES and the sum
|
|
of all calories in the meal.
|
|
"""
|
|
function fitness(candidate::DataFrames.DataFrame)
|
|
abs(TARGETCALORIES - sum(+, candidate[:Calories]))
|
|
end
|
|
|
|
"""
|
|
Helper function to generate a random row index
|
|
used by randomCandidate
|
|
"""
|
|
function randRow()
|
|
# Generate a random row index
|
|
abs(rand(Int) % size(df, 1)) + 1
|
|
end
|
|
|
|
"""
|
|
Helper function to generate a random candidate from the dataset.
|
|
|
|
n is the size of the candidate.
|
|
"""
|
|
function randomCandidate(n::Integer)
|
|
# Select n random rows from the dataset.
|
|
rows = [randRow() for i = 1:n]
|
|
df[rows, :]
|
|
end
|
|
|
|
|
|
"""
|
|
generateInitialPopulation(lambda::Integer, candidateSize::Integer)
|
|
|
|
From our dataset, generate an array of initial candidates to begin the search.
|
|
"""
|
|
function generateInitialPopulation(lambda::Integer, candidateSize::Integer)
|
|
[randomCandidate(candidateSize) for i = 1:lambda]
|
|
end
|
|
|
|
function main(genLimit)
|
|
# Generate the initial population.
|
|
pop = generateInitialPopulation(lambda, CANDIDATESIZE)
|
|
best = nothing
|
|
generationNum = 0
|
|
fit = nothing
|
|
parents = nothing
|
|
results = Dict{Integer, Float64}()
|
|
|
|
while generationNum <= genLimit
|
|
# Assess the fitness of parents
|
|
for parent in pop
|
|
fit = fitness(parent)
|
|
if best === nothing || fit < fitness(best)
|
|
best = parent
|
|
end
|
|
end
|
|
|
|
# Grab our best fitness for logging purposes.
|
|
bestFitness = fitness(best)
|
|
results[generationNum+1] = bestFitness
|
|
|
|
# Copy the best mu parents into the population.
|
|
sort!(pop, by = x -> fitness(x))
|
|
parents = pop[1:mu]
|
|
pop = deepcopy(parents)
|
|
|
|
# Employ our (mu + lambda) strategy by generating lambda/mu kids.
|
|
for p in parents
|
|
for i = 1:(lambda/mu)
|
|
push!(pop, mutate(p))
|
|
end
|
|
end
|
|
|
|
# println("Generation $generationNum, best $best, fitness $bestFitness")
|
|
generationNum += 1
|
|
end
|
|
|
|
results
|
|
|
|
end
|
|
# search(generateInitialPopulation()) |