import os, yaml, logging, re
# external imports
import torch
from joeynmt.helpers import load_config
from subword_nmt import apply_bpe
from subword_nmt import apply_bpe
from sacremoses import MosesTokenizer, MosesDetokenizer
from joeynmt.helpers import load_config, get_latest_checkpoint, \
    load_checkpoint
from joeynmt.vocabulary import build_vocab
from joeynmt.model import build_model
from joeynmt.prediction import validate_on_data
from urllib.request import urlopen
from io import BytesIO
from zipfile import ZipFile

# internal imports
from core.utils import load_line_as_data

class MasakhaneModelLoader():
    """User Defined Class to manage the download of machine trasnlation models"""
    def __init__(self, available_models_file):
        # model directory to store the modeks
        self._model_dir_prefix = os.environ.get('MODEL',
                                                "./models/joeynmt/")
        self._src_language = ''
        #load availiable models into memory
        self.models = self.load_available_models(available_models_file)

    def load_available_models(self, available_models_file):
        """Load a dictonary with available models to download"""
        models = {}
        with open(available_models_file, 'r') as ofile:
            # iterate over file entries
            for i, line in enumerate(ofile):
                entries = line.strip().split("\t")
                # extract headers
                if i == 0:
                    header_keys = [h.__str__() for h in entries]
                    continue

                # build available model dictionary from the headers & entries:
                # https://www.geeksforgeeks.org/python-dictionary-comprehension/
                model = {key:value for key,value in zip(header_keys, entries)}
                # don't add incomplete models
                if model['complete'] != 'yes':
                    continue

                models[f"{model['src_language']}-{model['tgt_language']}-{model['domain']}"] = model

        print('Found {} Masakhane models.'.format(len(models)))

        return models

    def download_model(self, src_language, tgt_language, domain):
        """ Download model for given trg language. """
        model_dir = f"{self._model_dir_prefix}{src_language}-{tgt_language}-{domain}"

        if not os.path.exists(model_dir):
            os.system(f'mkdir -p {model_dir}')

        model_files = self.models[f"{src_language}-{tgt_language}-{domain}"]

        # Check if files exist
        ckpt_path = os.path.join(model_dir, 'model.ckpt')
        src_vocab_path = os.path.join(model_dir, 'src_vocab.txt')
        trg_vocab_path = os.path.join(model_dir, 'trg_vocab.txt')
        config_path = os.path.join(model_dir, 'config_orig.yaml')
        src_bpe_path = os.path.join(model_dir, 'src.bpe.model')
        trg_bpe_path = os.path.join(model_dir, 'trg.bpe.model')

        if not os.path.exists in [ckpt_path, src_vocab_path, trg_vocab_path, config_path, src_bpe_path, trg_bpe_path]:
            URL = "https://zenodo.org/record/7636723/files/" + \
                src_language + "-" + tgt_language
            if domain == "":
                URL += "-baseline.zip?download=1"
            else:
                URL += "-" + domain + "-baseline.zip?download=1"

            http_response = urlopen(URL)
            zipfile = ZipFile(BytesIO(http_response.read()))
            zipfile.extractall(path=model_dir)

        # Rename config file to config_orig.yaml.
        os.rename(os.path.join(model_dir, 'config.yaml'), config_path)

        # Adjust config.
        config = load_config(config_path)
        new_config_file = os.path.join(model_dir, 'config.yaml')
        config = self._update_config(config, src_vocab_path, trg_vocab_path,
                                     model_dir, ckpt_path)
        with open(new_config_file, 'w') as cfile:
            yaml.dump(config, cfile)

        print('Downloaded model for {}-{}.'.format(src_language, tgt_language))

    def load_model(self, src_language, tgt_language, domain, bpe_src_code=None, tokenize=None):
        """ Load model for given trg language. """
        model_dir = f"{self._model_dir_prefix}{src_language}-{tgt_language}-{domain}"

        ckpt_path = os.path.join(model_dir, 'model.ckpt')
        src_vocab_path = os.path.join(model_dir, 'src_vocab.txt')
        trg_vocab_path = os.path.join(model_dir, 'trg_vocab.txt')
        config_path = os.path.join(model_dir, 'config_orig.yaml')

        # Adjust config.
        config = load_config(config_path)
        new_config_file = os.path.join(model_dir, 'config.yaml')
        config = self._update_config(config, src_vocab_path, trg_vocab_path,
                                     model_dir, ckpt_path)
        with open(new_config_file, 'w') as cfile:
            yaml.dump(config, cfile)

        print('Loaded model for {}-{}.'.format(src_language, tgt_language))

        conf = {}

        logger = logging.getLogger(__name__)
        conf["logger"] = logger
        # load the Joey configuration
        cfg = load_config(new_config_file)
        # load the checkpoint
        if "load_model" in cfg['training'].keys():
            ckpt = cfg['training']["load_model"]
        else:
            ckpt = get_latest_checkpoint(model_dir)
            if ckpt is None:
                raise FileNotFoundError("No checkpoint found in directory {}."
                                        .format(model_dir))

        # prediction parameters from config
        conf["use_cuda"] = cfg["training"].get(
            "use_cuda", False) if torch.cuda.is_available() else False

        conf["level"] = cfg["data"]["level"]
        conf["max_output_length"] = cfg["training"].get(
            "max_output_length", None)
        conf["lowercase"] = cfg["data"].get("lowercase", False)

        # load the vocabularies
        src_vocab_file = cfg["training"]["model_dir"] + "/src_vocab.txt"
        trg_vocab_file = cfg["training"]["model_dir"] + "/trg_vocab.txt"

        conf["src_vocab"] = build_vocab(field="src", vocab_file=src_vocab_file,
                                        dataset=None, max_size=-1, min_freq=0)
        conf["trg_vocab"] = build_vocab(field="trg", vocab_file=trg_vocab_file,
                                        dataset=None, max_size=-1, min_freq=0)

        # whether to use beam search for decoding, 0: greedy decoding
        if "testing" in cfg.keys():
            conf["beam_size"] = cfg["testing"].get("beam_size", 0)
            conf["beam_alpha"] = cfg["testing"].get("alpha", -1)
        else:
            conf["beam_size"] = 1
            conf["beam_alpha"] = -1

        # pre-processing
        if tokenize is not None:
            src_tokenizer = MosesTokenizer(lang=cfg["data"]["src"])
            trg_tokenizer = MosesDetokenizer(lang=cfg["data"]["trg"])
            # tokenize input
            def tokenizer(x): return src_tokenizer.tokenize(x, return_str=True)
            def detokenizer(x): return trg_tokenizer.detokenize(
                x.split(), return_str=True)
        else:
            def tokenizer(x): return x
            def detokenizer(x): return x

        if bpe_src_code is not None and level == "bpe":
            # load bpe merge file
            merge_file = open(bpe_src_code, "r")
            bpe = apply_bpe.BPE(codes=merge_file)
            def segmenter(x): return bpe.process_line(x.strip())
        elif conf["level"] == "char":
            # split to chars
            def segmenter(x): return list(x.strip())
        else:
            def segmenter(x): return x.strip()

        conf["preprocess"] = [tokenizer, segmenter]
        conf["postprocess"] = [detokenizer]
        # build model and load parameters into it
        model_checkpoint = load_checkpoint(ckpt, conf["use_cuda"])
        model = build_model(
            cfg["model"], src_vocab=conf["src_vocab"], trg_vocab=conf["trg_vocab"])
        model.load_state_dict(model_checkpoint["model_state"])
        if conf["use_cuda"]:
            model.cuda()
        conf["model"] = model
        print("Joey NMT model loaded successfully.")

        return conf

    def _update_config(self, config, new_src_vocab_path, new_trg_vocab_path,
                       new_model_dir, new_ckpt_path):
        """Overwrite the settings in the given config."""
        config['data']['src_vocab'] = new_src_vocab_path
        if config['model'].get('tied_embeddings', False):
            config['data']['trg_vocab'] = new_src_vocab_path
        else:
            config['data']['trg_vocab'] = new_trg_vocab_path
        config['training']['model_dir'] = new_model_dir
        config['training']['load_model'] = new_ckpt_path
        return config

    def _is_lowercase(self, src_vocab_path):
        # Infer whether the model is built on lowercased data.
        lowercase = True
        with open(src_vocab_path, 'r') as ofile:
            for line in ofile:
                if line != line.lower():
                    lowercase = False
                    break
        return lowercase

# Doesn't look like these functions are ever called...

    def _download_gdrive_file(self, file_id, destination):
        """Download a file from Google Drive and store in local file."""
        download_link = 'https://drive.google.com/uc?id={}'.format(file_id)
        os.system(f'gdown -q -O {destination} {download_link}')

    def _download_github_file(self, github_raw_path, destination):
        """Download a file from GitHub."""
        os.system(f'wget -q -O {destination} {github_raw_path}')

    def _download(self, url, destination):
        """Download file from Github or Googledrive."""
        try:
            if 'drive.google.com' in url:
                if url.startswith('https://drive.google.com/file'):
                    file_id = url.split("/")[-1]
                elif url.startswith('https://drive.google.com/open?'):
                    file_id = url.split('id=')[-1]
                self._download_gdrive_file(file_id, destination)
            else:
                self._download_github_file(url, destination)
        except:
            print("Download failed, didn't recognize url {}.".format(url))


#!/usr/bin/python
# -*- coding: utf-8 -*-
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap

def down_sample(a):
    tmp=[]
    for t in a:
        tmp.append(t[3::4])
    return tmp[3::4]

def draw_velocity(time, output_dir, data_dir):
    v = np.fromfile(data_dir + '/bin_data/atm_v_time_{}_iter_n_layer_0.bin'.format(time),'<f8')
    w = np.fromfile(data_dir + '/bin_data/atm_w_time_{}_iter_n_layer_0.bin'.format(time),'<f8')
    h = np.fromfile(data_dir + '/bin_data/atm_h_time_{}_iter_n_layer_0.bin'.format(time),'<f8')

    vm = np.sqrt(v**2+w**2)
    vm[vm==0] = 1
    v = v/vm
    w = w/vm

    x = np.linspace(-180, 180, 361)
    y = np.linspace(-90, 90, 181)
    xv, yv = np.meshgrid(x, y)
    #print xv
    #print yv

    figure = plt.figure(figsize=(15, 8))
    m = Basemap(llcrnrlon=-180,llcrnrlat=-90,urcrnrlon=180,urcrnrlat=90,projection='cyl', lon_0=0)

    xi, yi = m(xv.flatten(), yv.flatten())

    xi = xi.reshape((181,361))
    yi = yi.reshape((181,361))
    vm = vm.reshape((181,361))
    w = w.reshape((181,361))
    v = v.reshape((181,361))
    h = h.reshape((181,361))

    wn = down_sample(w)
    vn = down_sample(-v)
    wn = wn / np.sqrt(np.square(wn) + np.square(vn))
    vn = vn / np.sqrt(np.square(wn) + np.square(vn))
    cs = m.quiver(down_sample(xi), down_sample(yi), wn, vn, down_sample(vm), width=0.001,
         headlength=7, headwidth=5, pivot='tail', clim=[0, 1.2], cmap='jet')

    m.contour( xi, yi, h, colors ='k', linewidths= 0.3 )
    m.drawparallels(np.arange(-90., 90., 10.), labels=[1,0,0,0], fontsize=10)
    m.drawmeridians(np.arange(-180., 180., 45.), labels=[0,0,0,1], fontsize=10)

    cbar = m.colorbar(cs, location='bottom', pad="10%", label='Velocity (m/s)')
    plt.title("Atmospheric Velocity at {0}Ma".format(time))
    plt.savefig(output_dir + '/{}Ma_velocity.png'.format(time), bbox_inches='tight')
    plt.close()

if  __name__ == "__main__":
    draw_velocity(0, '../benchmark/output/atm_maps/velocity/', '../benchmark/output/')
    draw_velocity(5, '../benchmark/output/atm_maps/velocity/', '../benchmark/output/')


from random import random
import matplotlib.pyplot as plt

class AlgoritmoGenetico():
    def __init__(self, tamanho_populacao):
        self.tamanho_populacao = tamanho_populacao
        self.populacao = []
        self.geracao = 0
        self.melhor_solucao = 0
        self.lista_solucoes = []

    def inicializa_populacao(self, espacos, valores, limite_espacos):
        for i in range(self.tamanho_populacao):
            self.populacao.append(Individuo(espacos, valores, limite_espacos))
        self.melhor_solucao = self.populacao[0]

    def ordena_populacao(self):
        self.populacao = sorted(self.populacao,
                                key = lambda populacao: populacao.nota_avaliacao,
                                reverse = True)

    def melhor_individuo(self, individuo):
        if individuo.nota_avaliacao > self.melhor_solucao.nota_avaliacao:
            self.melhor_solucao = individuo

    def soma_avaliacoes(self):
        soma = 0
        for individuo in self.populacao:
           soma += individuo.nota_avaliacao
        return soma

    def seleciona_pai(self, soma_avaliacao):
        pai = -1
        valor_sorteado = random() * soma_avaliacao
        soma = 0
        i = 0
        while i < len(self.populacao) and soma < valor_sorteado:
            soma += self.populacao[i].nota_avaliacao
            pai += 1
            i += 1
        return pai

    def visualiza_geracao(self):
        melhor = self.populacao[0]
        print("G:%s -> Valor: %s Espaço: %s Cromossomo: %s" % (self.populacao[0].geracao,
                                                               melhor.nota_avaliacao,
                                                               melhor.espaco_usado,
                                                               melhor.cromossomo))

    def resolver(self, taxa_mutacao, numero_geracoes, espacos, valores, limite_espacos):
        self.inicializa_populacao(espacos, valores, limite_espacos)

        for individuo in self.populacao:
            individuo.avaliacao()

        self.ordena_populacao()
        self.melhor_solucao = self.populacao[0]
        self.lista_solucoes.append(self.melhor_solucao.nota_avaliacao)

        self.visualiza_geracao()

        for geracao in range(numero_geracoes):
            soma_avaliacao = self.soma_avaliacoes()
            nova_populacao = []

            for individuos_gerados in range(0, self.tamanho_populacao, 2):
                pai1 = self.seleciona_pai(soma_avaliacao)
                pai2 = self.seleciona_pai(soma_avaliacao)

                filhos = self.populacao[pai1].crossover(self.populacao[pai2])

                nova_populacao.append(filhos[0].mutacao(taxa_mutacao))
                nova_populacao.append(filhos[1].mutacao(taxa_mutacao))

            self.populacao = list(nova_populacao)

            for individuo in self.populacao:
                individuo.avaliacao()

            self.ordena_populacao()

            self.visualiza_geracao()

            melhor = self.populacao[0]
            self.lista_solucoes.append(melhor.nota_avaliacao)
            self.melhor_individuo(melhor)

        print("\nMelhor solução -> G: %s Valor: %s Espaço: %s Cromossomo: %s" %
              (self.melhor_solucao.geracao,
               self.melhor_solucao.nota_avaliacao,
               self.melhor_solucao.espaco_usado,
               self.melhor_solucao.cromossomo))

        return self.melhor_solucao.cromossomo

if __name__ == '__main__':

    limite = 3
    tamanho_populacao = 20
    taxa_mutacao = 0.01
    numero_geracoes = 100
    ag = AlgoritmoGenetico(tamanho_populacao)
    resultado = ag.resolver(taxa_mutacao, numero_geracoes, espacos, valores, limite)
    for i in range(len(lista_produtos)):
        if resultado[i] == '1':
            print("Nome: %s R$ %s " % (lista_produtos[i].nome,
                                       lista_produtos[i].valor))

    #for valor in ag.lista_solucoes:
    #    print(valor)
    plt.plot(ag.lista_solucoes)
    plt.title("Acompanhamento dos valores")
    plt.show()

curso/Algoritmos Genéticos em Python/13.py


import numpy as np
import matplotlib.pyplot as plt
import iris.plot as iplt
from irise import convert, diagnostics, variable
from myscripts.models.um import case_studies
import tropopause

pvtrop = 3.5
pvname = 'ertel_potential_vorticity'
dz = np.linspace(-2000, 2000, 21)

def main(cubes):
    """
    """
    # Calulate N^2
    theta = convert.calc('air_potential_temperature', cubes)
    nsq = variable.N_sq(theta)

    # Find the tropopause
    ztrop, fold_t, fold_b = tropopause.height(cubes)

    # Mask ridges and troughs
    ridges, troughs = tropopause.ridges_troughs(cubes)

    # Create profile of N_sq vs tropopause
    for name, mask in [('troughs', ridges), ('ridges', troughs)]:
        cube = diagnostics.profile(nsq, ztrop, dz, mask=mask)[0]
        iplt.plot(cube, cube.coords()[0], label=name)

    plt.axhline(color='k')
    plt.xlabel(r'$N^2$ $s^{-1}$')
    plt.ylabel('Distance from the tropopause')
    plt.legend(loc='best')
    plt.title('Tropopause PV = %.1f' % pvtrop)
    plt.show()

if __name__ == '__main__':
    forecast = case_studies.iop8.copy()
    cubes = forecast.set_lead_time(hours=24)
    main(cubes)


# https://www.acmicpc.net/problem/7576
# 토마토

from collections import deque

M, N = map(int, input().split()) #col row
board = []
for _ in range(N):
    board.append(list(map(int, input().split())))

def bfs():
    global M, N, board
    queue = deque()

    for i in range(N):
        for j in range(M):
            if board[i][j] == 1:
                queue.append((i,j))

    dr, dc = (1,-1,0,0), (0,0,-1,1)
    while queue:
        r,c = queue.popleft()
        for i in range(4):
            nr, nc = r+dr[i], c+dc[i]
            if nr<0 or nr>=N or nc<0 or nc>=M:
                continue
            if board[nr][nc] == 0:
                queue.append((nr, nc))
                board[nr][nc] = board[r][c] + 1

    answer = 0
    for i in range(N):
        for j in range(M):
            if board[i][j] == 0:
                return -1
            elif board[i][j] > answer:
                answer = board[i][j]
    return answer-1

print(bfs())


""" Forms Motors. """
from datetime import date

from django import forms

class MotorForm(forms.Form):
    """ Formularios para oferta de mobliliarios. """
    FUEL_CHOICES = [
        ('Nafta', 'Nafta'),
        ('Diesel', 'Diesel'),
        ('Alcohol', 'Alcohol'),
        ('Flex', 'Flex'),
        ('Eléctrico', 'Eléctrico'),
    ]

    TRANSMISSION_CHOICES = [
        ('manual', 'Manual'),
        ('automatic', 'Automática'),
    ]

    YEAR_CHOICES = [(year, year)
                    for year in range(1886, date.today().year + 1)]

    # Unico de Motor
    brand = forms.CharField(
        widget=forms.TextInput(attrs={'class': 'form-control'}),
        label='Marca',
        max_length=255,
        min_length=1,
        required=True,
    )
    model = forms.CharField(
        widget=forms.TextInput(attrs={'class': 'form-control'}),
        label='Modelo',
        max_length=255,
        min_length=1,
        required=True,
    )
    fuel = forms.ChoiceField(
        widget=forms.Select(attrs={'class': 'form-control'}),
        label='Combustible',
        choices=FUEL_CHOICES,
        required=True,
    )
    transmission = forms.ChoiceField(
        widget=forms.Select(attrs={'class': 'form-control'}),
        label='Transmisión',
        choices=TRANSMISSION_CHOICES,
        required=True,
    )
    year = forms.ChoiceField(
        widget=forms.Select(attrs={'class': 'form-control'}),
        label='Año',
        required=True,
        choices=YEAR_CHOICES,
        initial=date.today().year,
    )
    color = forms.CharField(
        widget=forms.TextInput(attrs={'class': 'form-control'}),
        label='Color',
        max_length=255,
        min_length=4,
        required=True,
    )

    # Compartido con BaseClass
    title = forms.CharField(
        widget=forms.TextInput(attrs={'class': 'form-control'}),
        label='Título',
        max_length=255,
        min_length=1,
        required=True,
    )

    description = forms.CharField(
        widget=forms.Textarea(attrs={'class': 'form-control'}),
        label='Descripción',
        max_length=255,
        min_length=1,
        required=True,
    )

    location = forms.CharField(
        widget=forms.TextInput(attrs={'class': 'form-control'}),
        label='Ubicación',
        max_length=255,
        min_length=4,
        required=True,
    )

    price = forms.IntegerField(
        widget=forms.TextInput(attrs={'class': 'form-control'}),
        label='Precio',
        min_value=1,
        required=True,
        error_messages={
            'invalid': 'Por favor, ingrese un número válido para el precio.',
        }
    )

    phone1 = forms.CharField(
        widget=forms.TextInput(attrs={'class': 'form-control'}),
        label='Celular 1',
        min_length=8,
        required=True,
        error_messages={
            'invalid': 'Por favor, ingrese un número de contacto válido',
        }
    )

    phone2 = forms.CharField(
        widget=forms.TextInput(attrs={'class': 'form-control'}),
        label='Celular 2',
        min_length=8,
        required=False
    )

    email = forms.EmailField(
        widget=forms.TextInput(attrs={'class': 'form-control'}),
        label='Email',
        error_messages={
            'invalid': 'Por favor, ingrese un email válido.',
        }
    )

    images = forms.ImageField(
    widget=forms.FileInput(attrs={'class': 'form-control'}),
    label='Imagen',
    required=False,
    )


# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.

from __future__ import annotations
import pandas as pd
import numpy as np
from typing import Iterable, Callable, Any

def unpack_column(
    table: pd.DataFrame, column_description: dict, table_name: str
) -> pd.DataFrame:
    """Validates a column in a pandas DataFrame

    Args:
        table (pandas.DataFrame): A table containing the column at the index
            specified by column_description
        column_description (dict): A dictionary with the following supported
            keys:

             - name: the name of the column, which is assigned to the column
                     label to the result table returned by this function
             - index: the zero based index of the column in the table's
                      ordered columns
             - type: (optional) the column will be converted (if necessary to
                     this type) If the conversion is not possible for a value
                     at any row, an error is raised.
             - min_value: (optional) inclusive minimum value constraint.
             - max_value: (optional) inclusive maximum value constraint.

        table_name (str): the name of the table being processed, purely
            for error feedback when an error occurs.

    Raises:
        ValueError: the values in the column were not convertable to the
            specified column description type
        ValueError: a min_value or max_value was specified without specifying
            type in column description
        ValueError: the min_value or max_value constraint was violated by the
            value in the column.

    Returns:
        pandas.DataFrame: the resulting table
    """
    data = table.iloc[:, column_description["index"]].copy()
    col_name = column_description["name"]
    if "type" in column_description:
        if data.isna().any():
            try:
                data = data.astype("object")
                data.loc[data.notna()] = data.loc[data.notna()].astype(
                    column_description["type"]
                )
            except ValueError:
                raise ValueError(
                    f"{table_name} table, column: '{col_name}' contains "
                    "values that cannot be converted to: "
                    f"'{column_description['type']}'"
                )
        else:
            data = data.astype(column_description["type"])
    if "min_value" in column_description:
        if "type" not in column_description:
            raise ValueError("type required with min_value")
        min_value = column_description["min_value"]
        if len(data[data < min_value]):
            raise ValueError(
                f"{table_name} table, column: '{col_name}' contains values "
                f"less than the minimum allowed value: {min_value}"
            )
    if "max_value" in column_description:
        if "type" not in column_description:
            raise ValueError("type required with max_value")
        max_value = column_description["max_value"]
        if len(data[data > max_value]):
            raise ValueError(
                f"{table_name} table, column: '{col_name}' contains values "
                f"greater than the maximum allowed value: {max_value}"
            )
    return data

def _list_duplicates(seq: Iterable) -> list:
    """
    get the list of duplicate values in the specified sequence
    https://stackoverflow.com/questions/9835762/how-do-i-find-the-duplicates-in-a-list-and-create-another-list-with-them

    Args:
        seq (iterable): a sequence of comparable values

    Returns:
        list: the list of values that appear at least 2 times in the input
    """
    seen = set()
    seen_add = seen.add
    # adds all elements it doesn't know yet to seen and all other to seen_twice
    seen_twice = set(x for x in seq if x in seen or seen_add(x))
    # turn the set into a list (as requested)
    return list(seen_twice)

def unpack_table(
    table: pd.DataFrame, column_descriptions: list[dict], table_name: str
) -> pd.DataFrame:
    """Validates and assigns column names to a column-ordered table using
    the specified list of column descriptions. Any existing column labels
    on the specified table are ignored.

    Args:
        table (pandas.DataFrame): a column ordered table to validate
        column_descriptions (list): a list of dictionaries with describing the
            columns. See :py:func:`unpack_column` for how this is used
        table_name (str): the name of the table being processed, purely
            for error feedback when an error occurs.

    Raises:
        ValueError: a duplicate column name was detected

    Returns:
        pandas.DataFrame: a type-validated table with columns replaced with
            the contents of column_descriptions.
    """
    cols = [x["name"] for x in column_descriptions]
    duplicates = _list_duplicates(cols)
    if duplicates:
        # this could potentially happen if a classifier is named the same
        # thing as another column
        raise ValueError(f"duplicate column names detected: {duplicates}")
    data = {
        x["name"]: unpack_column(table, x, table_name)
        for x in column_descriptions
    }
    return pd.DataFrame(columns=cols, data=data)

def _try_get_int(s) -> int:
    """
    Checks if the specified value is an integer, and returns a result

    Args:
        s (any): a value to test

    Returns:
        tuple: (int(s), True) if the value can be converted to an integer,
            and otherwise (None, False)
    """
    try:
        i = int(s)
        return i, True
    except ValueError:
        return None, False

def get_parse_bool_func(
    table_name: str, colname: str
) -> Callable[[Any], bool]:
    """gets a boolean-like value to boolean parse function according to the
    SIT specification.  The parameters are used to create a friendly error
    message when a parse failure occurs.

    Args:
        table_name (str): Table name to be used in failure error message
        colname (str): Column name to be used in failure error message

    Returns:
        func: a boolean-like value to bool parse function
    """

    def parse_bool(x: Any) -> bool:
        """Converts the specified value to a boolean according to SIT
        specification, or raises an error.

        Args:
            x (varies): a value to convert to boolean

        Raises:
            ValueError: The specified value was not convertable to boolean

        Returns:
            boolean: The converted value
        """
        if isinstance(x, bool):
            return x
        elif isinstance(x, int):
            # the sit format treats negatives as False for boolean fields
            return x > 0
        else:
            str_x = str(x).lower()
            int_x, success = _try_get_int(str_x)
            if success:
                return int_x > 0
            if str_x in ["true", "t", "y"]:
                return True
            elif str_x in ["false", "f", "n"]:
                return False
            else:
                raise ValueError(
                    f"{table_name}: cannot parse value: '{x}' in "
                    f"column: '{colname}' as a boolean"
                )

    return parse_bool

def substitute_using_age_class_rows(
    rows: pd.DataFrame,
    parse_bool_func: Callable[[Any], bool],
    age_classes: pd.DataFrame,
) -> pd.DataFrame:
    """Substitute age class criteria values that appear in SIT transition
    rules or disturbance events data into age values.

    Checks that min softwood age equals min hardwood age and max softwood
    age equals max hardwood age since CBM does not carry separate HW/SW ages.

    Args:
        rows (pandas.DataFrame): sit data containing columns that describe age
            eligibility:

            - using_age_class
            - min_softwood_age
            - min_hardwood_age
            - max_softwood_age
            - max_hardwood_age

        parse_bool_func (func): a function that maps boolean-like values to
            boolean.  Passed to the pandas.Series.map function for the
            using_age_class column.
        age_classes (pandas.DataFrame): [description]

    Raises:
        ValueError: values found in the age eligibility columns are not
            defined identifiers in the specified age classes table.
        ValueError: hardwood and softwood age criteria were not identical.

    Returns:
        pandas.DataFrame: the input table with age values criteria substituted
            for age class criteria.
    """

    rows.using_age_class = rows.using_age_class.map(parse_bool_func)
    non_using_age_class_rows = rows.loc[~rows.using_age_class]
    using_age_class_rows = rows.loc[rows.using_age_class].copy()

    for age_class_criteria_col in [
        "min_softwood_age",
        "min_hardwood_age",
        "max_softwood_age",
        "max_hardwood_age",
    ]:
        valid_age_classes = np.concatenate(
            [age_classes.name.unique(), np.array(["-1"])]
        )
        age_class_ids = (
            using_age_class_rows[age_class_criteria_col].astype(str).unique()
        )
        undefined_age_classes = np.setdiff1d(age_class_ids, valid_age_classes)
        if len(undefined_age_classes) > 0:
            raise ValueError(
                f"In column {age_class_criteria_col}, the following age class "
                f"identifiers: {undefined_age_classes} are not defined in SIT "
                "age classes."
            )

    age_class_start_year_map = {
        x.name: int(x.start_year) for x in age_classes.itertuples()
    }
    age_class_end_year_map = {
        x.name: int(x.end_year) for x in age_classes.itertuples()
    }
    using_age_class_rows.min_softwood_age = (
        using_age_class_rows.min_softwood_age.astype(str).map(
            age_class_start_year_map
        )
    )
    using_age_class_rows.min_hardwood_age = (
        using_age_class_rows.min_hardwood_age.astype(str).map(
            age_class_start_year_map
        )
    )
    using_age_class_rows.max_softwood_age = (
        using_age_class_rows.max_softwood_age.astype(str).map(
            age_class_end_year_map
        )
    )
    using_age_class_rows.max_hardwood_age = (
        using_age_class_rows.max_hardwood_age.astype(str).map(
            age_class_end_year_map
        )
    )

    # if the above mapping fails, it results in Nan values in the failed rows,
    # this replaces those with -1
    using_age_class_rows.min_softwood_age = (
        using_age_class_rows.min_softwood_age.fillna(-1)
    )
    using_age_class_rows.min_hardwood_age = (
        using_age_class_rows.min_hardwood_age.fillna(-1)
    )
    using_age_class_rows.max_softwood_age = (
        using_age_class_rows.max_softwood_age.fillna(-1)
    )
    using_age_class_rows.max_hardwood_age = (
        using_age_class_rows.max_hardwood_age.fillna(-1)
    )

    # return the final substituted rows
    result = pd.concat(
        [non_using_age_class_rows, using_age_class_rows]
    ).reset_index(drop=True)

    # convert to float then to int in case the columns are stored as
    # strings in float format (which fails on astype(int))
    result.min_softwood_age = result.min_softwood_age.astype(float).astype(int)
    result.min_hardwood_age = result.min_hardwood_age.astype(float).astype(int)
    result.max_softwood_age = result.max_softwood_age.astype(float).astype(int)
    result.max_hardwood_age = result.max_hardwood_age.astype(float).astype(int)

    # check that all age criteria are identical between SW and HW (since CBM
    # has only a stand age)
    has_null_min_age_criteria = (result.min_softwood_age < 0) | (
        result.min_hardwood_age < 0
    )
    has_null_max_age_criteria = (result.max_softwood_age < 0) | (
        result.max_hardwood_age < 0
    )
    differing_age_criteria = result.loc[
        (
            (result.min_softwood_age != result.min_hardwood_age)
            & ~has_null_min_age_criteria
        )
        | (
            (result.max_softwood_age != result.max_hardwood_age)
            & ~has_null_max_age_criteria
        )
    ]
    if len(differing_age_criteria) > 0:
        raise ValueError(
            "Values of column min_softwood_age must equal values of column "
            "min_hardwood_age, and values of column max_softwood_age must "
            "equal values of column max_hardwood_age since CBM defines only "
            "a stand age and does not track hardwood and softwood age "
            "seperately."
        )

    return result


import requests
import re
import datetime
from dateutil import parser
import time
from PIL import Image, ImageDraw, ImageFont
import urllib.parse

from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request
import os
import pickle
import codecs

HEADER = {'User-Agent': 'Live Match Results Ticker (beomulf@gmail.com)'}

S = requests.Session()

URL = "https://liquipedia.net/starcraft2/api.php"
HEADER = {'User-Agent': 'Live Match Results Ticker (beomulf@gmail.com)'}
TIMEZONES = {'CEST': '+02:00', 'EDT': '-04:00'}
RACELIBRARY = {'T': 'Terran', 'P': 'Protoss', 'Z': 'Zerg', 'R': 'Random'}

def build_ticker_DH_NA_groups(pageid, prepend=''):
    params = {
        'action': "parse",
        'pageid': pageid,
        'prop': 'wikitext',
        'section': 6,
        'format': "json"
    }
    """ Parse a section of a page, fetch its table data and save it to a CSV file
    """
    res = S.get(url=URL, params=params, headers=HEADER)
    data = res.json()
    wikitext = data['parse']['wikitext']['*']
    lines = wikitext.split('|-')
    matches = {}

    table = lines[0].split('{{HiddenSort')
    del table[0]
    for group in table:
        match_list = re.split('\|M[0-9]|<', group)
        group_name = group[1:8] + ' | '
        del match_list[0]
        player_names = re.split('\|p[0-9]=', group)
        del player_names[0]
        player_names_list = [x.split('|')[0].split('=') for x in player_names[0:8]]
        player_names_list = [x[0] for x in player_names_list]
        results_dict = dict.fromkeys(player_names_list)
        for key in player_names_list:
            results_dict[key] = {'Map Diff': 0, 'Map Wins': 0, 'Map Losses': 0, 'Match Wins': 0, 'Match Losses': 0}

        matches[group_name] = []
        for match in match_list:
            if 'opponent1' in match:
                if 'bestof=5' in match:
                    break
                subseries = match.split('    ')
                dateinfo = match.split('date=')[1].split('|')[0]
                date = dateinfo.split('{{')
                date = date[0] + re.sub('[a-z |{|}|\/]', '', date[1]).replace('\n', '')
                for zone, offset in TIMEZONES.items():
                    try:
                        date = date.replace(zone, offset)
                    except:
                        print('Invalid Timezone')

                date = parser.parse(date)
                currentTime = datetime.datetime.now(datetime.timezone.utc).astimezone()
                timeDiff = abs(currentTime-date)
                timeDiff = timeDiff.days * 24 + timeDiff.seconds // 3600
                manual_score_flag = False

                p1_score = 0
                p2_score = 0
                for line in subseries:
                    if 'opponent1' in line:
                        if re.search('\|1=', line):
                            p1 = line.split('|1=')[-1].split('|')[0].replace('}}\n', '')
                        else:
                            p1 = line.split('|')[2].replace('}}\n', '').split('p1=')[0]
                        if 'score' in line:
                            p1_score = line.split('score=')[1].split('}')[0]
                        else:
                            manual_score_flag = True
                    elif 'opponent2' in line:
                        if re.search('\|1=', line):
                            p2 = line.split('|1=')[-1].split('|')[0].replace('}}\n', '')
                        else:
                            p2 = line.split('|')[2].replace('}}\n', '').split('p1=')[0]
                        if 'score' in line:
                            p2_score = line.split('score=')[1].split('}')[0]
                        else:
                            manual_score_flag = True
                    if 'walkover=1' in line:
                        p1_score = 'W'
                        p2_score = 'L'
                    elif 'walkover=2' in line:
                        p1_score = 'L'
                        p2_score = 'W'
                    elif manual_score_flag and 'winner' in line:
                        winner_id = line.split('winner=')[1].split('}')[0].partition('|')
                        winner_id = winner_id[0]
                        if winner_id == '1':
                            p1_score += 1
                        elif winner_id == '2':
                            p2_score += 1

                if p1_score == '':
                    p1_score = '0'
                if p2_score == '':
                    p2_score = '0'
                if p1 not in results_dict:
                    results_dict[p1] = {'Map Diff': 0, 'Map Wins': 0, 'Map Losses': 0, 'Match Wins': 0,
                                         'Match Losses': 0}
                if p2 not in results_dict:
                    results_dict[p2] = {'Map Diff': 0, 'Map Wins': 0, 'Map Losses': 0, 'Match Wins': 0,
                                         'Match Losses': 0}
                results_dict[p1]['Map Wins'] += p1_score
                results_dict[p2]['Map Wins'] += p2_score
                results_dict[p1]['Map Losses'] += p2_score
                results_dict[p2]['Map Losses'] += p1_score
                results_dict[p1]['Map Diff'] += p1_score - p2_score
                results_dict[p2]['Map Diff'] += p2_score - p1_score
                if p1_score >= 2 or p2_score >= 2:
                    if p1_score > p2_score:
                        results_dict[p1]['Match Wins'] += 1
                        results_dict[p2]['Match Losses'] += 1
                    if p2_score > p1_score:
                        results_dict[p2]['Match Wins'] += 1
                        results_dict[p1]['Match Losses'] += 1

                if timeDiff < 10:
                    if p1 != '':
                        if p1 != 'BYE' and p2 != 'BYE':
                            matches[group_name].append(
                                ' ' + p1 + ' ' + str(p1_score) + '-' + str(p2_score) + ' ' + p2 + '    ')
                        elif p1 == 'BYE':
                            matches[group_name].append(' ' + p2 + ' (Bye) ')
                        elif p2 == 'BYE':
                            matches[group_name].append(' ' + p1 + ' (Bye) ')
                    if p1 == '':
                        p1 = 'TBD'
                    if p2 == '':
                        p2 = 'TBD'

        generate_group_standings_img(group_name, results_dict)

    matchlist = []
    for key in matches.keys():
        if matches[key] != []:
            if prepend != '':
                matchlist.append('  |  ' + prepend + '  |  ')

            matchlist.append(key + ''.join(matches[key]))

    matchstr = ''.join(matchlist)
    with open('results.txt', 'w') as output:
        output.write(matchstr)
    print('Populated Results')
    time.sleep(40)

def build_ticker_ept_cups(pageid, prepend=''):
    # TODO: Generalize function to all events
    # TODO: convert match_list to dictionary so we can bump less useful results
    # TODO: add gui to plug in pageid
    # TODO: loop every 30s to reduce user overhead
    params = {
        'action': "parse",
        'pageid': pageid,
        'prop': 'wikitext',
        'format': "json",
        'Accept-Encoding': 'gzip'
    }

    """ Parse a section of a page, fetch its table data and save it to a CSV file
    """
    try:
        res = S.get(url=URL, params=params, headers=HEADER)
        data = res.json()
        wikitext = data['parse']['wikitext']['*']
        lines = wikitext.split('|-')
        matches = []
        rounds = {}

        table = lines[0].split('==Results==')
        del table[0]
        table = re.split('\|R[0-9]', table[0])
        prev_series = 100
        round_tracker = 0

        for series in table:
            if '{{bracket' not in series:
                if 'header' in series:
                    header = series.split('=')[1].split('({{')
                    rounds[str(header[0])] = header[1].split('|')[1].replace('}})\n', '').split('\n')[0]
                elif re.search('M[0-9]', series):
                    round_keys = list(rounds.keys())
                    subseries = series.split('    ')
                    series_num = int(subseries[0].split('M')[1].split('=')[0])
                    manual_score_flag = False
                    p1_score = 0
                    p2_score = 0
                    for line in subseries:
                        if 'opponent1' in line:
                            if re.search('\|1=', line):
                                p1 = line.split('|1=')[-1].split('|')[0].replace('}}\n', '')
                            else:
                                p1 = line.split('|')[2].replace('}}\n', '')
                            if 'score' in line:
                                p1_score = line.split('score=')[1].split('}')[0]
                            else:
                                manual_score_flag = True
                        elif 'opponent2' in line:
                            if re.search('\|1=', line):
                                p2 = line.split('|1=')[-1].split('|')[0].replace('}}\n', '')
                            else:
                                p2 = line.split('|')[2].replace('}}\n', '')
                            if 'score' in line:
                                p2_score = line.split('score=')[1].split('}')[0]
                            else:
                                manual_score_flag = True
                        if 'walkover=1' in line:
                            p1_score = 'W'
                            p2_score = 'L'
                        elif 'walkover=2' in line:
                            p1_score = 'L'
                            p2_score = 'W'
                        elif manual_score_flag and 'winner' in line:
                            winner_id = line.split('winner=')[1].split('}')[0].partition('|')
                            winner_id = winner_id[0]
                            if winner_id == '1':
                                p1_score += 1
                            elif winner_id == '2':
                                p2_score += 1
                    if series_num < prev_series and p1 != '':
                        key = str(round_keys[round_tracker])
                        matches.append('| ' + key + ' (BO ' + rounds[key] + ') :')
                        round_tracker += 1
                    if p1_score == '':
                        p1_score = '0'
                    if p2_score == '':
                        p2_score = '0'
                    if p1 == '' and p2 != '':
                        p1 = 'TBD'
                    if p2 == '' and p1 != '':
                        p2 = 'TBD'
                    if p1 != '':
                        if p1 != 'BYE' and p2 != 'BYE':
                            matches.append(' ' + p1 + ' ' + str(p1_score) + '-' + str(p2_score) + ' ' + p2 + '    ')
                        elif p1 == 'BYE':
                            matches.append(' ' + p2 + ' (Bye) ')
                        elif p2 == 'BYE':
                            matches.append(' ' + p1 + ' (Bye) ')

                    prev_series = series_num
        matchstr = ' '.join(matches)
        if 'Quarterfinals  (BO 3)' in matchstr:
            matchstr = matchstr[matchstr.index('| Quarterfinals  (BO 3)'):]
    except:
        print('Invalid ID')
        matchstr = ''
    if prepend != '':
        prepend = '  |  ' + prepend
    matchstr = prepend + matchstr

    while len(matchstr) < 100 and len(matchstr) != 0:
        matchstr += matchstr

    with codecs.open('results.txt', 'w', encoding="utf-8") as output:
        output.write(matchstr)
        print('Populated Results')

    time.sleep(40)

def build_ticker_DH_EU_groups(pageid, prepend=''):
    # TODO: Generalize function to all events
    # TODO: convert match_list to dictionary so we can bump less useful results
    # TODO: convert match_list to dictionary so we can bump less useful results
    # TODO: add gui to plug in pageid
    # TODO: loop every 30s to reduce user overhead
    params = {
        'action': "parse",
        'pageid': pageid,
        'prop': 'wikitext',
        'format': "json",
        'Accept-Encoding': 'gzip'
    }

    """ Parse a section of a page, fetch its table data and save it to a CSV file
    """
    res = S.get(url=URL, params=params, headers=HEADER)
    data = res.json()
    wikitext = data['parse']['wikitext']['*']
    lines = wikitext.split('|-')
    matches = []
    rounds = {}

    lines = lines[-1]
    lines = lines.split('Toggle group')[1]
    group_table = lines.split('{{:')
    new_groups = group_table[0].split('{{Matchlist')
    group_names = [x.split('}')[0] for x in new_groups[1:len(new_groups)]]

    prev_series = 100
    round_tracker = 0
    matches = {}
    for group in group_names:
        print(group)
        time.sleep(40)
        HEADER_GROUPS = {'User-Agent': f'Live Match Results Ticker {group} (beomulf@gmail.com)'}

        group_params = {
            'action': "parse",
            'page': group,
            'prop': 'wikitext',
            'format': "json",
            'Accept-Encoding': 'gzip'
        }
        res = S.get(url=URL, params=group_params, headers=HEADER_GROUPS)
        data = res.json()
        wikitext = data['parse']['wikitext']['*']
        lines = wikitext.split('|-')
        group_data = lines[-1]
        player_names = re.split('\|p[0-9]=', group_data)
        del player_names[0]
        # if 'bg' in group:
        #     player_names = re.split('\|p[0-9]=', group_data)
        player_names_list = [x.split('\n')[0].split('=') for x in player_names[0:8]]
        player_names_list = [x[0] for x in player_names_list]
        results_dict = dict.fromkeys(player_names_list)
        for key in player_names_list:
            results_dict[key] = {'Map Diff': 0, 'Map Wins': 0, 'Map Losses': 0, 'Match Wins': 0, 'Match Losses': 0}

        table = re.split('\|M[0-9]|<', group_data)
        for match in table:
            if '{{HiddenSort' in match:
                group_name = match.split('|')[1].split('}}')[0] + ' | '
                matches[group_name] = []
            elif 'header' not in match and 'opponent1' in match:
                if 'bestof=5' in match:
                    break
                date_info = match.split('|')
                date = [x for x in date_info if 'date' in x]
                date = date[0].split('=')[1].split('{{')
                date = date[0] + re.sub('[a-z |{|}|\/]', '', date[1]).replace('\n', '')
                date = date.replace('A', '')
                for zone, offset in TIMEZONES.items():
                    try:
                        date = date.replace(zone, offset)
                    except:
                        print('Invalid Timezone')

                date = parser.parse(date)
                currentTime = datetime.datetime.now(datetime.timezone.utc).astimezone()
                timeDiff = abs(currentTime - date)
                timeDiff = timeDiff.days * 24 + timeDiff.seconds // 3600

                subseries = match.split('    ')
                manual_score_flag = False
                p1_score = 0
                p2_score = 0

                for line in subseries:
                    if 'opponent1' in line:
                        if re.search('\|1=', line):
                            p1 = line.split('|1=')[-1].split('|')[0].replace('}}\n', '')
                        else:
                            p1 = line.split('|')[2].replace('}}\n', '').split('p1=')[1]
                        if 'score' in line:
                            p1_score = line.split('score=')[1].split('}')[0]
                        else:
                            manual_score_flag = True
                    elif 'opponent2' in line:
                        if re.search('\|1=', line):
                            p2 = line.split('|1=')[-1].split('|')[0].replace('}}\n', '')
                        else:
                            p2 = line.split('|')[2].replace('}}\n', '').split('p1=')[1]
                        if 'score' in line:
                            p2_score = line.split('score=')[1].split('}')[0]
                        else:
                            manual_score_flag = True
                    if 'walkover=1' in line:
                        p1_score = 'W'
                        p2_score = 'L'
                    elif 'walkover=2' in line:
                        p1_score = 'L'
                        p2_score = 'W'
                    elif manual_score_flag and 'winner' in line:
                        winner_id = line.split('winner=')[1].split('}')[0].partition('|')
                        winner_id = winner_id[0]
                        if winner_id == '1':
                            p1_score += 1
                        elif winner_id == '2':
                            p2_score += 1

                if p1_score == '':
                    p1_score = '0'
                if p2_score == '':
                    p2_score = '0'
                results_dict[p1]['Map Wins'] += p1_score
                results_dict[p2]['Map Wins'] += p2_score
                results_dict[p1]['Map Losses'] += p2_score
                results_dict[p2]['Map Losses'] += p1_score
                results_dict[p1]['Map Diff'] += p1_score-p2_score
                results_dict[p2]['Map Diff'] += p2_score-p1_score
                if p1_score >= 2 or p2_score >= 2:
                    if p1_score > p2_score:
                        results_dict[p1]['Match Wins'] += 1
                        results_dict[p2]['Match Losses'] += 1
                    if p2_score > p1_score:
                        results_dict[p2]['Match Wins'] += 1
                        results_dict[p1]['Match Losses'] += 1

                if timeDiff < 8:
                    if p1 != '':
                        if p1 != 'BYE' and p2 != 'BYE':
                            matches[group_name].append(' ' + p1 + ' ' + str(p1_score) + '-' + str(p2_score) + ' ' + p2 + '    ')
                        elif p1 == 'BYE':
                            matches[group_name].append(' ' + p2 + ' (Bye) ')
                        elif p2 == 'BYE':
                            matches[group_name].append(' ' + p1 + ' (Bye) ')
                    if p1 == '':
                        p1 = 'TBD'
                    if p2 == '':
                        p2 = 'TBD'

        generate_group_standings_img(group_name, results_dict)

    matchstr = ''
    for key in matches.keys():
        if matches[key] != []:
            matchstr += key + ''.join(matches[key])
    if prepend != '':
        matchstr = '  |  ' + prepend + '  |  ' + matchstr

    with open('results.txt', 'w') as output:
        output.write(matchstr)
    print('Populated Results')

def build_kob_ticker(mainstream_group='', offstream_group=''):
    SCOPES = ['https://www.googleapis.com/auth/spreadsheets']
    SPREADSHEET_ID = '1TX2a7CHmrJaaNvytF_iVUGPAD9ALnRhIJXrVjJelTDk'
    creds = None

    if os.path.exists('token.pickle'):
        with open('token.pickle', 'rb') as token:
            creds = pickle.load(token)
    if not creds or not creds.valid:
        if creds and creds.expired and creds.refresh_token:
            creds.refresh(Request())
        else:
            flow = InstalledAppFlow.from_client_secrets_file(
                '../KoB_Toolsuite/credentials.json', SCOPES) # here enter the name of your downloaded JSON file
            creds = flow.run_local_server(port=8080)
        with open('token.pickle', 'wb') as token:
            pickle.dump(creds, token)

    service = build('sheets', 'v4', credentials=creds)

    # Call the Sheets API
    sheet = service.spreadsheets()
    result_input = sheet.get(spreadsheetId=SPREADSHEET_ID).execute()
    values_input = result_input.get('values', [])
    sheets = result_input.get('sheets', '')
    sheet_names = [x.get("properties", {}).get("title") for x in sheets]
    if mainstream_group == '':
        return sheet_names

def generate_group_standings_img(group_name, results_dict):
    img = Image.new('RGBA', (1920, 1080), color=(0, 0, 0, 0))
    img2 = Image.new('RGBA', (1920, 1080), color=(0, 0, 0, 0))
    fnt = ImageFont.truetype('Roboto-Bold.ttf', size=30)
    d = ImageDraw.Draw(img)
    d2 = ImageDraw.Draw(img2)

    ordered_results = sorted(results_dict, key=lambda x: (results_dict[x]['Match Wins'] - results_dict[x]['Match Losses'],
                                                          results_dict[x]['Map Diff'],
                                                          results_dict[x]['Map Wins']))
    # Player Names
    d.text((1260, 370), ordered_results[7], font=fnt, fill=(255, 255, 255))
    d.text((1260, 415), ordered_results[6], font=fnt, fill=(255, 255, 255))
    d.text((1260, 457), ordered_results[5], font=fnt, fill=(255, 255, 255))
    d.text((1260, 501), ordered_results[4], font=fnt, fill=(255, 255, 255))
    d.text((1260, 545), ordered_results[3], font=fnt, fill=(255, 255, 255))
    d.text((1260, 590), ordered_results[2], font=fnt, fill=(255, 255, 255))
    d.text((1260, 635), ordered_results[1], font=fnt, fill=(255, 255, 255))
    d.text((1260, 675), ordered_results[0], font=fnt, fill=(255, 255, 255))

    # Match Score
    d.text((1620, 375),
           str(results_dict[ordered_results[7]]['Match Wins']) + ' - ' + str(
               results_dict[ordered_results[7]]['Match Losses']),
           font=fnt, fill=(255, 255, 255), anchor='mt')
    d.text((1620, 420),
           str(results_dict[ordered_results[6]]['Match Wins']) + ' - ' + str(
               results_dict[ordered_results[6]]['Match Losses']),
           font=fnt, fill=(255, 255, 255), anchor='mt')
    d.text((1620, 464),
           str(results_dict[ordered_results[5]]['Match Wins']) + ' - ' + str(
               results_dict[ordered_results[5]]['Match Losses']),
           font=fnt, fill=(255, 255, 255), anchor='mt')
    d.text((1620, 509),
           str(results_dict[ordered_results[4]]['Match Wins']) + ' - ' + str(
               results_dict[ordered_results[4]]['Match Losses']),
           font=fnt, fill=(255, 255, 255), anchor='mt')
    d.text((1620, 553),
           str(results_dict[ordered_results[3]]['Match Wins']) + ' - ' + str(
               results_dict[ordered_results[3]]['Match Losses']),
           font=fnt, fill=(255, 255, 255), anchor='mt')
    d.text((1620, 597),
           str(results_dict[ordered_results[2]]['Match Wins']) + ' - ' + str(
               results_dict[ordered_results[2]]['Match Losses']),
           font=fnt, fill=(255, 255, 255), anchor='mt')
    d.text((1620, 640),
           str(results_dict[ordered_results[1]]['Match Wins']) + ' - ' + str(
               results_dict[ordered_results[1]]['Match Losses']),
           font=fnt, fill=(255, 255, 255), anchor='mt')
    d.text((1620, 683),
           str(results_dict[ordered_results[0]]['Match Wins']) + ' - ' + str(
               results_dict[ordered_results[0]]['Match Losses']),
           font=fnt, fill=(255, 255, 255), anchor='mt')

    # Map Scores
    d.text((1820, 375),
           str(results_dict[ordered_results[7]]['Map Wins']) + ' - ' + str(
               results_dict[ordered_results[7]]['Map Losses']),
           font=fnt, fill=(255, 255, 255), anchor='mt')
    d.text((1820, 420),
           str(results_dict[ordered_results[6]]['Map Wins']) + ' - ' + str(
               results_dict[ordered_results[6]]['Map Losses']),
           font=fnt, fill=(255, 255, 255), anchor='mt')
    d.text((1820, 464),
           str(results_dict[ordered_results[5]]['Map Wins']) + ' - ' + str(
               results_dict[ordered_results[5]]['Map Losses']),
           font=fnt, fill=(255, 255, 255), anchor='mt')
    d.text((1820, 509),
           str(results_dict[ordered_results[4]]['Map Wins']) + ' - ' + str(
               results_dict[ordered_results[4]]['Map Losses']),
           font=fnt, fill=(255, 255, 255), anchor='mt')
    d.text((1820, 553),
           str(results_dict[ordered_results[3]]['Map Wins']) + ' - ' + str(
               results_dict[ordered_results[3]]['Map Losses']),
           font=fnt, fill=(255, 255, 255), anchor='mt')
    d.text((1820, 597),
           str(results_dict[ordered_results[2]]['Map Wins']) + ' - ' + str(
               results_dict[ordered_results[2]]['Map Losses']),
           font=fnt, fill=(255, 255, 255), anchor='mt')
    d.text((1820, 640),
           str(results_dict[ordered_results[1]]['Map Wins']) + ' - ' + str(
               results_dict[ordered_results[1]]['Map Losses']),
           font=fnt, fill=(255, 255, 255), anchor='mt')
    d.text((1820, 683),
           str(results_dict[ordered_results[0]]['Map Wins']) + ' - ' + str(
               results_dict[ordered_results[0]]['Map Losses']),
           font=fnt, fill=(255, 255, 255), anchor='mt')

    img.save(group_name.split('|')[0].replace(' ', '') + '.png')

    fullscreen_start = 300
    score_start = 640
    maps_start = 815
    # Player Names
    name_vert_start = 730
    num_vert_start = 735
    counter = 0
    for result in ordered_results:
        d2.text((fullscreen_start, name_vert_start-counter*50), result, font=fnt, fill=(255, 255, 255))
        d2.text((score_start, num_vert_start-counter*50),
                str(results_dict[result]['Match Wins']) + ' - ' + str(results_dict[result]['Match Losses']),
                font=fnt,
                fill=(255, 255, 255),
                anchor='mt')
        d2.text((maps_start, num_vert_start-counter*50),
                str(results_dict[result]['Map Wins']) + ' - ' + str(results_dict[result]['Map Losses']),
                font=fnt,
                fill=(255, 255, 255),
                anchor='mt')
        counter += 1

    img2.save(group_name.split('|')[0].replace(' ', '') + '_FullScreen.png')


from enum import Enum
from typing import Optional

from discord.ext import commands

from .utils import ASPECT_RATIO_ORIGINAL

class ResizeFlagDescriptions(Enum):
    height = "Flag to specify height."
    width = "Flag to specify width."
    aspect_ratio = f"Flag to specify width:height aspect ratio when resizing. \
Pass either of `{', '.join(ASPECT_RATIO_ORIGINAL)}` to retain the original aspect ratio of file(s). \
If either height/width flag is passed, it will resized based on it, but will not work if both are passed. \
If neither is specified, it will use the original width to resize the height."
    fit = f"Flag `(yes/true)` to specify if you want the bot to fit the image to the edges by cropping away transparent surrounding areas."
    center = f"Flag `(yes/true)` to specify if you want to resize image(s)' background while keeping the image centered and unwarped."
    crop = f"Flag `(yes/true)` to specify if you want the bot to crop your image when resizing."

class ResizeFlags(
    commands.FlagConverter, prefix="--", delimiter=" ", case_insensitive=True
):
    height: Optional[int] = commands.flag(
        aliases=("h",), max_args=1, description=ResizeFlagDescriptions.height.value
    )
    width: Optional[int] = commands.flag(
        aliases=("w",), max_args=1, description=ResizeFlagDescriptions.width.value
    )
    ar: Optional[str] = commands.flag(
        name="aspect_ratio",
        aliases=("ar",),
        max_args=1,
        description=ResizeFlagDescriptions.aspect_ratio.value,
    )
    fit: Optional[bool] = commands.flag(
        name="fit", max_args=1, description=ResizeFlagDescriptions.fit.value
    )
    center: Optional[bool] = commands.flag(
        name="center",
        aliases=("centre",),
        max_args=1,
        description=ResizeFlagDescriptions.center.value,
    )
    crop: Optional[bool] = commands.flag(
        name="crop", max_args=1, description=ResizeFlagDescriptions.crop.value
    )


# -*- coding: utf-8 -*-
"""
Created on Sun Feb 14 18:50:22 2021

@author: Cillian
"""

import numpy as np
from pyDOE import lhs

def get_initial_pts(parameter_samples, parameter_ranges, criteria='center' ):
    """Get initial Latin Hypercube sample points and scale

    Args:
        parameter_samples (str): Number of samples required

        parameter_ranges (list of lists): Each inner list contains the range of
         a parameter [upper bound, lower bound]

        criteria (str, optional): Latin Hypercube sampling method
    """

    # Number of parameters
    n = len(parameter_ranges)

    # Get LHS Samples
    lhs_pts = lhs(n, samples=parameter_samples, criterion=criteria)

    # Scale parameters to required ranges
    for i,j in enumerate(parameter_ranges):
        lhs_pts[:,i] = lhs_pts[:,i]*(j[0] - j[1]) + j[1]

    return( lhs_pts )

def get_grid_points(M):
    """Discretise input domain into a grid
    TOOD: Make more generic. Currently only works for specific Grey-Scott bounds

    Args:

        M (int): Number of equally spaced points to use for each input parameter

    Returns:
        np array with shape (M, num_parameters )

    """

    Grid_pts = np.empty((M**4,4))
    i = 0
    for DA in np.linspace(0.002,0.01,M) :
        for DB in np.linspace(0.0001,0.001,M):
            for k in np.linspace(0.01,0.1,M):
                for f in np.linspace(0.1,0.2,M):
                    Grid_pts[i,:] = np.array([DA, DB, k ,f])
                    i += 1
    return(Grid_pts  )

def obtain_samples_GS(M, p_range = [[0.01,0.002], [0.001,0.0001],[0.1,0.01],[0.2,0.1]]):
    """Sample four input parameters from uniform distributions

    Args:
        M (int): Number of samples required

        p_range (list of lists): Each inner list contains the range of
         a parameter [upper bound, lower bound]

    Returns:
        np array with shape (M, num_parameters )

    """

    DA =  np.random.uniform(p_range[0][1],p_range[0][0],M)
    DB =  np.random.uniform(p_range[1][1],p_range[1][0],M)
    k =  np.random.uniform(p_range[2][1],p_range[2][0],M)
    f = np.random.uniform(p_range[3][1],p_range[3][0],M)

    return np.array([DA, DB, k,f])


# Title: 3Sum
# Link: https://leetcode.com/problems/3sum/

from itertools import combinations
from collections import defaultdict

class Solution:
    def three_sum(self, nums: list) -> list:
        ans = set()
        ans_dict = set()

        d = defaultdict(lambda: 0)
        for n in nums:
            d[n] += 1

        # for all diffeerent
        x = sorted(d.keys())
        for a, b in combinations(x, 2):
            if (a, b) in ans_dict:
                continue
            d[a] -= 1
            d[b] -= 1
            if d[-a-b]:
                s = sorted([a, b, -a-b])
                ans.add(tuple(s))
                ans_dict.add((s[0], s[1]))
                ans_dict.add((s[0], s[2]))
                ans_dict.add((s[1], s[2]))
            d[a] += 1
            d[b] += 1

        # for two same
        for a in x:
            if d[a] >= 2:
                if a > 0:
                    t = (-a-a, a, a)
                else:
                    t = (a, a, -a-a)

                if t not in ans:
                    d[a] -= 2
                    if d[-a-a] > 0:
                        ans.add(t)

        return ans

def main():
    solution = Solution()
    nums = [-1, 0, 1, 2, -1, -4]
    print(solution.three_sum(nums))

if __name__ == '__main__':
    main()