#!/usr/bin/env python

## Ilustrace 3 - problem batohu
"""
Porovnani vysledku:
https://www.r-bloggers.com/genetic-algorithms-a-simple-r-example/
Knapsack NP problem
"""
import itertools as it
import random
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.pyplot import subplot

# # -- definition of the variables
MAX_ITEM = 7
MAX_WEIGHT = 20

MAX_SURVIVAL_POINTS = 100
NUMBER_OF_GENERATIONS = 100
POPULATION_SIZE = 20
NBR_ITEMS = 7
MUTATE_RATE = 0.01
CROSSOVER_RATE = 5
MAX_ELITE = 2

df_dataset = pd.DataFrame([["pocketknife", 10, 1], ["beans", 20, 5], ["potatoes", 15, 10],
                           ["unions", 2, 1], ["sleepingbag", 30, 7], ["rope", 10, 5],
                           ["compas", 30, 1]], columns=["Item", "Survivalpoints", "Weight"])

# # -- help functions
s = lambda x: df_dataset.loc[x][1]
w = lambda x: df_dataset.loc[x][2]
p = lambda x: df_dataset.loc[x][0]

# ifilter is not included in Python 3, help function
def ifilter(predicate, iterable):
    # ifilter(lambda x: x%2, range(10)) --> 1 3 5 7 9
    if predicate is None:
        predicate = bool
    for x in iterable:
        if predicate(x):
            yield x


# # -- definitions of the genetics algorithm
def fitness(currentpopulation):
    """
    :param currentpopulation: np.random.randint ( 2, size=(POPULATION_SIZE, NBR_ITEMS) )
    :return: np.array, example:[[ 0 24][25  2] [ 0 22] [ 0 28] [ 0  7]]
    """
    gg = np.array([(np.sum([[s(item)] for item, gene in enumerate(items) if gene == 1]),
                    np.sum([[w(item)] for item, gene in enumerate(items) if gene == 1])) for
                   index, items in enumerate(currentpopulation)])
    # the fix of overhead suppressed to 0
    for i in range(len(gg)):
        if gg[i][1] > MAX_WEIGHT:  # gg[ i ][ 0 ] > MAX_SURVIVAL_POINTS or
            gg[i, 0] = 0
            gg[i, 1] = 0
    return (gg)


def errorVector(ggg):
    """
    :param ggg: np.array
    :return: curErrorVector, example: [[  15.   99.] [  30.   83.] [  30.  100.] [   0.   84.][  30.   88.]]
    """
    curErrorVector = ggg
    for i in range(len(ggg)):
        curErrorVector[i, 0] = MAX_SURVIVAL_POINTS-ggg[i, 0]
        curErrorVector[i, 1] = MAX_WEIGHT-ggg[i, 1]
    return curErrorVector


def crossover(currPope):
    # select two rows 0 and 1 and in the second turn select 2nd and 3th row
    selectRow = lambda x: ifilter(lambda x: x % 2, range(len(currPope)))
    newPopulation = []

    for i in selectRow(currPope):
        temp_m = []
        temp_n = []
        m = currPope[i, :]
        n = currPope[i-1, :]
        temp_n.append(np.asarray(list(it.islice(n, CROSSOVER_RATE, None))))
        temp_m.append(np.asarray(list(it.islice(m, CROSSOVER_RATE))))
        newPopulation.append(np.append(temp_m, temp_n))
        newPopulation.append(np.append(temp_n, temp_m))

    return np.asarray(newPopulation)


def roulette(curPop, fitnesses, elitism=0):
    """ Roulette selection, implemented according to:
        <http://stackoverflow.com/questions/177271/roulette
        -selection-in-genetic-algorithms/177278#177278>
    """
    total_fitness = float(sum(fitnesses))
    rel_fitness = [f / total_fitness for f in fitnesses]
    # Generate probability intervals for each individual
    probs = [sum(rel_fitness[:i+1]) for i in range(len(rel_fitness))]
    newPop = []
    for n in range(len(
            curPop)-MAX_ELITE):  # list ( range ( m ) ):  # python 3: list(range(*args))
        r = random.uniform(0, 1)

        for (i, individual) in enumerate(curPop):
            if r <= probs[i]:
                newPop.append(individual)
                break

    return np.asarray(newPop)

def mutation(currPopa):
    nextPopa = np.multiply(currPopa,
                          np.random.randint(2, size=(
                              POPULATION_SIZE-MAX_ELITE, MAX_ITEM)) if random.random() < MUTATE_RATE else np.ones(
                              (POPULATION_SIZE-MAX_ELITE, MAX_ITEM), dtype=np.int))
    return nextPopa

def elitism(currPopw, fitnesses):
        nP = []
        idx = np.argsort(np.array(fitnesses))
        winners = idx[-MAX_ELITE:]
        fintesses_selected = fitnesses[winners]

        for n, fit in enumerate(fintesses_selected):
            for (i, individual) in enumerate(currPopw):
                if fit == fitnesses[i]:
                    nP.append(individual)
                    break

        return np.asarray(nP)


def bestSolution(currPopr):
    vecFit = fitness(currPopr)
    winner = currPopr[np.argmax(vecFit[:, 1])]
    winner_name = np.array([p(item) for item, index in enumerate(winner) if index == 1])
    summary_weight = np.array([w(item) for item, index in enumerate(winner) if index == 1])
    summary_points = np.array([s(item) for item, index in enumerate(winner) if index == 1])
    mean_points = np.array([np.sum([s(item) for item, gene in enumerate(items) if gene == 1]) for index, items in enumerate(currPopr)])
    return winner, winner_name.tolist(), summary_weight.sum(), summary_points.sum(), mean_points.mean()


if __name__ == '__main__':
    # help variables
    ser = []
    mean_plot = []
    poi = []
    # I. start with an initial time t:= 0;
    print(df_dataset)
    print("MAX_WEIGHT = ", MAX_WEIGHT)
    # II. initialize a usually random population of individuals P (t);
    currentPop = np.random.randint(2, size=(POPULATION_SIZE, NBR_ITEMS))
    # help init nextPop zeroes
    nextPop = np.zeros((currentPop.shape[0], currentPop.shape[1]))
    # III. start evolution while terminate, evolution cycle;
    for i in range(NUMBER_OF_GENERATIONS):
        # evaluate fitness of all initial individuals of population, evaluate P (t); ->
        fitVector = fitness(currentPop)
        elitePop = elitism(currentPop, fitVector[:, 0])
        # roulette ->
        nextPop = roulette(currentPop, fitVector[:, 0])
        # crossover ->
        nextPop = crossover(nextPop)
        # mutation ->
        nextPop = mutation(nextPop)
        # new population ->
        currentPop = np.append(np.array(elitePop), nextPop, axis=0)
        # print the best solution ->
        win, items, weight, points, mean = bestSolution(currentPop)
        ser.append(points)
        poi.append(weight)
        mean_plot.append(mean)
        print("The best solution is:", "|", win, "|", items, "|", weight, "|", points, "|","mean point:", mean, "|", "in generation", "|", i+1)
    gna = subplot(111)
    sers = pd.DataFrame(np.array(ser), columns=["Body přežití"])
    sers.to_csv("knapsack.csv", sep='\t', encoding='utf-8')
    gna_1 = sers.plot(ax=gna)
    ters = pd.DataFrame(np.array(mean_plot), columns=["Mean of survival points for population"])
    gna_2 = ters.plot(ax=gna_1)#print(sers)
    plt.show(gna_2)
    sers["Průměr"] = ters
    sers.to_csv("knapsack.csv", sep=',', encoding='utf-8')
