#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""     Behavioural model fit -- Version 3.1.4
Last edit:  2022/09/24
Author(s):  Geysen, Steven (SG)
Notes:      - Fit models to behavioural data of Marzecova et al. (2019)
            - Release notes:
                * Increased range beta options

To do:      - Fit models
            - Statistics

Questions:

Comments:   AM: The data file was merged in R, from the single files generated
                in Matlab (they are also attached) - they include parameters
                from Yu & Dayan's (2005) computational model.
                The columns contain:
                    * 'id' - participants id
                    * 'block' - block # of the task
                    * 'trial' - trial # of the task
                    * 'relCue' - direction of the relevant cue
                        (1: left / 2: right)
                    * 'irrelCue'- direction of the irrelevant cue
                        (1: left / 2: right)
                    * 'validity' - validity with respect to the relevant cue
                    * 'targetLoc' - location of the target (1: left / 2: right)
                    * 'relCueCol' - color of the relevant cue
                        (1: white / 2: black)
                    * 'gammaBlock' - the validity level within the block
                    * 'RT' - response time in ms
                    * 'correct' - if the response was correct: 1, if missed: 3
                        if incorrect button: 2 (e.g., left button instead of
                        right)
                    * 'I' - parameter I from the Yu & Dayan's (2005)
                        approximate algorithm
                    * 'guessCue' - the cue which is currently assumed to be
                        correct
                    * 'Switch' - count of trials between assumed switches of
                        the cue
                    * 'Lamda' - lamda parameter from Yu & Dayan's (2005)
                        approximate algorithm - unexpected uncertainty
                    * 'Gamma' - gamma parameter from Yu & Dayan's (2005)
                        approximate algorithm - expected uncertainty
                    * 'pMui' - probability that the current context is correct
                    * 'pMuNotI'- probability that the current  context is not
                        correct
                    * 'pe' - prediction error reflecting divergence from the
                        prediction on current trial (combines Lamda and Gamma)
                    * 'logRTmod' - log prediction error
                    * 'logRTexp" - log RT

Sources:    https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fmin.html
            https://docs.scipy.org/doc/scipy/reference/tutorial/optimize.html#nelder-mead-simplex-algorithm-method-nelder-mead
            Remove NaN from array ( https://stackoverflow.com/a/11620982 )
            https://theprogrammingexpert.com/python-remove-nan-from-list/
"""

#%% ~~ Imports and directories ~~ %%#

import time

import numpy as np
import pandas as pd

import fns.assisting_functions as af
import fns.behavioural_functions as bf
import fns.plot_functions as pf
import matplotlib.pyplot as plt

from pathlib import Path
from scipy import optimize, stats

# Directories
SPINE = Path.cwd().parent
DATA_DIR = SPINE / 'data'
OUT_DIR = SPINE / 'results'
if not Path.exists(OUT_DIR):
    Path.mkdir(OUT_DIR)

#%% ~~ Variables ~~ %%#

# Uncertainty data
un_data = pd.read_csv(DATA_DIR / 'UnDataProjectSteven.csv')
## Rescale 1-2 to 0-1
scaList = ['relCue', 'irrelCue', 'validity', 'targetLoc', 'relCueCol']
un_data.loc[:, (scaList)] = abs(un_data.loc[:, (scaList)] - 1)
## Separate data sets for convenience
valid_data = un_data[un_data['validity'] == 0]
invalid_data = un_data[un_data['validity'] == 1]

# Number of iterations
N_ITERS = 10
# Models with optimiseable parameters
# MDLS = ['RW', 'H', 'M']
MDLS = ['RW', 'H']
# Number of participants
npp = un_data['id'].max()
# Number of trials in bin
binsize = 15

# Alpha/eta options
alpha_options = np.linspace(0.01, 1, 20)
# Beta options
##SG: The SoftMax policy needs a high beta value for the model to be accurate
    # (see simulations). Therefore it is not usefull to look at beta values
    # smaller than 10.
beta_options = np.linspace(0.1, 20, 20)

# Plot specs
## Plot number
plotnr = 0
## Plot labels
plabels = ['Valid trials', 'Invalid trials']
## Model labels
models = af.labelDict()
##SG: To have the smallest alpha and beta values in the same corner (left-down)
plotbetas = np.flip(beta_options)

#%% ~~ Exploration ~~ %%#
#########################

#%% ~~ RT split ~~ %%#
#--------------------#
"""
Trying to answer "Can I know the participant's selection?"

The reasoning is that RT under the median are fast RTs, and fast RTs should be
more prevalent on congruent trials. Slow RTs are then a stand in for
incongruent. This plot is to see how well this assumption can be seen in the
behavioural data.

DOES NOT WORK!
"""

# Participant loop
for ppi in range(npp):
    ## Skip pp6 (not in data)
    if ppi + 1 == 6:
        continue
    ## Use only data from pp
    pp_data = un_data[un_data['id'] == ppi + 1]

    # Median RT
    ##SG: Excluded NaN values when computing the result.
    median_rt = pp_data['RT'].median()
    pp_data.loc[:, ('selCue_PP')] = np.where(pp_data['RT'] < median_rt,
                                             pp_data['relCueCol'],
                                             abs(1 - pp_data['relCueCol']))
    pf.selplot(pp_data, 'pp', plotnr, pp=ppi)
    plotnr += 1

#%% ~~ Plots ~~ %%#
#-----------------#

# RT distribution plot
# --------------------
fig, ax = plt.subplots()
ax.hist(valid_data['RT'], bins = 30, alpha = 0.5, label=plabels[0])
ax.hist(invalid_data['RT'], bins = 30, alpha = 0.5, label=plabels[1])

fig.suptitle('RT distribution participants', fontsize=14)
ax.set_xlabel('Response times (s)')
ax.set_ylabel('Count')
ax.legend()

plt.show()
plotnr += 1

# RT validity effect
# ------------------
valrt = np.asarray(valid_data['RT'])
invalrt = np.asarray(invalid_data['RT'])
## Remove NaN values
rt_data = [valrt[~np.isnan(valrt)], invalrt[~np.isnan(invalrt)]]

fig, axs = plt.subplots(nrows=1, ncols=2, figsize=(9, 4))
fig.suptitle('Validity effect')
vplot, bplot = axs[0], axs[1]
## Violin plot
vplot.violinplot(rt_data,
                  showmeans=False,
                  showmedians=True)
vplot.set_title('Violin plot participants')
## Box plot
bplot.boxplot(rt_data)
bplot.set_title('Box plot participants')

for ax in axs:
    ax.yaxis.grid(True)
    ax.set_xticks([y + 1 for y in range(len(rt_data))],
                  labels=plabels)
    ax.set_xlabel('Trial type')
    ax.set_ylabel('Response times')

plt.show()
plotnr += 1

# RTs over time
# -------------
first_bin = []
last_bin = []

for ppi in range(npp):
    ## Skip pp6 (not in data)
    if ppi + 1 == 6:
        continue
    ## Use only data from pp
    pp_data = un_data[un_data['id'] == ppi + 1]
    pp_data.reset_index(drop=True, inplace=True)
    pp_rt = pp_data['RT']

    lag_relCueCol = pp_data.relCueCol.eq(pp_data.relCueCol.shift())
    switches = np.where(lag_relCueCol == False)[0]

    ## Bin RT of first and last b trials
    for starti, endi in af.pairwise(switches):
        first_bin.append(np.nanmean(pp_rt[starti:starti + binsize]))
        last_bin.append(np.nanmean(pp_rt[endi - binsize:endi + 1]))

    ## Length of switch bars depends on values of participant
    barlen = (np.nanmin(pp_rt) + (np.nanmin(pp_rt) * 0.1),
              np.nanmax(pp_rt) + (np.nanmax(pp_rt) * 0.1))

    # RT curve
    plt.figure(plotnr)
    plt.suptitle(f'RT curve participant {ppi}', y=.99)
    plt.plot(pp_rt, label='RT')
    plt.vlines(switches[1:], barlen[0], barlen[1], colors='black')

    plt.xlabel('Trials')
    plt.ylabel('Response time (s)')
    plt.ylim(barlen[0] - 50, barlen[1] + 100)

    plt.legend()
    plt.tight_layout()

    plt.show()
    plotnr += 1

# Binned RT
## Remove NaN values
firstlist = [i for i in first_bin if np.isnan(i) == False]
lastlist = [i for i in last_bin if np.isnan(i) == False]
plotbins = [firstlist, lastlist]

fig, axs = plt.subplots(nrows=1, ncols=2, figsize=(9, 4))
fig.suptitle('Mean binned RT')
vplot, bplot = axs[0], axs[1]
## Violin plot
vplot.violinplot(plotbins,
                  showmeans=False,
                  showmedians=True)
vplot.set_title('Violin plot participants')
## Box plot
bplot.boxplot(plotbins)
bplot.set_title('Box plot participants')

for ax in axs:
    ax.yaxis.grid(True)
    ax.set_xticks([y + 1 for y in range(len(plotbins))],
                  labels=[f'First {binsize} trials',
                          f'Last {binsize} trials'])
    ax.set_xlabel('Trial numbers')
    ax.set_ylabel('Response times')

plt.show()
plotnr += 1

# =============================================================================
# Output numbers
# =============================================================================
print('RT valid trials')
print('mean', np.nanmean(valid_data['RT']), np.nanstd(valid_data['RT']))
print('RT invalid  trials')
print('mean', np.nanmean(invalid_data['RT']), np.nanstd(invalid_data['RT']))

print(f'First {binsize} trials')
print('mean', np.nanmean(first_bin), np.nanstd(first_bin))
print(f'Last {binsize} trials')
print('mean', np.nanmean(last_bin), np.nanstd(last_bin))
## Quick check if different enough
print('Paired t-test')
print(stats.ttest_rel(first_bin, last_bin, nan_policy='omit'))

#%% ~~ Fitting ~~ %%#
#####################

#%% ~~ SoftMax ~~ %%#
#-------------------#

#%% ~~ Grid search ~~ %%#

gridThetas = np.full((npp, len(MDLS), 2), np.nan)

start_total = time.time()
for ppi in range(npp):
    ## Skip pp6 (not in data)
    if ppi + 1 == 6:
        continue
    ## Use only data from pp
    pp_data = un_data[un_data['id'] == ppi + 1]
    pp_data.reset_index(drop=True, inplace=True)
    one_totpp = np.zeros((len(alpha_options), len(beta_options), len(MDLS)))

    start_pp = time.time()
    for locm, modeli in enumerate(MDLS):
        for loca, alphai in enumerate(alpha_options):
            for locb, betai in enumerate(beta_options):
                for iti in range(N_ITERS):
                    one_totpp[loca, locb, locm] += bf.pp_negSpearCor(
                        (alphai, betai), pp_data, model=modeli, asm='soft'
                        )
        modeldata = one_totpp[:, :, locm] / N_ITERS
        # Optimal values
        toploc=[i[0] for i in np.where(one_totpp == np.min(one_totpp))]
        gridThetas[ppi, locm, :] = [alpha_options[toploc[0]], beta_options[toploc[1]]]

        # Intermittent checking
        if ppi % 2 == 0:
            fig, ax = plt.subplots()
            im, _ = pf.heatmap(np.rot90(one_totpp[:, :, locm]),
                               np.round(plotbetas, 3),
                               np.round(alpha_options, 3), ax=ax,
                               row_name='$\u03B2$', col_name='$\u03B1$',
                               cbarlabel='Negative Spearman Correlation')
            plt.suptitle(f'Grid search Negative Spearman Correlation of choice {ppi}')
            plt.show()
            plotnr += 1
    print(f'Duration pp {ppi}: {round((time.time() - start_pp) / 60, 2)} minutes')
print(f'Duration total: {round((time.time() - start_total) / 60, 2)} minutes')

# Save optimal values
##SG: Remove empty row of participant 6
# gridThetas = np.delete(gridThetas, 5, 0)
for locm, modeli in enumerate(MDLS):
    title = f'pp_gridsearch_{N_ITERS}iters_softmax_{modeli}.csv'
    pd.DataFrame(gridThetas[:, locm, :],
                 columns=['alpha', 'beta']).to_csv(OUT_DIR / title)

#%% ~~ Nelder - Mead ~~ %%#

initial_guess = np.full((npp, N_ITERS, 2), np.nan)
nmThetas = np.zeros((npp, len(MDLS), 2))

start_total = time.time()
for ppi in range(npp):
    ## Skip pp6 (not in data)
    if ppi + 1 == 6:
        continue
    ## Use only data from pp
    pp_data = un_data[un_data['id'] == ppi + 1]
    pp_data.reset_index(drop=True, inplace=True)

    start_pp = time.time()
    for iti in range(N_ITERS):
        initial_guess[ppi, iti, :] = (np.random.choice(alpha_options),
                                      np.random.choice(beta_options))
        for locm, modeli in enumerate(MDLS):
            nmThetas[ppi, locm, :] += optimize.fmin(
                bf.pp_negSpearCor, initial_guess[ppi, iti, :],
                args=(pp_data, modeli), ftol=0.001)
    print(f'Duration pp {ppi}: {round((time.time() - start_pp) / 60, 2)} minutes')
print(f'Duration total: {round((time.time() - start_total) / 60, 2)} minutes')

nmThetas /= N_ITERS
# Save optimal values
# nmThetas = np.delete(gridThetas, 5, 0)
for locm, modeli in enumerate(MDLS):
    title = f'pp_NelderMead_{N_ITERS}iters_softmax_{modeli}.csv'
    pd.DataFrame(nmThetas[:, locm, :],
                 columns=['alpha', 'beta']).to_csv(OUT_DIR / title)

# Correlation plot
fig, axs = plt.subplots(nrows=1, ncols=2)
fig.suptitle('Parameter estimation Nelder-Mead')
for pari, ax in enumerate(axs):
    for locm, modeli in enumerate(MDLS):
        ax.plot(nmThetas[:, locm, pari], 'o', label=f'{models[modeli]}')
    ax.set_ylabel('Initial guess')

axs[0].set_xticks(np.round(alpha_options, 3))
axs[0].set_xlabel('Mean estimated alpha/eta values')
axs[0].set_yticks(np.round(alpha_options, 3))
axs[1].set_xlabel('Mean estimated beta values')
axs[1].set_yticks(np.round(beta_options, 3))
plt.legend()

plt.show()
plotnr += 1

#%% ~~ Argmax ~~ %%#
#------------------#

##SG: +1 to store optimal values
gridThetas = np.zeros((npp, len(MDLS), len(alpha_options) + 1))

start_total = time.time()
for ppi in range(npp):
    ## Skip pp6 (not in data)
    if ppi + 1 == 6:
        continue
    ## Use only data from pp
    pp_data = un_data[un_data['id'] == ppi + 1]
    pp_data.reset_index(drop=True, inplace=True)

    start_pp = time.time()
    for locm, modeli in enumerate(MDLS):
        for loca, alphai in enumerate(alpha_options):
            for iti in range(N_ITERS):
                gridThetas[ppi, locm, loca] += bf.pp_negSpearCor((alphai, ),
                                                                 pp_data,
                                                                 model=modeli)
        modeldata = gridThetas[ppi, locm, :-1] / N_ITERS
        # Optimal values
        toploc = [i[0] for i in np.where(modeldata == np.min(modeldata))]
        gridThetas[ppi, locm, -1] = alpha_options[toploc[0]]

        # Intermittent checking
        if ppi % 2 == 0:
            pf.heatmap_1d(modeli, modeldata, toploc, alpha_options,
                          gridThetas[ppi, :, -1])
            plotnr += 1
    print(f'Duration pp {ppi}: {round((time.time() - start_pp) / 60, 2)} minutes')
    print(gridThetas[ppi, :, -1])
print(f'Duration total: {round((time.time() - start_total) / 60, 2)} minutes')
# Save optimal values
title = f'pp_gridsearch_{N_ITERS}iters_argmax.csv'
##SG: Remove empty row of participant 6
# gridThetas = np.delete(gridThetas, 5, 0)
pd.DataFrame(gridThetas[:, :, -1], columns=MDLS).to_csv(OUT_DIR / title)

##SG: Paired t-test to see if the difference between the estimated model
    # parameters is big.
print(stats.ttest_rel(gridThetas[:, 0, -1], gridThetas[:, 1, -1],
                      nan_policy='omit'))

#%% ~~ Nelder - Mead ~~ %%#

initial_guess = np.full((npp, N_ITERS), np.nan)
nmThetas = np.zeros((npp, len(MDLS)))

start_total = time.time()
for ppi in range(npp):
    ## Skip pp6 (not in data)
    if ppi + 1 == 6:
        continue
    ## Use only data from pp
    pp_data = un_data[un_data['id'] == ppi + 1]
    pp_data.reset_index(drop=True, inplace=True)

    start_pp = time.time()
    for iti in range(N_ITERS):
        initial_guess[ppi, iti] = np.random.choice(alpha_options)
        for locm, modeli in enumerate(MDLS):
            nmThetas[ppi, locm] += optimize.fmin(bf.pp_negSpearCor,
                                                  (initial_guess[ppi, iti], ),
                                                  args=(pp_data, modeli, 'arg'),
                                                  ftol=0.001)
    print(f'Duration pp {ppi}: {round((time.time() - start_pp) / 60, 2)} minutes')

    if ppi % 2 == 0:
        print(initial_guess)
        print(nmThetas[ppi])
print(f'Duration total: {round((time.time() - start_total) / 60, 2)} minutes')

nmThetas /= N_ITERS
# Save optimal values
title = f'pp_NelderMead_{N_ITERS}iters_argmax.csv'
# nmThetas = np.delete(gridThetas, 5, 0)
pd.DataFrame(nmThetas, columns=MDLS).to_csv(OUT_DIR / title)

# Correlations
yvals = np.array(list(set(initial_guess)))

fig, ax = plt.subplots()
fig.suptitle('Parameter estimation Nelder-Mead')
for locm, modeli in enumerate(MDLS):
    print(modeli)
    print(stats.ttest_rel(nmThetas[:, locm], initial_guess,
                          nan_policy='omit'))
    # Plot
    ax.plot(nmThetas[:, locm], 'o', label=f'{models[modeli]}')

ax.set_xlabel('Mean estimated values')
ax.set_ylabel('Initial guess')
ax.set_yticks(np.round(yvals, 3))
plt.legend()

plt.show()
plotnr += 1

# ------------------------------------------------------------------------ End


from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import numpy as np
import math
import os
import os.path
import re
import sys
import time
import six
import cython_bleu
import glob
import tqdm

import find_best_pairs

from bleu_hook import _get_ngrams, bleu_tokenize, compute_bleu
from profiling import Timer
import profiling
from datetime import datetime

def read_nonempty(filename):
  with open(filename, 'r') as file:
    return [line.strip() for line in file.readlines()
            if line.strip() not in ['', '.']]

# bleu_score & tokenize # tensor2tensor
def tokenize(string):
  return bleu_tokenize(string)

def get_latest_i(numb_of_book):
  path = 'working_dir/*.book{}'.format(numb_of_book)
  files = glob.glob(path)
  files = [f.split('/')[-1] for f in files if 'nothing' not in f]
  files = [f.split('.')[0] for f in files]
  if not files:
    return 0
  else:
    return max([int(f) for f in files])

def tokenize_then_ngram(list_of_string):
  result = []
  for string in tqdm.tqdm(list_of_string):
    # with Timer('tok_ngram'):
    tokens = tokenize(string)
    ngrams = _get_ngrams(tokens, max_order=4)
    result.append((tokens, ngrams))
  # profiling.print_records()
  return result

def compute_bleu_slow(references,
                      translations,
                      max_order=4,
                      use_bp=True):
  """Computes BLEU score of translated segments against one or more references.

  Args:
    reference_corpus: list of references for each translation. Each
        reference should be tokenized into a list of tokens.
    translation_corpus: list of translations to score. Each translation
        should be tokenized into a list of tokens.
    max_order: Maximum n-gram order to use when computing BLEU score.
    use_bp: boolean, whether to apply brevity penalty.

  Returns:
    BLEU score.
  """
  reference_length = 0
  translation_length = 0
  bp = 1.0
  geo_mean = 0

  matches_by_order = [0] * max_order
  possible_matches_by_order = [0] * max_order
  precisions = []

  references_tokens, ref_ngram_counts = references
  translations_tokens, translation_ngram_counts = translations

  reference_length += len(references_tokens)
  translation_length += len(translations_tokens)

  overlap = dict((ngram,
                  min(count, translation_ngram_counts[ngram]))
                for ngram, count in ref_ngram_counts.items())

  for ngram in overlap:
    matches_by_order[len(ngram) - 1] += overlap[ngram]
  for ngram in translation_ngram_counts:
    possible_matches_by_order[len(ngram)-1] += translation_ngram_counts[ngram]

  # with Timer('the rest of bleu score'):
  precisions = [0] * max_order
  smooth = 1.0
  for i in range(0, max_order):
    if possible_matches_by_order[i] > 0:
      precisions[i] = matches_by_order[i] / possible_matches_by_order[i]
      if matches_by_order[i] > 0:
        precisions[i] = matches_by_order[i] / possible_matches_by_order[i]
      else:
        smooth *= 2
        precisions[i] = 1.0 / (smooth * possible_matches_by_order[i])
    else:
      precisions[i] = 0.0

  if max(precisions) > 0:
    p_log_sum = sum(math.log(p) for p in precisions if p)
    geo_mean = math.exp(p_log_sum/max_order)

  if use_bp:
    if not reference_length:
      bp = 1.0
    else:
      ratio = translation_length / reference_length
      if ratio <= 0.0:
        bp = 0.0
      elif ratio >= 1.0:
        bp = 1.0
      else:
        bp = math.exp(1 - 1. / ratio)
  bleu = geo_mean * bp
  # print_records()
  return np.float32(bleu)

def test_bleu():

  string1 = 'hello world how are you'
  string2 = string1

  bleu = compute_bleu(
    tokenize_then_ngram([string1])[0],
    tokenize_then_ngram([string2])[0]
  )

  bleu = cython_bleu.compute_bleu(
    tokenize_then_ngram([string1])[0],
    tokenize_then_ngram([string2])[0]
  )

  assert bleu == 1.0, bleu
  print('OK')

  string3 = 'this sentence has no overlapping'

  bleu = cython_bleu.compute_bleu(
    tokenize_then_ngram([string1])[0],
    tokenize_then_ngram([string3])[0]
  )

  original_bleu = compute_bleu(
      reference_corpus=[bleu_tokenize(string1)],
      translation_corpus=[bleu_tokenize(string3)],
  )

  assert abs(bleu - original_bleu)<1e-7, (bleu, original_bleu)
  print('OK')

def Bleu_calculate(eng_file, viet_file, en2vi, vi2en, name_to_save):
  if not os.path.exists('working_dir/ccalign{}_bleu.nparray'.format(name_to_save)):

    print('Tokenizing & ngramming ...')
    print('eng file')
    with open(eng_file,'r') as f:
      eng_lines = f.readlines()
    ef_ngrams = tokenize_then_ngram(eng_lines)

    print('en2vi')
    with open(en2vi,'r') as f:
      en2vi_lines = f.readlines()
    etf_ngrams = tokenize_then_ngram(en2vi_lines)

    print('vi file')
    with open(viet_file,'r') as f:
      viet_lines = f.readlines()
    vf_ngrams = tokenize_then_ngram(viet_lines)

    print('vi2en file')
    with open(vi2en,'r') as f:
      vi2en_lines = f.readlines()
    vtf_ngrams = tokenize_then_ngram(vi2en_lines)

    assert len(ef_ngrams)==len(vf_ngrams)
    assert len(etf_ngrams)==len(ef_ngrams)
    assert len(vtf_ngrams)==len(vf_ngrams)

    print('LENGTHs:', len(ef_ngrams), len(vf_ngrams))
    print('Finish tokenize & ngram time: ', datetime.now().time())

    # ENG bleu:

    f = open('working_dir/ccalign{}_bleu.nparray'.format(name_to_save), 'wb')
    # bleu_fn = compute_bleu
    bleu_fn = cython_bleu.compute_bleu
    bleu_list = []

    for i in tqdm.tqdm(range(len(ef_ngrams))):
      bleu = bleu_fn(ef_ngrams[i], vtf_ngrams[i])
      bleu += bleu_fn(vtf_ngrams[i], ef_ngrams[i])
      bleu += bleu_fn(vf_ngrams[i], etf_ngrams[i])
      bleu += bleu_fn(etf_ngrams[i], vf_ngrams[i])
      bleu_list += [bleu]
    # assert len(bleu_list) in [4643, 4642]
    np.save(f, bleu_list)

    f.close()

  print('Done working on ccalign_{}'.format(name_to_save))

# ef = '8_ccalign.en'
# vf = '8_ccalign.vi'
# en2vi = '8_ccalign.en2vi'
# vi2en = '8_ccalign.vi2en'
# Bleu_calculate(ef, vf, en2vi, vi2en, 'try8')
# print('done')


resources = {
    "water": 300,
    "milk": 200,
    "coffee": 100,
}

machine_profit = 0

def PrintReport() -> None:
    print(  f"Water: {resources['water']}ml\n" +
            f"Milk: {resources['milk']}ml\n" +
            f"Coffee: {resources['coffee']}g\n" +
            f"Money: ${machine_profit}"

    )

def CheckResources(drink) -> bool:

    if drink == "espresso":
        water = 50
        coffee = 18
        milk = 0
    elif drink == "latte":
        water = 200
        coffee = 24
        milk = 150
    else:
        #drink is cappuccino
        water = 250
        coffee = 24
        milk = 100

    if resources["water"] - water < 0:
        print("Sorry there is not enough water.")
        return False
    if resources["coffee"] - coffee < 0:
        print("Sorry there is not enough coffee.")
        return False
    if resources["milk"] - milk < 0:
        print("Sorry there is not enough milk")
        return False
    return True

def GetDrinkPrice(drink_type) -> float:

    if drink_type == "espresso":
        return 1.5
    elif drink_type == "latte":
        return 2.5
    else:
        #drink is cappuccino
        return 3

def ProcessCoins(drink_type) -> float:

    """recives user money and returns the remainder of the money"""

    drink_cost = GetDrinkPrice(drink_type)

    print("Please insert coins.")
    quarters = int(input("How many quarters?: "))
    dimes = int(input("How many dimes?: "))
    nickles = int(input("How many nickles?: "))
    pennies = int(input("How many pennies?: "))

    quarters = 0.25 * quarters
    dimes = 0.1 * dimes
    nickles = 0.05 * nickles
    pennies = 0.01 * pennies

    sum = quarters + dimes + nickles + pennies

    return sum - drink_cost

def MakeCoffee(drink_type):
    if drink_type == "espresso":
        water = 50
        coffee = 18
        milk = 0
    elif drink_type == "latte":
        water = 200
        coffee = 24
        milk = 150
    else:
        #drink is cappuccino
        water = 250
        coffee = 24
        milk = 100

    resources["water"] -= water
    resources["coffee"] -= coffee
    resources["milk"] -= milk

#add loop to service
drink_type = input("What would you like? (espresso/latte/cappuccino): ")

while drink_type != "off":

    if drink_type == "report":
        PrintReport()

    elif drink_type != "off":

        #There are enough resources
        if CheckResources(drink_type):
            change = ProcessCoins(drink_type)
            if change >= 0:
                MakeCoffee(drink_type)
                drink_cost = GetDrinkPrice(drink_type)
                machine_profit += drink_cost

                if change > 0:
                    print(f"Here is ${'{:.2f}'.format(change)} in change.")
                else:
                    print("No change.")
                print(f"Here is your {drink_type} ☕. Enjoy!")
            else:
                print("Sorry, that's not enough money. Money refunded")

    drink_type = input("What would you like? (espresso/latte/cappuccino): ")


import sys

from unittest.mock import patch
from unittest import TestCase

from hypothesis.strategies import fixed_dictionaries, sampled_from, text
from hypothesis import given

from ipalint.cli import Cli
from ipalint.core import Core

class CliTestCase(TestCase):

	def setUp(self):
		self.cli = Cli()

	@given(text().filter(lambda t: not t.startswith('-')),
			text().filter(lambda t: not t.startswith('-')),
			fixed_dictionaries({
				'no_header': sampled_from(['--no-header', '']),
				'ignore_nfd': sampled_from(['--ignore-nfd', '']),
				'ignore_ws': sampled_from(['--ignore-ws', '']),
				'linewise': sampled_from(['--linewise', '']),
				'no_lines': sampled_from(['--no-lines', ''])}))
	def test_run(self, dataset, col, flags):
		args = [dataset]
		if col: args.extend(['--col', col])
		args.extend([flag for flag in flags.values() if flag])

		with patch.object(Core, 'lint', return_value='42') as mock_lint:
			with patch.object(sys.stdout, 'write'):
				try:
					self.cli.run(args)
				except SystemExit:
					pass

				mock_lint.assert_called_once_with(
					dataset = dataset,
					col = col if col else None,
					no_header = True if flags['no_header'] else False,
					ignore_nfd = True if flags['ignore_nfd'] else False,
					ignore_ws = True if flags['ignore_ws'] else False,
					linewise = True if flags['linewise'] else False,
					no_lines = True if flags['no_lines'] else False)


#!/usr/bin/env python3

# ===============================================================================
# A googol (10**100) is a massive number: one followed by one-hundred zeros;
# 100**100 is almost unimaginably large: one followed by two-hundred zeros.
# Despite their size, the sum of the digits in each number is only 1.
#
# Considering natural numbers of the form, a**b, where a, b < 100,
# what is the maximum digital sum?
# ===============================================================================

def DigiSum(n):
    return sum(int(i) for i in str(n))

def solution():
    s = 0
    for a in range(100):
        for b in range(100):
            s = max(s, DigiSum(a**b))
    return s

if __name__ == '__main__':
    print(solution())


#Zad2
#Przygotować funkcję, która przyjmie w parametrach imię i nazwisko, oraz zwróci pierwszą literę imienia i nazwisko połączone kropką. Funkcja powinna również dbać o poprawność wielkich liter. Przykładowo, wejście: (jan, kowalski), wyjście: J. Kowalski.

def zad2(str1, str2):
    str1 = str1.title()
    str2 = str2.title()
    return str1[0] + ". " + str2

print("Podaj imie:")
imie = input()
print("Podaj nazwisko:")
nazw = input()
print(zad2(imie, nazw))


import re
import sys
from sys import argv
import string
import operator
import numpy as np

d_init = dict()
d_trans = dict()
d_emiss = dict()
l_symbol = []
l_state = []
l_word = []
input_hmm = sys.argv[1]
test_file = sys.argv[2]
output_file = sys.argv[3]
output = open(output_file, 'w')
stderr = open('stderr', 'w')

with open(input_hmm, 'r') as f:
    initial_checker = False
    transition_checker = False
    emission_checker = False
    init_line_num = 0
    trans_line_num = 0
    emiss_line_num = 0
    prev_state_trans = ''
    prev_state_emiss = ''
    for line in f:
        line = line.strip('\n')
        line = line.strip('')
        if len(line) == 0:
            continue
        if transition_checker and 'emission' not in line:
            words = line.split()
            words = [x for x in words if len(x) != 0]
            trans = (words[0], words[1])
            prob = float(words[2])
            if prob < 0 or prob > 1:
                stderr.write(
                    "warning: the prob is not in [0,1] range:  " + line)
            else:
                if trans not in d_trans:
                    d_trans[trans] = prob

        if emission_checker:
            words = line.split()
            words = line.split()
            words = [x for x in words if len(x) != 0]
            emiss = (words[0], words[1])
            if words[1] not in l_word:
                l_word.append(words[1])
            prob = float(words[2])
            if prob < 0 or prob > 1:
                stderr.write("warning: the prob is not in [0,1] range:  " + line)
            else:
                if emiss not in d_emiss:
                    d_emiss[emiss] = prob

        if '\\transition' in line:
            initial_checker = False
            transition_checker = True
        if '\\emission' in line:
            transition_checker = False
            emission_checker = True
'''information from hmm loaded'''

with open(test_file, 'r') as f:
    for line in f:
        line = line.strip('\n')
        line = line.strip('')
        words = line.split()
        line_tag = ''
        count = 1
        l_from_state = []
        l_from_state_prob = []
        path = []
        max_path = ''
        max_prob = 0
        for word in words:
            if word not in l_word:
                word = '<unk>'
            l_to_state = []
            l_to_state_prob = []
            l_v_prob = []
            '''for the first word'''
            tag = ''
            if count == 1:
                d_word = dict()
                d_word = {k: v for k, v in d_emiss.items() if k[1] == word}
                for item in d_word:
                    l_to_state.append(item[0])
                    l_to_state_prob.append(d_word[item])
                    trans = ('BOS_BOS', item[0])
                    if trans in d_trans:
                        trans_prob = d_trans[trans]
                    else:
                        trans_prob = 0
                    v_prob = trans_prob * d_emiss[item]
                    l_v_prob.append(v_prob)
                max_index = l_v_prob.index(max(l_v_prob))
                tag = l_to_state[max_index]
                non_zero_index = [i for i, e in enumerate(l_v_prob) if e != 0]
                l_to_state = list(l_to_state[i] for i in non_zero_index)
                l_v_prob = list(l_v_prob[i] for i in non_zero_index)
                for state in l_to_state:
                    path.append('BOS_BOS' + ' ' + state)
                l_from_state = l_to_state
                l_from_state_prob = l_v_prob
            else:
                '''maximum value of pre viterbi probability and transition probability'''
                # print(word)
                d_word = dict()
                d_word = {k: v for k, v in d_emiss.items() if k[1] == word}
                for item in d_word:
                    max_va = 0
                    pre_state = ''
                    l_to_state.append(item[0])
                    to_state = item[0]
                    l_to_state_prob.append(d_word[item])
                    for c, i in enumerate(l_from_state):
                        trans = (i, to_state)
                        if trans in d_trans:
                            trans_prob = d_trans[trans]
                        else:
                            trans_prob = 0
                        va = l_from_state_prob[c] * trans_prob
                        if va > max_va:
                            max_va = va
                            pre_state = i
                    for n, i in enumerate(path):
                        if len(pre_state) > 0 and i.endswith(pre_state) and len(i.split()) == count:
                            path.append(path[n] + ' ' + to_state)
                            break
                    l_v_prob.append(max_va * d_word[item])
                non_zero_index = [i for i, e in enumerate(l_v_prob) if e != 0]
                l_to_state = list(l_to_state[i] for i in non_zero_index)
                l_v_prob = list(l_v_prob[i] for i in non_zero_index)
                l_from_state = l_to_state
                l_from_state_prob = l_v_prob
            l_rm = []
            for p in path:
                s = p.split()
                if len(s) != count + 1:
                    l_rm.append(p)
            for item in l_rm:
                path.remove(item)
            count = count + 1
            if count == len(words) + 1:
                max_index = l_from_state_prob.index(max(l_from_state_prob))
                max_prob = max(l_from_state_prob)
                max_prob = np.log10(max_prob)
                tag = l_from_state[max_index]
                for p in path:
                    if p.endswith(tag):
                        max_path = p
        output.write(line)
        output.write(' => ')
        output.write(max_path + ' ')
        output.write(str(max_prob))
        output.write('\n')


import argparse

import numpy as onp

from jax.config import config as jax_config
import jax.numpy as np
import jax.random as random

import numpyro.distributions as dist
from numpyro.examples.datasets import SP500, load_dataset
from numpyro.handlers import sample
from numpyro.hmc_util import initialize_model
from numpyro.mcmc import hmc
from numpyro.util import fori_collect

"""
Generative model:

sigma ~ Exponential(50)
nu ~ Exponential(.1)
s_i ~ Normal(s_{i-1}, sigma - 2)
r_i ~ StudentT(nu, 0, exp(-2 s_i))

This example is from PyMC3 [1], which itself is adapted from the original experiment
from [2]. A discussion about translating this in Pyro appears in [3].

For more details, refer to:
 1. *Stochastic Volatility Model*, https://docs.pymc.io/notebooks/stochastic_volatility.html
 2. *The No-U-Turn Sampler: Adaptively Setting Path Lengths in Hamiltonian Monte Carlo*,
    https://arxiv.org/pdf/1111.4246.pdf
 3. Forum discussion, https://forum.pyro.ai/t/problems-transforming-a-pymc3-model-to-pyro-mcmc/208/14

"""

def model(returns):
    step_size = sample('sigma', dist.Exponential(50.))
    s = sample('s', dist.GaussianRandomWalk(scale=step_size, num_steps=np.shape(returns)[0]))
    nu = sample('nu', dist.Exponential(.1))
    return sample('r', dist.StudentT(df=nu, loc=0., scale=np.exp(-2*s)),
                  obs=returns)

def print_results(posterior, dates):
    def _print_row(values, row_name=''):
        quantiles = [0.2, 0.4, 0.5, 0.6, 0.8]
        row_name_fmt = '{:>' + str(len(row_name)) + '}'
        header_format = row_name_fmt + '{:>12}' * 5
        row_format = row_name_fmt + '{:>12.3f}' * 5
        columns = ['(p{})'.format(q * 100) for q in quantiles]
        q_values = onp.quantile(values, quantiles, axis=0)
        print(header_format.format('', *columns))
        print(row_format.format(row_name, *q_values))
        print('\n')

    print('=' * 5, 'sigma', '=' * 5)
    _print_row(posterior['sigma'])
    print('=' * 5, 'nu', '=' * 5)
    _print_row(posterior['nu'])
    print('=' * 5, 'volatility', '=' * 5)
    for i in range(0, len(dates), 180):
        _print_row(np.exp(-2 * posterior['s'][:, i]), dates[i])

def main(args):
    jax_config.update('jax_platform_name', args.device)
    _, fetch = load_dataset(SP500, shuffle=False)
    dates, returns = fetch()
    init_rng, sample_rng = random.split(random.PRNGKey(args.rng))
    init_params, potential_fn, constrain_fn = initialize_model(init_rng, model, returns)
    init_kernel, sample_kernel = hmc(potential_fn, algo='NUTS')
    hmc_state = init_kernel(init_params, args.num_warmup, rng=sample_rng)
    hmc_states = fori_collect(0, args.num_samples, sample_kernel, hmc_state,
                              transform=lambda hmc_state: constrain_fn(hmc_state.z))
    print_results(hmc_states, dates)

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Stochastic Volatility Model")
    parser.add_argument('-n', '--num-samples', nargs='?', default=3000, type=int)
    parser.add_argument('--num-warmup', nargs='?', default=1500, type=int)
    parser.add_argument('--device', default='cpu', type=str, help='use "cpu" or "gpu".')
    parser.add_argument('--rng', default=21, type=int, help='random number generator seed')
    args = parser.parse_args()
    main(args)


import collections
import os
import random

import torch
import torch.nn as nn
import tqdm

from datamgr import SetDataManager
from backbone import wrn28_10
from io_utils import parse_args, resume_training, enable_gpu_usage
from top_losses import LossesBag
from losses import get_rotations, get_bag

use_gpu = torch.cuda.is_available()

def evaluate(val_loader, model, losses_bag, params, save_latent=False):
    losses_bag.clear_epoch()
    if not params.local_batch:
        model.eval()
        losses_bag.eval()
    else:
        model.train()
        losses_bag.train()
    if save_latent:
        penultimate_dict = collections.defaultdict(list)
        features_dict = collections.defaultdict(list)
    with torch.no_grad():
        progress = tqdm.tqdm(total=len(val_loader), leave=True, ascii=True)
        for _, (inputs, targets) in enumerate(val_loader):
            if use_gpu:
                inputs, targets = inputs.cuda(), targets.cuda()
            inputs = torch.flatten(inputs, 0, 1)
            targets = torch.flatten(targets, 0, 1)

            progress_desc = []
            out_latent = model.forward(inputs)
            if params.unit_sphere:
                out_latent = normalize(out_latent)

            if params.triplet:
                _ = losses_bag['triplet'](out_latent, targets)
                progress_desc.append(losses_bag['triplet'].get_desc())

            if params.rotation:
                angles = torch.zeros([int(inputs.shape[0])], dtype=torch.int64).cuda()
                _ = losses_bag['rotation'](out_latent, angles)
                progress_desc.append(losses_bag['rotation'].get_desc())

            if params.mixup:
                _ = losses_bag['mixup'](out_latent)  # disentanglement
                progress_desc.append(losses_bag['mixup'].get_desc())

            if save_latent:
                features_latent = losses_bag.agregate_features()
                penultimate_latent = out_latent.detach().cpu().numpy()
                for penultimate, features, target in zip(penultimate_latent, features_latent, targets):
                    penultimate_dict[int(target.item())].append(penultimate)
                    features_dict[int(target.item())].append(features)

            progress_desc = ' '.join(progress_desc)
            progress.set_description(desc=progress_desc)
            progress.update()
        progress.close()
    # torch.cuda.empty_cache()  # ?
    if save_latent:
        return penultimate_dict, features_dict
    return None

def normalize(out):
    norms = torch.clamp(torch.sum(out**2, dim=1), min=1e-5)  # positive sum
    out = out / norms  # unit sphere
    return out

def train_epoch(model, losses_bag, base_loader, optimizer, params):
    model.train()
    losses_bag.train()
    losses_bag.clear_epoch()
    if use_gpu:
        torch.cuda.empty_cache()

    progress = tqdm.tqdm(total=len(base_loader), leave=True, ascii=True)
    for _, (inputs, targets) in enumerate(base_loader):
        progress_desc = []
        optimizer.zero_grad()

        if use_gpu:
            inputs, targets = inputs.cuda(), targets.cuda()
        inputs = torch.flatten(inputs, 0, 1)
        targets = torch.flatten(targets, 0, 1)

        if params.rotation:
            inputs, angles = get_rotations(inputs)

        latent_space = model(inputs)
        if params.unit_sphere:
            latent_space = normalize(latent_space)

        if params.triplet:
            loss, _ = losses_bag['triplet'](latent_space, targets)
            loss.backward(retain_graph=True)
            progress_desc.append(losses_bag['triplet'].get_desc())

        if params.rotation:
            loss, _ = losses_bag['rotation'](latent_space, angles)
            loss.backward(retain_graph=True)
            progress_desc.append(losses_bag['rotation'].get_desc())

        if params.mixup:
            loss, _ = losses_bag['mixup'](latent_space)
            loss.backward(retain_graph=False)  # clear yo mama
            progress_desc.append(losses_bag['mixup'].get_desc())

        optimizer.step()

        progress_desc = ' '.join(progress_desc)
        progress.set_description(desc=progress_desc)
        progress.update()
    progress.close()

def full_training(base_loader, val_loader, model, start_epoch, stop_epoch, params):
    optimizer = torch.optim.Adam([
                {'params': model.parameters()},
                ] + losses_bag.optimizer_dict())

    print("stop_epoch", start_epoch, stop_epoch)

    for epoch in range(start_epoch, stop_epoch):
        print('\nEpoch: %d' % epoch)

        train_epoch(model, losses_bag, base_loader, optimizer, params)

        if not os.path.isdir(params.checkpoint_dir):
            os.makedirs(params.checkpoint_dir)

        if (epoch % params.save_freq==0) or (epoch==stop_epoch-1):
            outfile = os.path.join(params.checkpoint_dir, '{:d}.tar'.format(epoch))
            model_dict = {'epoch':epoch, 'state':model.state_dict(), **losses_bag.states_dict()}
            torch.save(model_dict, outfile)

        evaluate(val_loader, model, losses_bag, params)

    return model

if __name__ == '__main__':
    params = parse_args('graph')
    random.seed(914637)

    image_size = 84  # small
    # the weights are stored into ./weights folder
    save_dir = './weights'
    # the location of the json files, themselves containing the location of the images
    data_dir = {}
    data_dir['cifar']           = './filelists/cifar/'
    data_dir['CUB']             = './filelists/CUB/'
    data_dir['miniImagenet']    = './filelists/miniImagenet/'

    base_file = data_dir[params.dataset] + 'base.json'
    val_file = data_dir[params.dataset] + 'val.json'
    params.checkpoint_dir = '%s/checkpoints/%s/%s/%s' %(save_dir, params.dataset, params.model, params.run_name)
    start_epoch = params.start_epoch
    stop_epoch = params.stop_epoch

    n_way, n_shot, n_val = params.n_way, params.n_shot, params.n_val
    base_datamgr = SetDataManager(base_file, image_size, n_way, n_shot, n_val)
    val_datamgr = SetDataManager(val_file, image_size, n_way, n_shot, n_val)
    base_loader = base_datamgr.get_data_loader(aug=params.train_aug)
    val_loader = val_datamgr.get_data_loader(aug=False)

    if params.model == 'WideResNet28_10':
        model = wrn28_10(num_classes=params.num_classes)
        torch.backends.cudnn.benchmark = True
    else:
        raise ValueError

    bag = get_bag(params)
    losses_bag = LossesBag(bag)

    if use_gpu:
        model = enable_gpu_usage(model)
        losses_bag.use_gpu()

    if params.resume:
        start_epoch = resume_training(params.checkpoint_dir, model)
        losses_bag.load_states(params.checkpoint_dir)

    model = full_training(base_loader, val_loader, model, start_epoch, start_epoch+stop_epoch, params)


kg = int(input())

restKg = kg % 5
restThree = kg % 318
fiveKg = 0
threeKg = 0
a = 1
b = 1
c = kg
d = kg
e = kg
f = kg

if restKg % 3 == 0:
   fiveKg = kg // 5
   threeKg = restKg // 3
   e = fiveKg + threeKg

if kg % 3 == 0:

    d = kg // 3

while(5*a < kg):
    if (kg - 5 * a) % 3 == 0:
        if c > a + ((kg - 5 * a) // 3):
            c = a + ((kg - 5 * a) // 3)
    a = a + 1
f = min(e,d,c)

if f == kg: print(-1)
else: print(f)


import numpy as np
import inspect
import functools
from maddpg.agents import dodgeball_agents
import torch
from enum import Enum
import random
import copy
def store_args(method):
    """Stores provided method args as instance attributes.
    """
    argspec = inspect.getfullargspec(method)
    defaults = {}
    if argspec.defaults is not None:
        defaults = dict(
            zip(argspec.args[-len(argspec.defaults):], argspec.defaults))
    if argspec.kwonlydefaults is not None:
        defaults.update(argspec.kwonlydefaults)
    arg_names = argspec.args[1:]

    @functools.wraps(method)
    def wrapper(*positional_args, **keyword_args):
        self = positional_args[0]
        # Get default arg values
        args = defaults.copy()
        # Add provided arg values
        for name, value in zip(arg_names, positional_args[1:]):
            args[name] = value
        args.update(keyword_args)
        self.__dict__.update(args)
        return method(*positional_args, **keyword_args)

    return wrapper

class Initialization(Enum):
    Zero = 0
    XavierGlorotNormal = 1
    XavierGlorotUniform = 2
    KaimingHeNormal = 3  # also known as Variance scaling
    KaimingHeUniform = 4
    Normal = 5

_init_methods = {
    Initialization.Zero: torch.zero_,
    Initialization.XavierGlorotNormal: torch.nn.init.xavier_normal_,
    Initialization.XavierGlorotUniform: torch.nn.init.xavier_uniform_,
    Initialization.KaimingHeNormal: torch.nn.init.kaiming_normal_,
    Initialization.KaimingHeUniform: torch.nn.init.kaiming_uniform_,
    Initialization.Normal: torch.nn.init.normal_,
}

def linear_layer(
    input_size: int,
    output_size: int,
    kernel_init: Initialization = Initialization.XavierGlorotUniform,
    kernel_gain: float = 1.0,
    bias_init: Initialization = Initialization.Zero,
) -> torch.nn.Module:
    """
    Creates a torch.nn.Linear module and initializes its weights.
    :param input_size: The size of the input tensor
    :param output_size: The size of the output tensor
    :param kernel_init: The Initialization to use for the weights of the layer
    :param kernel_gain: The multiplier for the weights of the kernel. Note that in
    TensorFlow, the gain is square-rooted. Therefore calling  with scale 0.01 is equivalent to calling
        KaimingHeNormal with kernel_gain of 0.1
    :param bias_init: The Initialization to use for the weights of the bias layer
    """
    layer = torch.nn.Linear(input_size, output_size)
    if (
        kernel_init == Initialization.KaimingHeNormal
        or kernel_init == Initialization.KaimingHeUniform
    ):
        _init_methods[kernel_init](layer.weight.data, nonlinearity="linear")
    else:
        _init_methods[kernel_init](layer.weight.data)
    layer.weight.data *= kernel_gain
    _init_methods[bias_init](layer.bias.data)
    return layer

class GaussianNoise:
    """Ornstein-Uhlenbeck process."""

    def __init__(self, size, seed, sigma=0.50, decay=0.9982):
        """Initialize parameters and noise process."""
        self.size = size
        self.sigma = sigma
        self.decay = decay
        self.seed = torch.manual_seed(seed)
        # self.seed = np.random.seed(seed)
        self.reset()

    def reset(self):
        """Reset the internal state (= noise) to mean (mu)."""
        self.sigma = max(self.decay*self.sigma,0.02)

    def sample(self):
        """Update internal state and return it as a noise sample."""
        return self.sigma * torch.randn(self.size)

class OUNoise:
    """Ornstein-Uhlenbeck process."""

    def __init__(self, size, seed, mu=0.0, theta=0.01, sigma=0.2356, sigma_min = 0.05, sigma_decay=0.9965):
        """Initialize parameters and noise process."""
        self.mu = mu * np.ones(size)
        self.theta = theta
        self.sigma = sigma
        self.sigma_min = sigma_min
        self.sigma_decay = sigma_decay
        self.seed = random.seed(seed)
        self.size = size
        self.reset()

    def reset(self):
        """Reset the internal state (= noise) to mean (mu)."""
        self.state = copy.copy(self.mu)
        """Resduce  sigma from initial value to min"""
        self.sigma = max(self.sigma_min, self.sigma*self.sigma_decay)

    def sample(self):
        """Update internal state and return it as a noise sample."""
        x = self.state
        dx = self.theta * (self.mu - x) + self.sigma * np.random.standard_normal(self.size)
        self.state = x + dx
        return self.state

def make_env(args,name,time_scale,no_graphics):
    env = dodgeball_agents(name, time_scale, no_graphics)
    env.set_env()
    args.n_learning_agents = env.nbr_agent
    args.n_agents = env.nbr_agent
    args.obs_shape = [env.agent_obs_size for i in range( args.n_agents)]
    args.action_shape =[env.spec.action_spec.discrete_size + env.spec.action_spec.continuous_size for i in range( args.n_agents)]
    args.high_action = 1
    args.low_action = -1
    args.continuous_action_space = env.spec.action_spec.continuous_size
    args.discrete_action_space = env.spec.action_spec.discrete_size
    args.device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    args.seed=45
    return env, args


"""CPU functionality."""
import sys

PRN = 0b01000111
LDI = 0b10000010
HLT = 0b00000001
MUL = 0b10100010
PUSH = 0b01000101
POP = 0b01000110
CALL = 0b01010000
RET = 0b00010001
ADD = 0b10100000

class CPU:
    """Main CPU class."""

    def __init__(self):
        """Construct a new CPU."""
        self.ram = [0] * 256
        self.reg = [0] * 8
        self.pc = 0
        self.branchtable = {}
        self.branchtable[PRN] = self.handle_PRN
        self.branchtable[LDI] = self.handle_LDI
        self.branchtable[HLT] = self.handle_HLT
        self.branchtable[MUL] = self.handle_MUL
        self.branchtable[PUSH] = self.handle_PUSH
        self.branchtable[POP] = self.handle_POP
        self.branchtable[CALL] = self.handle_CALL
        self.branchtable[RET] = self.handle_RET
        self.branchtable[ADD] = self.handle_ADD
        self.SP = 7
        # sets the stack pointer register's value to be 244 AKA 0xF4
        self.stack_pointer = self.reg[self.SP] = 244

# A function that takes in the current instruction and runs them in O(1) against our branchtable,
# based on what the current instruction is.
    def handle_operations(self, IR, operand_a, operand_b, distance):
        if IR == LDI:
            self.branchtable[IR](operand_a, operand_b, distance)
        elif IR == PRN:
            self.branchtable[IR](operand_a, distance)
        elif IR == MUL:
            self.branchtable[IR](operand_a, operand_b, distance)
        elif IR == PUSH:
            self.branchtable[IR](operand_a, distance)
        elif IR == POP:
            self.branchtable[IR](operand_a, distance)
        elif IR == CALL:
            self.branchtable[IR](operand_a)
        elif IR == RET:
            self.branchtable[IR]()
        elif IR == ADD:
            self.branchtable[IR](operand_a, operand_b, distance)
        elif IR == HLT:
            self.branchtable[IR]()

    # A helper function that performs ADD per the ls8 spec.
    def handle_ADD(self, operand_a, operand_b, distance):
        self.alu('ADD', operand_a, operand_b)
        self.pc += distance

    # A helper function that performs RET per the ls8 spec.
    def handle_RET(self):
        self.pc = self.ram[self.stack_pointer]
        self.stack_pointer -= 1

    # A helper function that performs CALL per the ls8 spec.
    def handle_CALL(self, operand_a):
        return_address = self.pc + 2
        self.stack_pointer -= 1
        self.ram[self.stack_pointer] = return_address
        self.pc = self.reg[operand_a]

    # A helper function that performs POP per the ls8 spec.
    def handle_POP(self, operand_a, distance):
        self.reg[operand_a] = self.ram[self.stack_pointer]
        self.stack_pointer += 1
        self.pc += distance

    # A helper function that performs PUSH per the ls8 spec.
    def handle_PUSH(self, operand_a, distance):
        self.stack_pointer -= 1
        self.ram[self.stack_pointer] = self.reg[operand_a]
        self.pc += distance

    # A helper function that performs MUL per the ls8 spec.
    def handle_MUL(self, operand_a, operand_b, distance):
        self.alu('MUL', operand_a, operand_b)
        self.pc += distance

    # A helper function that performs HLT per the ls8 spec.
    def handle_HLT(self):
        running = False
        return sys.exit(1)

    # A helper function that performs PRN per the ls8 spec.
    def handle_PRN(self, operand_a, distance):
        print(self.reg[operand_a])
        self.pc += distance

    # A helper function that performs LDI per the ls8 spec.
    def handle_LDI(self, operand_a, operand_b, distance):
        self.reg[operand_a] = operand_b
        self.pc += distance

    # Gets the current ram value of the current MAR.
    def ram_read(self, MAR):
        return self.ram[MAR]

    def ram_write(self, MDR):
        pass

    def load(self):
        """Load a program into memory."""

        if len(sys.argv) != 2:
            print(f'usage: {sys.argv[0]} <filename>')
            sys.exit(1)

        try:
            with open(sys.argv[1]) as f:
                address = 0
                for line in f:
                    num = line.split('#', 1)[0]
                    if num.strip() == '':
                        continue

                    self.ram[address] = int(num, 2)
                    address += 1

        except FileNotFoundError:
            print(f'{sys.argv[0]}: {sys.argv[1]} not found.')
            sys.exit(2)

    def alu(self, op, reg_a, reg_b):
        """ALU operations."""

        if op == "ADD":
            self.reg[reg_a] += self.reg[reg_b]
        elif op == "MUL":
            self.reg[reg_a] = self.reg[reg_a] * self.reg[reg_b]
        # elif op == "SUB": etc
        else:
            raise Exception("Unsupported ALU operation")

    def trace(self):
        """
        Handy function to print out the CPU state. You might want to call this
        from run() if you need help debugging.
        """

        print(f"TRACE: %02X | %02X %02X %02X |" % (
            self.pc,
            # self.fl,
            # self.ie,
            self.ram_read(self.pc),
            self.ram_read(self.pc + 1),
            self.ram_read(self.pc + 2)
        ), end='')

        for i in range(8):
            print(" %02X" % self.reg[i], end='')

        print()

    def run(self):
        """Run the CPU."""
        running = True
        # print(0b00011000)
        while running:
            # Gets the current instruction from RAM
            IR = self.ram[self.pc]
            # Sets the first operand from ram (operand is a like a variable)
            operand_a = self.ram_read(self.pc + 1)
            # sets the second operand from ram (operand is like a variable)
            operand_b = self.ram_read(self.pc + 2)
            # gets the number of operands by using bitwise-AND to parse our instruction.
            num_operands = (IR & 0b11000000) >> 6
            # gets the number of operations that the pc will need to be incremented.
            dist_to_move_pc = num_operands + 1
            # A function that takes in the current instruction and runs them in O(1) against our branchtable,
            # based on what the current instruction is.
            self.handle_operations(IR, operand_a, operand_b, dist_to_move_pc)


from jinja2 import FileSystemLoader, Environment
import logging
import os
from pymongo import MongoClient
from bs4 import BeautifulSoup

from bromonitorgenerator.templates.figure_mapping import figure_mapping, data
from bromonitorgenerator.utils.start_date import set_start_date
from bromonitorgenerator.utils.utils import today
from common.config import MONGODB_URL

# Load the bromonitor_template html from filesystem
file_loader = FileSystemLoader("templates")
env = Environment(loader=file_loader)
template = env.get_template("bromonitor_template.html")

# Render the template with figures and links inserted at the mapped keys
html = template.render(figure_mapping)
output_recent = {"date": today(), "html": html, "static_data": data, "most_recent": True}

# Remove hrefs for the archived version
soup = BeautifulSoup(html, "lxml")
for a in soup.findAll("a"):
    del a["href"]

# Remove divs containing explanation on how the links work for archived version
for div in soup.find_all("div", {"class": "bromonitor-link-toelichting"}):
    div.decompose()

output_archive = {"date": today(), "html": str(soup), "most_recent": False}

# Mongodb connection
client = MongoClient(MONGODB_URL)
db = client.bro

# Insert fully generated archive html into mongo
# keeping one archive for each date
db.bromonitor.replace_one(
    filter={"date": output_archive["date"], "most_recent": False},
    replacement=output_archive,
    upsert=True,
)

# Insert fully generated most recent html into mongo
# Keep only exact 1 most_recent bromonitor
db.bromonitor.replace_one(
    filter={"most_recent": True}, replacement=output_recent, upsert=True
)

# Set the startdate value in the mongodb to the final registrationobject in the database currently
set_start_date()


import numpy as np

def parse(line):
    p = line.split('=')
    left = p[0].split(' ')
    print(p)
    x = left[left.index('x') - 1]
    y = left[left.index('x') - 1]
    right = p[1].strip()
    row = [x, y, right]
    return row

matrix = np.array([[1, 2], [3, 4]])
print('Determinant: ', np.linalg.det(matrix))
print('Inversion: ', np.linalg.inv(matrix))
print('Solution: (x, y) = ', np.linalg.solve(matrix, np.array([1, 2])))

firstLine = input('Enter first line:')
secondLine = input('Enter second line:')

firstRow = parse(firstLine)
secondRow = parse(secondLine)
matrix = np.array([firstRow, secondRow])

print('Solution: (x, y) = ', np.linalg.solve(matrix, np.array([1, 2])))


# 바텀업
N = int(input())
stair = [int(input()) for i in range(N)]
dp = []
if N > 0:
    dp.append(stair[0])
if N > 1:
    dp.append(stair[0]+stair[1])
if N > 2:
    dp.append(max(stair[2]+stair[0], stair[2]+stair[1]))
for i in range(3, N):
    dp.append(max((stair[i]+stair[i-1]+dp[i-3]), (stair[i] + dp[i-2])))
print(dp[N-1])

# 탑다운
# import sys
# sys.setrecursionlimit(20000)
# input = sys.stdin.readline
#
#
# def top_down(n):
#     if n == 1:
#         dp[1] = stair[1]
#         return dp[1]
#     if n == 2:
#         dp[2] = stair[2] + stair[1]
#         return dp[2]
#     if n == 3:
#         dp[3] = max(stair[3]+stair[1], stair[3] + stair[2])
#         return dp[3]
#     if n > 3 and dp[n] == 0:
#         dp[n] = max(stair[n]+stair[n-1]+top_down(n-3),
#                     stair[n] + top_down(n-2))
#         return dp[n]
#     return dp[n]
#
#
# N = int(input())
# stair = [0] + [int(input()) for _ in range(N)]
# dp = [0 for _ in range(N+1)]
# print(top_down(N))


import pdb

N = int(input())
i = 0
list = [0] * N

while i < N:
  list[i] = input()
  i += 1

list.sort()
k = 1
count_list = [0]*N
count_list[0] = 1
ans_list = [0] * N
ans_list[0] = list[0]
l = 0

while k < N:
  if list[k] != list[k-1]:
    l +=1
    ans_list[l] = list[k]

  count_list[l] += 1
  k += 1

s = max(count_list)

for i in range(N):
  if count_list[i] == s:
    print(ans_list[i])


##### Orange-HRM Login test #####
import time
import unittest
from selenium import webdriver
import HtmlTestRunner

class htmlReporting(unittest.TestCase):

    def setUp(self) -> None:
        driver_path = 'C:\\Users\\ranje\\Downloads\\chromedriver_win32\\chromedriver.exe'
        self.Driver = webdriver.Chrome(executable_path=driver_path)
        # application_url = 'https://opensource-demo.orangehrmlive.com/'
        # cls.Driver.get(application_url)
        # cls.Driver.maximize_window()

    def test_verifyHomePageTitle(self):
        application_url = 'https://opensource-demo.orangehrmlive.com/'
        self.Driver.get(application_url)
        # time.sleep(5)
        self.Driver.maximize_window()
        self.assertEqual("OrangeHRM",self.Driver.title,"webpage Title is not matching")

    def test_verifyLogin(self):
        application_url = 'https://opensource-demo.orangehrmlive.com/'
        self.Driver.get(application_url)
        time.sleep(5)
        self.Driver.find_element_by_id('txtUsername').send_keys('Admin')
        self.Driver.find_element_by_id('txtPassword').send_keys('admin123')
        self.Driver.find_element_by_id('btnLogin').click()
        # time.sleep(5)
        self.assertEqual("OrangeHRM", self.Driver.title, "webpage Title is not matching")

    def tearDown(self) -> None:
        self.Driver.quit()
        print("Test completed")

if __name__ == '__main__':
    unittest.main(testRunner = HtmlTestRunner.HTMLTestRunner(output = "E:\\python_code\\Reports"))

### to generate report we have to run the code through terminal

### python test_htmlReporting.py --> This is the command to run in terminal


def strlik(str1, str2):
	if not(len(str1)==len(str2)):
		return False
	for x in range(len(str1)):
		if str1[x] != str2[x]:
			return False
	return True
#print(strlik("test", "test"))

def strrev(streng):
	liste = list(streng)
	y = len(liste)
	x = 0
	while x <= (y/2):
		y -= 1
		liste[x], liste[y] = liste[y], liste[x]
		x += 1
	streng = "".join(liste)
	return streng
#print(strrev("anders"))

def strpal(streng):
	return strlik(streng, strrev(streng))
print(strpal("abba"))

def strinn(str1, str2):
	print(str1.split(str2))
	if(strlik(str1, "".join(str1.split(str2)))):
		return False
	return str1.index(str2)
print(strinn("blablatestblabla", "tomat"))


import paramaters as params
import numpy as np
import copy
import objectDefinitions as oD
from shapely.ops import nearest_points, cascaded_union
from sortedcontainers import SortedList, SortedSet, SortedDict
from shapely.geometry import Polygon, Point, LineString, MultiPolygon, box, MultiPoint
import plotContours
from matplotlib import pyplot
from mpl_toolkits.mplot3d import Axes3D

def calcPlaneTriangleIntersection( plane, triangle ):
	pointSigns = [1 ,1, 1]
	for i, vector in enumerate(triangle):
		if np.dot(np.subtract( vector, plane.Point), plane.Normal) < 0:
			pointSigns[i] = -1

	triangleLoc = np.sum(pointSigns)

	if triangleLoc == -1:
		triTip = pointSigns.index(1)

	elif triangleLoc == 1:
		triTip = pointSigns.index(-1)
	elif np.abs(triangleLoc) == 3:
		return False

	intVecs = []
	for i in range(3):
		if i != triTip:
			intVecs.append(np.subtract(triangle[i], triangle[triTip]))

	L1 = oD.Line(triangle[triTip], intVecs[0])
	L2 = oD.Line(triangle[triTip], intVecs[1])

	p1 = calcPlaneLineIntersection( plane, L1 )
	p2 = calcPlaneLineIntersection( plane, L2 )
	return [p1, p2]

# determine if a point is inside a given polygon or not
# Polygon is a list of (x,y) pairs.

def tolerantBinarySearchVertexList( vertex, vertexList , toPrint=False):
	"""Performs iterative binary search to find the position of a vertex in a given, sorted, list.
	vertexList -- sorted list of vertices or points
	vertex -- 3d vertex or point (list or array) you are searching for
	"""
	first = 0
	last = len(vertexList) -1
	if (not isinstance(vertex, list)):
		vertex = vertex.tolist()
	if toPrint:
		print(vertexList)
		print("Vertex to Find = %s" % (vertex))
	while first <= last:
		i = (first + last) / 2
		if toPrint:
			print("Current Index: %s, Vertex = %s" % (i, vertexList[i]))
		if tolerantEquals(vertexList[i], vertex):
				return i
		elif tolerantCompare( vertexList[i] , vertex) == 1:
				last = i - 1
		elif tolerantCompare( vertexList[i], vertex) == -1:
				first = i + 1
		else:
			return None

def tolerantLinearSearch( vertex, vertexList ):
	for i, vertexTest in enumerate(vertexList):

		if (not isinstance(vertexTest, list)):
			vertexTest = vertexTest.tolist()
		if tolerantEquals(vertexTest, vertex):
			return i

	return None

def tolerantEquals( v1, v2, tolerance=.000000001 ):

	if (np.abs(v1[0]-v2[0]) < tolerance) and (np.abs(v1[1]-v2[1]) < tolerance) and (np.abs(v1[2]-v2[2]) < tolerance):
		return True
	else:
		return False

def tolerantCompare( v1, v2, tolerance=.000000001 ):
	"""Returns True if v1 > v2 within tolerance, and False otherwise"""

	if (np.abs(v1[0]-v2[0]) < tolerance):
		if (np.abs(v1[1]-v2[1]) < tolerance):
			if (np.abs(v1[2]-v2[2]) < tolerance):
				return 0
			else:
				if v1[2] > v2[2]:
					return 1
		else:
			if v1[1] > v2[1]:
				return 1
	else:
		if v1[0] > v2[0]:
			return 1
	return -1

def cmp_to_key(mycmp):
    'Convert a cmp= function into a key= function'
    class K(object):
        def __init__(self, obj, *args):
            self.obj = obj
        def __lt__(self, other):
            return mycmp(self.obj, other.obj) < 0
        def __gt__(self, other):
            return mycmp(self.obj, other.obj) > 0
        def __eq__(self, other):
            return mycmp(self.obj, other.obj) == 0
        def __le__(self, other):
            return mycmp(self.obj, other.obj) <= 0
        def __ge__(self, other):
            return mycmp(self.obj, other.obj) >= 0
        def __ne__(self, other):
            return mycmp(self.obj, other.obj) != 0
    return K

def calcPlaneLineIntersection( plane, line ):
	sI = np.dot(plane.Normal, np.subtract(plane.Point, line.Point))/np.dot(plane.Normal, line.Direction)
	return line.Point + sI * line.Direction

def pointPlaneDist( plane, point ):
	pass

def projectPointToPlane( plane, planeX, point, normalize=True):
	if normalize:
		plane.Normal = np.linalg.norm(plane.Normal)
		planeX = np.linalg.norm(planeX)
	x = np.dot((point-plane.Point), planeX)
	y = np.dot((point-plane.Point), np.cross(plane.Normal, planeX))
	return [x,y]

def genTopology ( mesh ):

	vertexList = SortedList(key=cmp_to_key(tolerantCompare))
	faceList = SortedList()
	ind = 0

	for triangle in mesh.vectors:
		ind += 1
		for vertex in triangle:
			j = tolerantBinarySearchVertexList( vertex, vertexList )
			if (j == None):
				vertexList.add(vertex.tolist())

	ind = 0
	for triangle in mesh.vectors:
		ind += 1
		face = []
		for vertex in triangle:
			j = tolerantBinarySearchVertexList( vertex, vertexList )
			if (j != None):
				face.append(j)
			else:
				tolerantBinarySearchVertexList(vertex, vertexList, True)
				raise ValueError("Couldn't find vertex in list")
		faceList.add(face)

	return oD.MeshTopology(faceList, vertexList)

"""
Converts a polygon to a list. If the polygon is a multipolygon or already a list
it breaks it into a list of Polygon objects. If not, it returns a list containing the element
poly.
"""
def polyToList( poly ):

	if (type(poly) == list) or poly.geom_type == "MultiPolygon" :
		polyList = []
		for internalPoly in poly:
			polyList.append(Polygon(internalPoly.exterior.coords))
			for interior in internalPoly.interiors:
				polyList.append(Polygon(interior.coords))
		return polyList
	else:
		polyList = []
		for interior in poly.interiors:
			polyList.append(Polygon(interior.coords))

		if polyList:
			polyList.append(Polygon(poly.exterior.coords))
			return polyList
		else:
			return [poly]

"""
Optional paramaters to be added later for toolpath generation.
Units are in Millimeters
"""
def genToolPath( multiPoly, pathStepSize=3, initial_Offset=0, zHeight=0, topBounds=0, bufferRes=4 ):
	multiPoly = multiPoly.simplify(params.POLY_TOL, preserve_topology=True)
	polyList = []
	step = initial_Offset
	inc = 0
	while (1):
		inc += 1
		poly = multiPoly.buffer(step, resolution=bufferRes)
		poly = poly.simplify(params.POLY_TOL, preserve_topology=True)
		if poly.is_empty:
			break

		polyList.append(polyToList(poly))
		step = step-pathStepSize

	if polyList == []:
		return False
	orderedPolyList = []
	orderedPolyList.append(polyList[0].pop(0))

	currPolyIndex = 0
	while (1):
		if not polyList:
			break

		if currPolyIndex + 1 == len(polyList):
			if not polyList[0]:
				polyList.pop(0)
				currPolyIndex = currPolyIndex - 1
				continue
			orderedPolyList.append(polyList[0].pop(0))
			currPolyIndex = 0
			continue

		for i, nextPoly in enumerate(polyList[currPolyIndex+1], 0):
			if nextPoly.within(orderedPolyList[-1]):
				orderedPolyList.append(polyList[currPolyIndex+1].pop(i))
				break
		currPolyIndex = currPolyIndex + 1

	x = np.array([])
	y = np.array([])
	z = np.array([])
	for i, poly in enumerate(orderedPolyList[1::], 1):

		x_, y_ = orderedPolyList[i-1].exterior.coords.xy
		x = np.concatenate([x ,x_])if x.shape else x_
		y = np.concatenate([y ,y_])if y.shape else y_
		z = np.concatenate([z, np.full_like(x_, zHeight)])
		closestIndex = findClosestPolyIndex(orderedPolyList[i].exterior.coords,
											orderedPolyList[i-1].exterior.coords[0])
		orderedPolyList[i] = Polygon(orderedPolyList[i].exterior.coords[closestIndex:-1] +
									 orderedPolyList[i].exterior.coords[0:closestIndex])
		changePath = LineString([orderedPolyList[i-1].exterior.coords[0], orderedPolyList[i].exterior.coords[0]])

		if changePath.crosses(multiPoly) or not multiPoly.contains(changePath):

			cx, cy = changePath.coords.xy
			cz = np.full_like(cx, topBounds)
			x = np.concatenate([x, cx])
			y = np.concatenate([y, cy])
			z = np.concatenate([z, cz])

	x_, y_ = orderedPolyList[-1].exterior.coords.xy
	x = np.concatenate([x ,x_])
	y = np.concatenate([y ,y_])
	z = np.concatenate([z, np.full_like(x_, zHeight)])

	res = lambda: None
	res.PolyList = orderedPolyList
	res.XYZ = [x, y, z]

	return res

def generateToolOffsets( contourList, zSpacing, bufferRes=4, noVertical=False):
	"""
	Takes in a contour list (a list of polygons) that already
	take overhangs into account (i.e., all n+m contours contain
	the contour n) and generates a new contour list, where the resulting
	contours prevent the tool from hitting previous layers.
	"""

	# This assumes a cylindrical tool with a 90 degree conical tip.

	maxOffset = params.TOOL_DIAMETER/2

	resContourList = copy.deepcopy(contourList)
	for i, contour in enumerate(contourList, 0):
		currOffset = zSpacing
		buffList = []
		n = 1
		if noVertical:
			if n <= i:
				buffList.append(resContourList[i-n].buffer(currOffset, resolution=bufferRes))
		else:
			while n <= i:
				if currOffset > maxOffset:
					currOffset = maxOffset
					n = i # Set this so that the loop breaks after this round is completed
				buffList.append(contourList[i-n].buffer(currOffset, resolution=bufferRes))
				n = n + 1
				currOffset = zSpacing*n

		resContourList[i] = cascaded_union(buffList + [resContourList[i]])
		# if buffList:
		# 	resContourList[i] = buffList[-1]
	return resContourList

def genSlicePlanes( maxHeight, spacing, originCSys ):
	"""
	Generates a number of slicing planes with origins directly
	above the originCSys, and are 'spacing' distance apart from eachother.
	Note that this generates slicing planes in the Z Direction
	only, and for other axes of slicing, a transform and inverse transform should be
	used to move the model in and out of the correct coordinate system.
	"""

	planes = []
	zHeights = np.arange(originCSys.Point[2], originCSys.Point[2]+maxHeight, spacing)

	for zHeight in zHeights:
		originPoint = copy.deepcopy(originCSys.Point)
		originPoint[2] = zHeight
		planes.append(oD.Plane(originPoint, [0, 0, 1]))
	return planes

def contourConstruction( segmentList ):
	segMap = {}
	contours = []

	for segment in segmentList:
		if segment[0].__hash__() == segment[1].__hash__():
			continue
		u = segment[0]
		v = segment[1]
		if u not in segMap:
			segMap[u] = [v, None]
		else:
			segMap[u] = [segMap[u][0], v]

		if v not in segMap:
			segMap[v] = [u, None]
		else:
			segMap[v] = [segMap[v][0], u]

	while segMap:
		points = []
		points.append(segMap.keys()[0])
		[p, last] = segMap.pop(points[0])
		points.append(p)

		j = 1
		while points[j-1].__hash__() != last.__hash__():

			[u, v] = segMap.pop(points[j])

			if tolerantEquals(u.Coordinates, points[j-1].Coordinates):
				points.append(v)
			else:
				points.append(u)
			j += 1
		contours.append(points)

	return contours

def incrementalSlicing(meshTopology, planes, planeDelta):
	triangleLists = splitTriangles( meshTopology, planes, planeDelta)
	activeTriangles = set()
	segments = [[] for i in range(len(planes))]

	for i in range(len(planes)):
		activeTriangles.update(triangleLists[i])
		for triangle in copy.deepcopy(activeTriangles):
			if triangle.maxZ < planes[i].Point[2]:
				activeTriangles.remove(triangle)
			else:
				intPoints = calcPlaneTriangleIntersection( planes[i], meshTopology.getTrianglefromIndex(triangle.Indices))
				segments[i].append([oD.Vertex(intPoints[0]), oD.Vertex(intPoints[1])])

	return segments

def splitTriangles( meshTopology, planes, planeDelta):
	triangleLists = [set() for i in range(len(planes)+1)]
	if planeDelta > 0:
		for i, face in enumerate(meshTopology.faces, 0):
			triangle = meshTopology.getTrianglefromIndex( face )
			minT, maxT = meshTopology.minMaxList[i]

			if minT < planes[0].Point[2]:
				j = 0

			elif minT > planes[-1].Point[2]:
				j = len(planes)
			else:
				j = int(np.floor((minT-planes[0].Point[2])/planeDelta) +1 )
			triangleLists[j] = triangleLists[j].union([oD.Face(face, minT, maxT)])

	return triangleLists

def findClosestPolyIndex( coords, point ):
	minDist = float("inf")
	minIndex = -1
	for i, coord in enumerate(coords, 0):
		newDist = dist(coord, point)
		if newDist < minDist:
			minIndex = i
			minDist = newDist
	return minIndex

def dist(x,y):
    return np.sqrt((x[0]-y[0])**2 + (x[1]-y[1])**2)

def getPolyDepth( multiPoly ):
	"""
	Returns a list of polygons (not a MultiPolygon) where each polygon has the attribute
	'depth' which represents the depth of containment of the polygon
	"""
	multiPoly = MultiPolygon(sorted(multiPoly, key=oD.Within, reverse=True))

	depthMapping = []
	multiPolyList = []
	for poly in multiPoly:
		poly.depth = 0
		multiPolyList.append(poly)

	for i, poly in enumerate(multiPolyList, 0):
		if i == len(multiPolyList)-1:
			break
		if oD.Within(multiPolyList[i+1]) < oD.Within(poly):
			multiPolyList[i+1].depth = poly.depth + 1
		else:
			multiPolyList[i+1].depth = poly.depth

	polygonGrouping = []
	polyInteriors = []
	for poly in multiPolyList:

		polyExterior = None

		if poly.depth % 2 == 0:
			polygonGrouping.append([poly, []])
		else:
			polygonGrouping[-1][1].append(poly)

	polyResults = []
	for polygon in polygonGrouping:

		if polygon[1]:
			tupleList =[]
			for polygonInstance in polygon[1]:
				tupleList.append(list(polygonInstance.exterior.coords))
			polyResults.append(Polygon(list(polygon[0].exterior.coords), tupleList))

		else:
			polyResults.append(polygon[0])

	return MultiPolygon(polyResults)


#Program: eHP Calculator
#Author: Seth Weaver-Rosamilia
#Last date modified 2/26/23
#Calculates effective HP for the game League of Legends

#imports breezypython
from breezypythongui import EasyFrame

#establishes window layout
class eHP(EasyFrame):
    def __init__(self):
        EasyFrame.__init__(self, width = 600, height = 200, title = 'LoL eHP Calculator')

        topPanel = self.addPanel(row = 0, column = 0)

        baseHealthPanel = topPanel.addPanel(row = 0, column = 0)
        baseHealthLabel = baseHealthPanel.addLabel(text = "Enter Base HP", row = 0, column = 1)
        baseHealthInput = baseHealthPanel.addIntegerField(value = 0, row = 1, column = 1)
        baseHealthInput.grid(sticky = "NW")

        baseArmorPanel = topPanel.addPanel(row = 0, column = 1)
        baseArmorLabel = baseArmorPanel.addLabel(text = "Enter Base Armor", row = 0, column = 1)
        baseArmorInput = baseArmorPanel.addIntegerField(value = 0, row = 1, column = 1)
        baseArmorInput.grid(sticky = "NW")

        baseMRPanel = topPanel.addPanel(row = 0, column = 2)
        baseMRLabel = baseMRPanel.addLabel(text = "Enter Base MR", row = 0, column = 1)
        baseMRInput = baseMRPanel.addIntegerField(value = 0, row = 1, column = 1)
        baseMRInput.grid(sticky = "NW")

        midPanel = self.addPanel(row = 1, column = 0)

        bonusHealthPanel = midPanel.addPanel(row = 0, column = 0)
        bonusHealthLabel = bonusHealthPanel.addLabel(text = "Enter Bonus HP", row = 0, column = 1)
        bonusHealthInput = bonusHealthPanel.addIntegerField(value = 0, row = 1, column = 1)
        bonusHealthInput.grid(sticky = "NW")

        bonusArmorPanel = midPanel.addPanel(row = 0, column = 1)
        bonusArmorLabel = bonusArmorPanel.addLabel(text = "Enter Bonus Armor", row = 0, column = 1)
        bonusArmorInput = bonusArmorPanel.addIntegerField(value = 0, row = 1, column = 1)
        bonusArmorInput.grid(sticky = "NW")

        bonusMRPanel = midPanel.addPanel(row = 0, column = 2)
        bonusMRLabel = bonusMRPanel.addLabel(text = "Enter bonus MR", row = 0, column = 1)
        bonusMRInput = bonusMRPanel.addIntegerField(value = 0, row = 1, column = 1)
        bonusMRInput.grid(sticky = "NW")

        botPanel = self.addPanel(row = 2, column = 0)
        calcButton = botPanel.addButton(row = 0, column = 0, text = "Calculate", command = self.calculateTotals)
        calcButton.grid(sticky = "NSEW")

    def calculateTotals():
        baseHP = baseHealthInput.getNumber()
        bonusHP = bonusHealthInput.getNumber()
        totalHP = baseHP + bonusHP

        baseArmor = baseArmorInput.getNumber()
        bonusArmor = bonusArmorInput.getNumber()
        totalArmor = baseArmor + bonusArmor

        baseMR = baseMRInput.getNumber()
        bonusMR = bonusMRInput.getNumber()
        totalMR = baseMR + bonusMR

def main():
    eHP().mainloop()

if __name__ == "__main__":
    main()


import os
#import math
from sklearn.model_selection import StratifiedKFold
import numpy as np
from FileDataGenerator import FileDataGen
#import copy
import warnings
warnings.filterwarnings(action='ignore', category=DeprecationWarning)

class K_Fold:
    def __init__(self, path, k):
        data=[]
        labels=[]
        for class_ in os.listdir(path):
            dat = [os.path.join(path, class_, img) for img in os.listdir(os.path.join(path, class_))]
            lab = [class_ for i in os.listdir(os.path.join(path, class_))]
            labels = labels+lab
            data = data + dat

        self.folds = list(StratifiedKFold(n_splits=k, shuffle=True, random_state=1234).split(data, labels))
        self.data = np.array(data)
        self.labels = np.array(labels)
        self.k=k
        self.hist_dict = dict()

    def Apply_KFold(self, model, train_gen_params, test_gen_params, train_params, test_params, fit_params):

        self.hist_dict = dict()

        Wsave = model.get_weights()

        train_datagen = FileDataGen(**train_gen_params)
        val_datagen = FileDataGen(**test_gen_params)

        for j, (train_idx, val_idx) in enumerate(self.folds):
            print('\nFold ',j)
            X_train_cv = self.data[train_idx]
            y_train_cv = self.labels[train_idx]
            X_valid_cv = self.data[val_idx]
            y_valid_cv= self.labels[val_idx]

            model.set_weights(Wsave) #re-initialize weights

            train_gen = train_datagen.flow_from_filelist(
                X_train_cv,
                y_train_cv,
                **train_params)

            val_gen = val_datagen.flow_from_filelist(
                X_valid_cv,
                y_valid_cv,
                **test_params)

            print('Training')
            hist=model.fit_generator(
                generator = train_gen,
                steps_per_epoch=len(X_train_cv)/train_params['batch_size'],
                **fit_params,
                validation_steps=len(X_valid_cv)/test_params['batch_size'],
                validation_data = val_gen)

            if len(self.hist_dict) == 0:
                #self.hist_dict = copy.deepcopy(hist.history) #Save all the data
                for key, val in hist.history.items(): #Just save at the end of the epoch
                    self.hist_dict[key]= [val[len(val)-1]]
            else:
                for key, val in hist.history.items(): #Just save at the end of the epoch
                    self.hist_dict[key].append(val[len(val)-1])
                    #for i in val:  #Save all the data
                        #self.hist_dict[key].append(i)
        return self.hist_dict

    def Check_Folds(self):
        print('There are {} Folds'.format(len(self.folds)))
        #print('Train data contains {} samples'.format(len(self.folds[0][0])))
        #print('Test data contains {} samples'.format(len(self.folds[0][1])))
        #print('Test data samples are aprox computed from ceil(len(data)/k): {}'.format(math.ceil(len(self.data)/self.k)))
        for j, (train_idx, val_idx) in enumerate(self.folds):
            print('\nFold ',j)
            X_train_cv = self.data[train_idx]
            y_train_cv = self.labels[train_idx]
            X_valid_cv = self.data[val_idx]
            y_valid_cv= self.labels[val_idx]
            unique, counts = np.unique(y_train_cv, return_counts=True)
            print('For training, {0} samples: {1}'.format(len(X_train_cv), dict(zip(unique, counts))))

            unique, counts = np.unique(y_valid_cv, return_counts=True)
            print('For testing, {0} samples: {1}'.format(len(X_valid_cv), dict(zip(unique, counts))))

            print('First five X_train images: \n{}'.format(X_train_cv[:5]))
            print('First five X_val images: \n{}'.format(X_valid_cv[:5]))


import os
import sys
sys.path.append('../')
import gc
import glob
import yaml
import datetime
import random

import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as f
from torch.utils.data import DataLoader
import tensorboardX as tbx
from torchvision.models import resnet18
from fastprogress import progress_bar, master_bar

from models import *
from src.augmentation import get_test_augmentation, get_train_augmentation
from src.dataset import AlconDataset, KanaDataset
from src.metrics import *
from src.utils import *
from src.collates import *
from train_methods import *

#TODO:

def main():
    now = datetime.datetime.now()
    now_date = '{}-{:0>2d}-{:0>2d}_{:0>2d}-{:0>2d}-{:0>2d}'.format(now.year, now.month, now.day, now.hour, now.minute, now.second)
    print('{}-{}-{} {}:{}:{}'.format(now.year, now.month, now.day, now.hour, now.minute, now.second))
    with open('../params/exp0.yaml', "r+") as f:
        param = yaml.load(f, Loader=yaml.FullLoader)
    param['date'] =  now_date
    # seed set
    seed_setting(param['seed'])
    if torch.cuda.is_available():
        torch.backends.cudnn.benchmark = True

    fold = param['fold']
    outdir = os.path.join(param['save path'], str(os.path.basename(__file__).split('.')[-2]) + '_fold{}'.format(fold), now_date)
    if os.path.exists(param['save path']):
        os.makedirs(outdir, exist_ok=True)
    else:
        print("Not find {}".format(param['save path']))
        raise FileNotFoundError

    # outdir = '../tmp'

    # Dataset

    train_dataset = AlconDataset(df=get_train_df().query('valid != @fold'),
                                 augmentation=get_train_augmentation(),
                                 datadir=os.path.join(param['dataroot'],'train','imgs'), mode='train')
    valid_dataset = AlconDataset(df=get_train_df().query('valid == @fold'),
                                 augmentation=get_test_augmentation(),
                                 datadir=os.path.join(param['dataroot'],'train','imgs'), mode='valid')
    print('train dataset size: {}'.format(len(train_dataset)))
    print('valid dataset size: {}'.format(len(valid_dataset)))

    # Dataloader
    train_dataloader = DataLoader(train_dataset, batch_size=param['batch size'], num_workers=param['thread'],
                                  pin_memory=False, drop_last=False)
    valid_dataloader = DataLoader(valid_dataset, batch_size=param['batch size'], num_workers=param['thread'],
                                  pin_memory=False, drop_last=False)

    print('train loader size: {}'.format(len(train_dataloader)))
    print('valid loader size: {}'.format(len(valid_dataloader)))

    # model
    model = resnet18(pretrained=True)
    model.fc = nn.Linear(model.fc.in_features, 48)

    param['model'] = model.__class__.__name__

    # optim
    if param['optim'].lower() == 'sgd':
        optimizer = torch.optim.SGD(model.parameters(), lr=param['lr'], momentum=0.9,
                                    weight_decay=1e-5, nesterov=False)
    elif param['optim'].lower() == 'adam':
        optimizer =  torch.optim.SGD(model.parameters(), lr=param['lr'])
    else:
        raise NotImplementedError

    # scheduler
    scheduler = eval(param['scheduler'])

    model = model.to(param['device'])
    loss_fn = torch.nn.CrossEntropyLoss().to(param['device'])
    eval_fn = accuracy

    max_char_acc = 0.
    max_3char_acc = 0.
    min_loss = 10**5

    writer = tbx.SummaryWriter("../log/exp0")
    for key, val in param.items():
        # print(f'{key}: {val}')
        writer.add_text('data/hyperparam/{}'.format(key), str(val), 0)

    mb = master_bar(range(param['epoch']))
    for epoch in mb:
        avg_train_loss, avg_train_accuracy, avg_three_train_acc = train_alcon(model, optimizer, train_dataloader, param['device'],
                                       loss_fn, eval_fn, epoch, scheduler=None, writer=writer, parent=mb) #ok

        avg_valid_loss, avg_valid_accuracy, avg_three_valid_acc = valid_alcon(model, valid_dataloader, param['device'],
                                                                              loss_fn, eval_fn)

        writer.add_scalars("data/metric/valid", {
            'loss': avg_valid_loss,
            'accuracy': avg_valid_accuracy,
            '3accuracy': avg_three_valid_acc
        }, epoch)

        print('======================== epoch {} ========================'.format(epoch+1))
        print('lr              : {:.5f}'.format(scheduler.get_lr()[0]))
        print('loss            : train={:.5f}  , test={:.5f}'.format(avg_train_loss, avg_valid_loss))
        print('acc(per 1 char) : train={:.3%}  , test={:.3%}'.format(avg_train_accuracy, avg_valid_accuracy))
        print('acc(per 3 char) : train={:.3%}  , test={:.3%}'.format(avg_three_train_acc, avg_three_valid_acc))

        if min_loss > avg_valid_loss:
            print('update best loss:  {:.5f} ---> {:.5f}'.format(min_loss, avg_valid_loss))
            min_loss = avg_valid_loss
            torch.save(model.state_dict(), os.path.join(outdir, 'best_loss.pth'))

        if max_char_acc < avg_valid_accuracy:
            print('update best acc per 1 char:  {:.3%} ---> {:.3%}'.format(max_char_acc, avg_valid_accuracy))
            max_char_acc = avg_valid_accuracy
            torch.save(model.state_dict(), os.path.join(outdir, 'best_acc.pth'))

        if max_3char_acc < avg_three_valid_acc:
            print('update best acc per 3 char:  {:.3%} ---> {:.3%}'.format(max_3char_acc , avg_three_valid_acc))
            max_3char_acc = avg_three_valid_acc
            torch.save(model.state_dict(), os.path.join(outdir, 'best_3acc.pth'))

        if 1:
            if scheduler is not None:
                if writer is not None:
                    writer.add_scalar("data/learning rate", scheduler.get_lr()[0], epoch)
                scheduler.step()

    writer.add_scalars("data/metric/valid", {
        'best loss': min_loss,
        'best accuracy': max_char_acc,
        'best 3accuracy': max_3char_acc
    })

    print('finish train')
    print('result')
    print('best loss : {}'.format(min_loss))
    print('best 1 acc : {}'.format(max_char_acc))
    print('best 3 acc : {}'.format(max_3char_acc))
    writer.export_scalars_to_json(os.path.join(outdir, 'history.json'))
    writer.close()

    #TODO: gc.collect()
    del train_dataset, valid_dataset
    del train_dataloader, valid_dataloader
    del scheduler, optimizer
    gc.collect()

    print('load weight')
    model.load_state_dict(torch.load(os.path.join(outdir, 'best_3acc.pth')))

    test_dataset = AlconDataset(df=get_test_df(),
                                augmentation=get_test_augmentation(),
                                datadir=os.path.join(param['dataroot'], 'test', 'imgs'), mode='test')

    test_dataloader = DataLoader(test_dataset, batch_size=param['batch size'], num_workers=param['thread'],
                                 pin_memory=False, drop_last=False)
    print('test dataset size: {}'.format(len(test_dataset)))
    print('test loader size: {}'.format(len(test_dataloader)))

    output_list = pred_alcon(model, test_dataloader, param['device'])
    torch.save(output_list, os.path.join(outdir, 'prediction.pth'))
    pd.DataFrame(output_list).drop('logit', axis=1).sort_values('ID').set_index('ID').to_csv(os.path.join(outdir, 'submission.csv'))

# def prediction():
#     pass
#
# def train(model, optimizer, dataloader, device, loss_fn, eval_fn, epoch, scheduler=None, writer=None):
#     model.train()
#     avg_loss = 0
#     avg_accuracy = 0
#     for step, (inputs, targets) in enumerate(dataloader):
#         inputs = inputs.to(device)
#         targets = targets.to(device)
#         optimizer.zero_grad()
#         logits = model(inputs)
#         preds = logits.softmax(dim=1)
#         loss = loss_fn(logits, targets.argmax(dim=1))
#         loss.backward()
#         # nn.utils.clip_grad_norm_(model.parameters(), 1.0)
#         optimizer.step()
#         avg_loss += loss.item()
#         acc = eval_fn(preds, targets)
#         avg_accuracy += acc
#         if scheduler is not None:
#             if writer is not None:
#                 writer.add_scalar("data/learning rate", scheduler.get_lr()[0], step + epoch*len(dataloader))
#             scheduler.step()
#
#         writer.add_scalars("data/metric/train", {
#             'train_loss': loss.item(),
#             'train_accuracy': acc
#         }, step + epoch*len(dataloader))
#
#     avg_loss /= len(dataloader)
#     avg_accuracy /= len(dataloader)
#     return avg_loss, avg_accuracy
#
#
# def valid(model, dataloader, device, loss_fn, eval_fn):
#     model.eval()
#     avg_loss = 0
#     avg_accuracy = 0
#     with torch.no_grad():
#         for inputs, targets in dataloader:
#             inputs = inputs.to(device)
#             targets = targets.to(device)
#             logits = model(inputs)
#             preds = logits.softmax(dim=1)
#             loss = loss_fn(logits, targets.argmax(dim=1))
#             avg_loss += loss.item()
#             avg_accuracy += eval_fn(preds, targets)
#         avg_loss /= len(dataloader)
#         avg_accuracy /= len(dataloader)
#
#     return avg_loss, avg_accuracy
#
#
# def train_alcon(model, optimizer, dataloader, device, loss_fn, eval_fn, epoch, scheduler=None, writer=None, parent=None):
#     model.train()
#     avg_loss = 0
#     avg_accuracy = 0
#     three_char_accuracy = 0
#     for step, (inputs, targets, indices) in enumerate(progress_bar(dataloader, parent=parent)):
#         inputs = inputs.to(device)
#         targets = targets.to(device)
#         _avg_loss = 0
#         _avg_accuracy = 0
#         preds = torch.zeros(targets.size()).to(device)
#         for i in range(3):
#             # _inputs = inputs[:, i].to(device)
#             # _targets = targets[:, i].to(device)
#             _inputs = inputs[:, i]
#             _targets = targets[:, i]
#             optimizer.zero_grad()
#             logits = model(_inputs)
#             _preds = logits.softmax(dim=1)
#             preds[:, i] = _preds
#             loss = loss_fn(logits, _targets.argmax(dim=1))
#             loss.backward()
#             # nn.utils.clip_grad_norm_(model.parameters(), 1.0)
#             optimizer.step()
#             _avg_loss += loss.item()
#             acc = eval_fn(_preds, _targets.argmax(dim=1))
#             _avg_accuracy += acc.item()
#         _avg_loss /= 3
#         avg_loss += _avg_loss
#         _avg_accuracy /= 3
#         avg_accuracy += _avg_accuracy
#
#         _three_char_accuracy = accuracy_three_character(preds, targets.argmax(dim=2), mean=True).item()
#         # _three_char_accuracy = accuracy_three_character(pred, targets.to(device), mean=True)
#
#         three_char_accuracy += _three_char_accuracy
#         if scheduler is not None:
#             if writer is not None:
#                 writer.add_scalar("data/learning rate", scheduler.get_lr()[0], step + epoch*len(dataloader))
#             scheduler.step()
#
#         writer.add_scalars("data/metric/train", {
#             'loss': _avg_loss,
#             'accuracy': _avg_accuracy,
#             '3accuracy': _three_char_accuracy
#         }, step + epoch*len(dataloader))
#
#     avg_loss /= len(dataloader)
#     avg_accuracy /= len(dataloader)
#     three_char_accuracy /= len(dataloader)
#     print()
#     return avg_loss, avg_accuracy, three_char_accuracy
#
#
# def valid_alcon(model, dataloader, device, loss_fn, eval_fn):
#     model.eval()
#     with torch.no_grad():
#         avg_loss = 0
#         avg_accuracy = 0
#         three_char_accuracy = 0
#         for step, (inputs, targets, indices) in enumerate(dataloader):
#             inputs = inputs.to(device)
#             targets = targets.to(device)
#             _avg_loss = 0
#             _avg_accuracy = 0
#             preds = torch.zeros(targets.size()).to(device)
#             for i in range(3):
#                 # _inputs = inputs[:, i].to(device)
#                 # _targets = targets[:, i].to(device)
#                 _inputs = inputs[:, i]
#                 _targets = targets[:, i]
#                 logits = model(_inputs)
#                 _preds = logits.softmax(dim=1)
#                 preds[:, i] = _preds
#                 loss = loss_fn(logits, _targets.argmax(dim=1))
#                 _avg_loss += loss.item()
#                 acc = eval_fn(_preds, _targets.argmax(dim=1))
#                 _avg_accuracy += acc.item()
#             _avg_loss /= 3
#             avg_loss += _avg_loss
#             _avg_accuracy /= 3
#             avg_accuracy += _avg_accuracy
#
#             _three_char_accuracy = accuracy_three_character(preds, targets.argmax(dim=2), mean=True).item()
#             # _three_char_accuracy = accuracy_three_character(pred, targets.to(device), mean=True)
#
#             three_char_accuracy += _three_char_accuracy
#
#         avg_loss /= len(dataloader)
#         avg_accuracy /= len(dataloader)
#         three_char_accuracy /= len(dataloader)
#     return avg_loss, avg_accuracy, three_char_accuracy
#
#
# def train_mixup(model, optimizer, dataloader, device, loss_fn, eval_fn, epoch, scheduler=None, writer=None):
#     model.train()
#     avg_loss = 0
#     for step, (inputs, targets) in enumerate(dataloader):
#         inputs = inputs.to(device)
#         targets = targets.to(device)
#         inputs, targets_a, targets_b, lam = mixup_data(inputs, targets, alpha=0.2, device=device)
#         optimizer.zero_grad()
#         logits = model(inputs)
#         loss = lam * loss_fn(logits, targets_a.argmax(dim=1))  + (1 - lam) * loss_fn(logits, targets_b.argmax(dim=1))
#         loss.backward()
#         nn.utils.clip_grad_norm_(model.parameters(), 1.0)
#         optimizer.step()
#         avg_loss += loss.item()
#         if scheduler is not None:
#             if writer is not None:
#                 writer.add_scalar("data/lr", scheduler.get_lr()[0], step + epoch*len(dataloader))
#             scheduler.step()
#         writer.add_scalars("data/metric/train_loss", loss.item(), step + epoch * len(dataloader))
#     avg_loss /= len(dataloader)
#
#     return avg_loss

if __name__ =='__main__':
    main()


from collections import Counter

import aoc

def part1(data: str, *, days_after: int = 0):
    fishes = [int(n) for n in data.strip().split(",")]

    while days_after:
        fishes_now = fishes[:]
        for i, fish in enumerate(fishes_now):
            fish -= 1
            if fish == -1:
                fishes.append(8)
                fishes[i] = 6
            else:
                fishes[i] = fish
        # print(fishes)

        days_after -= 1

    return len(fishes)

def part2(data: str, *, days_after: int = 0):
    """ Optimized?
    """
    fishes = [int(n) for n in data.strip().split(",")]

    counter = Counter(fishes)

    while days_after:
        # how many "maturing" ?
        maturing = counter.pop(0, 0)

        # shift the counters ( 1 -> 0, 2 -> 1, ..., 8 -> 7 )
        counter = {n - 1: counter[n] for n in range(1, 9)}

        # account for the ones that matured
        counter[8] = maturing
        counter[6] += maturing

        days_after -= 1

    return sum(counter.values())

test_input = "3,4,3,1,2"

def test_part1():
    assert 26 == part1(test_input, days_after=18)
    assert 5934 == part1(test_input, days_after=80)

def test_part2():
    assert 26 == part2(test_input, days_after=18)
    assert 5934 == part2(test_input, days_after=80)

if __name__ == '__main__':
    data = aoc.read_input(__file__)
    print("Part1", part1(data, days_after=80))
    print("Part2", part2(data, days_after=256))


def encrypt(text, s):
    result = ""
    for char in text:
        if char.isupper():
            result += chr((ord(char) + s - 65) % 26 + 65)
        elif char.islower():
            result += chr((ord(char) + s - 97) % 26 + 97)
        else:
            result += char

    return result

def decrypt(text):
    result = []
    for s in range(26):
        temp = ''
        for char in text:
            if char.isupper():
                temp += chr((ord(char) + s - 65) % 26 + 65)
            elif char.islower():
                temp += chr((ord(char) + s - 97) % 26 + 97)
            else:
                temp += char
        result.append(temp)
    return result