import logging
import time

import matplotlib.pyplot as plt
import numpy as np

from models.agents.DQNAgent import DQNAgent
from models.agents.RandAgents import RandomAgent, CheckAgent, RandomRaiseOrCheckAgent
from models.controllers.controller import Controller
from models.deck import Deck

class Kuhn3P8CController(Controller):
    """
    Kuhn Poker with 3 players and 8 cards.
    No blind, fixed ante, fixed bet size.

    Information/state vector will be of form:
    [private card][betting state][active players]
    with size:
    [num cards (8)][num players (3)][num players (3)]
    """

    def __init__(self, p0, p1, p2):
        super(Kuhn3P8CController, self).__init__()
        cards = Deck().cards[:8]  # Limiting to 8 cards for now
        self.cards = self.shuffle(cards)
        self.players = [p0, p1, p2]

    def play(self, num_games):
        # region Helper routines
        def betting():
            while sum(active_players) > 1:
                # While more than one player is present
                logging.debug('Betting State: ' + str(betting_state))
                logging.debug('Active Players: ' + str(active_players))

                # previous_bet_state = betting_state.copy()
                history = []

                for _ in range(len(active_players)):
                    i = (_ + offset) % len(active_players)  # Rotate first player

                    # If current player is not an active player, move to next player
                    if active_players[i] == 0:
                        logging.debug('Player ' + str(i) + ' nonactive; skipping.')
                        continue

                    current_player = self.players[i]
                    # Generate information state to be passed to agent
                    information_state = np.concatenate((card_states[i], betting_state, active_players))
                    choice = current_player.choose(information_state)
                    logging.debug('Player ' + str(i) + ' choice is ' + self.choice_map[choice])
                    history.append(choice)

                    # Pseudo-switch statement
                    bet_amount = {
                        0: max(betting_state),  # Check matches current max bet
                        1: max(betting_state) + 1,  # Raise adds one to pool
                        2: betting_state[i]  # Fold does not alter current betting state
                    }[choice]
                    # Update bets
                    betting_state[i] = bet_amount

                    # Remove active player if player chooses to fold
                    if choice == 2:
                        active_players[i] = 0

                    # Break out if two passes and only one player remains
                    if sum(active_players) == 1:
                        break

                # Break out if all players check; check = 0, so if all players check, the sum will be 0
                if sum(history[-sum(active_players):]) == 0:
                    break

        def showdown():
            # If only one player (i.e. everyone else folded) then just return position of that player
            if sum(active_players) == 1:
                return active_players.index(1)

            # List of tuples (player number, active or not, player card
            cards = [(i, active_players[i], self.cards[i]) for i in range(len(active_players))]
            for player in cards:
                # Remove nonactive players from consideration
                if player[1] == 0:
                    cards.remove(player)

            for ind, _, card in cards:
                logging.debug(str(ind) + ' ' + str(card))

            cards.sort(key=lambda x: x[2], reverse=True)  # Sort by card value
            logging.debug('Winner: ' + str(cards[0]))
            return cards[0][0]  # Index of winner

        def reward_assignment(winner_index):
            reward = sum(betting_state)
            winning_player = self.players[winner_index]

            # Final reward is total pot minus bet by winning player
            final_reward = reward - betting_state[winner_index]
            logging.debug('Giving reward of ' + str(final_reward) + ' to player ' + str(winner_index))
            winning_player.reward(final_reward)

            # Remove bets from losing players
            for i, player in enumerate(self.players):
                if player == winning_player:
                    # No penalty to winning player
                    continue

                logging.debug('Removing reward of ' + str(betting_state[i]) + ' from player ' + str(i))
                player.reward(-betting_state[i])
        # endregion

        start = time.time()
        offset = 0  # For rotating starting player
        for game in range(num_games):
            # Progress checking
            if (game + 1) % 100 == 0:
                print('Game', game + 1, 'of', num_games, ':', Controller.time_since(start))
            # Initialize player card states
            # Array where personal card will be set to 1, other cards set to 0; one-hot
            p0_state = np.zeros(len(self.cards))
            p1_state = np.zeros(len(self.cards))
            p2_state = np.zeros(len(self.cards))

            # Initialize ante and whether players are still betting
            betting_state = [1, 1, 1]
            active_players = [1, 1, 1]
            # Shuffle and assign based upon index value; e.g. p0 is given card 0, p1 is given card 1, p2 is given card 2
            self.cards = Controller.shuffle(self.cards)
            p0_state[self.cards[0].ordinal] = 1
            p1_state[self.cards[1].ordinal] = 1
            p2_state[self.cards[2].ordinal] = 1
            card_states = [p0_state, p1_state, p2_state]
            betting()
            winner = showdown()
            reward_assignment(winner)
            offset += 1
            # TODO Dump checkpoint of models, especially when start doing more deep learning ones

    def plot_reward_comparison(self, names, loc=''):
        for i in range(3):
            name = loc + 'Player ' + str(i) + ': (' + names[i] + ')'
            plt.plot(self.players[i].cum_rewards, label=name)
        plt.legend()
        plt.xlabel('Number of Games')
        plt.ylabel('Cumulative Reward')
        plt.title('3 Player Kuhn Poker: Fixed Bet, Unlimited Rounds')
        plt.savefig('kuhn-3P.png', dpi=1000, format='png')
        plt.clf()

if __name__ == '__main__':
    # logging.basicConfig(level=logging.DEBUG)
    a = CheckAgent()
    b = RandomRaiseOrCheckAgent()
    c = DQNAgent()
    kuhn = Kuhn3P8CController(a, b, c)
    kuhn.play(1000)
    kuhn.plot_reward_comparison(['CheckAgent', 'RandRaiseCheckAgent', 'DQNAgent'])


# -*- coding: utf-8 -*-
from openerp import models, fields, api

class EstagioResolucaoOcorrencia(models.Model):
    _name = 'estagio_resolucao_ocorrencia'
    _description = 'Estágios de resolução das ocorrências'
    _inherit = ['mail.thread', 'ir.needaction_mixin']

    name = fields.Many2one(
        comodel_name='area_resolvedora',
        string='Área Resolvedora',
        help='Área atualmente responsável pela resolução da demanda',
        track_visibility='onchange',
        required=True)

    data_inicio = fields.Datetime(
        string="Data de Inicio",
        help="Data de inicio do tratamento",
        track_visibility="onchange"
    )

    data_final = fields.Datetime(
        string="Data de Término",
        help="Data de término do tratamento",
        track_visibility="onchange"
    )

    ocorrencia = fields.Many2one(
        string="Ocorrência",
        comodel_name='ocorrencia',
        required=True,
        help="Ocorrência a qual a abertura/fechamento está vinculada")


from xai.brain.wordbase.nouns._outburst import _OUTBURST

#calss header
class _OUTBURSTS(_OUTBURST, ):
	def __init__(self,):
		_OUTBURST.__init__(self)
		self.name = "OUTBURSTS"
		self.specie = 'nouns'
		self.basic = "outburst"
		self.jsondata = {}


class TrieNode:
    def __init__(self):
        self.children = dict()

    def insert(self, char):
        pass

class Trie:
    def __init__(self):
        self.root = TrieNode()

    def insert(self, word):
        head = self.root
        tail = head
        for char in word:
           value = tail.children.get(char)
           if (value is None):
                node = TrieNode()
                tail.children[char] = node
           tail = tail.children[char]
        pass

    def find(self, prefix):
        result = []
        head = self.root
        index = 0
        size = len(prefix)
        if (prefix is not None):
            if (size > 0):
                while (index < size):
                    char = prefix[index]
                    char_in_trie = head.children.get(char)
                    if (char_in_trie is not None):
                        head = char_in_trie
                        index+=1
                    else:
                        break
                if (index == size):
                    result = self._find(head)

        return result

    def _find(self,tail):
         result = []
         for item in tail.children.items():
             subresult =  self._find(item[1])
             subresult = [ item[0] + subitem  for subitem in subresult]
             if (len(subresult) == 0):
                 subresult = [item[0]]
             result+=subresult

         return result

    def __repr__(self):
        string = ""
        tail = self.root
        data = self._find(tail)
        print(data)
        return string


import numpy as np

UP = 0
RIGHT = 1
DOWN = 2
LEFT = 3

class GridWorldEnv:
    def __init__(self, shape=None):
        if not shape:
            shape = [4, 4]
        self.shape = shape
        nS = np.prod(shape)
        self.nS = nS
        self.states = np.arange(nS)
        nA = 4
        self.nA = nA
        self.actions = np.arange(nA)
        self.height, self.width = self.shape

        P = {}

        for (y, x), state in np.ndenumerate(self.states.reshape(shape)):
            P[state] = {action: [] for action in self.actions}

            is_done = True if state == 0 or state == (nS - 1) else False

            for action in self.actions:
                if is_done:
                    P[state][action] = [(1, state, 0, is_done)]
                else:
                    if action == UP:
                        next_state_up = state if y == 0 else state - self.width
                        P[state][action] = [(1, next_state_up, -1, is_done)]
                    if action == RIGHT:
                        next_state_right = state if x == self.width - 1 else state + 1
                        P[state][action] = [(1, next_state_right, -1, is_done)]
                    if action == DOWN:
                        next_state_down = state if y == self.height - 1 else state + self.width
                        P[state][action] = [(1, next_state_down, -1, is_done)]
                    if action == LEFT:
                        next_state_left = state if x == 0 else state - 1
                        P[state][action] = [(1, next_state_left, -1, is_done)]

        self.P = P


#! /usr/bin/env python
#
#  Copyright 2018 California Institute of Technology
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.
#
# ISOFIT: Imaging Spectrometer Optimal FITting
# Author: David R Thompson, david.r.thompson@jpl.nasa.gov
#

import os
import sys
import json
import argparse
import scipy as s
from spectral.io import envi
from os.path import expandvars, split, abspath
from scipy.io import savemat

from common import expand_all_paths, spectrumLoad
from forward import ForwardModel
from inverse import Inversion
from inverse_mcmc import MCMCInversion
from geometry import Geometry
from output import Output
import cProfile

class OOBError(Exception):
    """Spectrum is out of bounds or has bad data"""

    def __init__(self):
        super(OOBError, self).__init__("")

def main():

    description = 'Spectroscopic Surface & Atmosphere Fitting'
    parser = argparse.ArgumentParser()
    parser.add_argument('config_file')
    parser.add_argument('--row_column', default='')
    parser.add_argument('--profile', action='store_true')
    args = parser.parse_args()

    # Setup
    config = json.load(open(args.config_file, 'r'))
    configdir, f = split(abspath(args.config_file))
    config = expand_all_paths(config, configdir)

    # Forward Model
    fm = ForwardModel(config['forward_model'])

    # Inversion method
    if 'mcmc_inversion' in config:
        iv = MCMCInversion(config['mcmc_inversion'], fm)
    else:
        iv = Inversion(config['inversion'], fm)

    # Output object
    out = Output(config, iv)

    # Simulation mode? Binary or text mode?
    simulation_mode = (not ('input' in config)) or \
        (not ('measured_radiance_file' in config['input']))
    text_mode = simulation_mode or \
        config['input']['measured_radiance_file'].endswith('txt')

    # Do we apply a radiance correction?
    if (not simulation_mode) and \
            'radiometry_correction_file' in config['input']:
        radiance_correction_file = config['input']['radiometry_correction_file']
        radiance_correction, wl = spectrumLoad(radiance_correction_file)
    else:
        radiance_correction = None

    if text_mode:

        # ------------------------------- Text mode

        # build geometry object
        if 'input' in config:
            obs, loc, glt, ds, esd = None, None, None, None, None
            if 'glt_file' in config['input']:
                glt = s.loadtxt(config['input']['glt_file'])
            if 'obs_file' in config['input']:
                obs = s.loadtxt(config['input']['obs_file'])
            if 'loc_file' in config['input']:
                loc = s.loadtxt(config['input']['loc_file'])
            if 'date_string' in config['input']:
                ds = config['input']['date_string']
            if 'earth_sun_distance_file' in config['input']:
                esd = s.loadtxt(config['input']['earth_sun_distance_file'])
            geom = Geometry(obs=obs, glt=glt, loc=loc, ds=ds, esd=esd)
        else:
            geom = None

        if simulation_mode:

            # Get our measurement from instrument data or the initial state vector
            state_est = fm.init_val.copy()
            rdn_est = fm.calc_rdn(state_est, geom)
            rdn_meas = rdn_est.copy()
            rdn_sim = fm.instrument.simulate_measurement(rdn_meas, geom)
            rfl_est, rdn_est, path_est, S_hat, K, G =\
                iv.forward_uncertainty(state_est, rdn_meas, geom)

        else:

            # Get radiance
            rdn_meas, wl = spectrumLoad(
                config['input']['measured_radiance_file'])
            if radiance_correction is not None:
                rdn_meas = rdn_meas * radiance_correction
            rdn_sim = None

            if args.profile:
                cProfile.runctx('iv.invert(rdn_meas, geom, None)',
                                globals(), locals(), sort='tottime')
                sys.exit(0)

            elif 'mcmc_inversion' in config:

                # MCMC Sampler
                state_est, samples = iv.invert(rdn_meas, geom, out=out)
                if 'mcmc_samples_file' in config['output']:
                    D = {'samples': samples}
                    savemat(config['output']['mcmc_samples_file'], D)
                rfl_est, rdn_est, path_est, S_hat, K, G =\
                    iv.forward_uncertainty(state_est, rdn_meas, geom)

            else:

                # Conjugate Gradient
                state_est = iv.invert(rdn_meas, geom, out=out)
                rfl_est, rdn_est, path_est, S_hat, K, G =\
                    iv.forward_uncertainty(state_est, rdn_meas, geom)

        out.write_spectrum(state_est, rfl_est, rdn_est, path_est,
                           rdn_meas, rdn_sim, geom)

    else:

        # ------------------------------ Binary mode

        meas_file = config['input']['measured_radiance_file']
        meas_hdr = meas_file + '.hdr'
        meas = envi.open(meas_hdr, meas_file)
        nl, nb, ns = [int(meas.metadata[n])
                      for n in ('lines', 'bands', 'samples')]

        # Do we apply a flatfield correction?
        if 'flatfield_correction_file' in config['input']:
            ffile = config['input']['flatfield_correction_file']
            fcor = envi.open(ffile+'.hdr', ffile)
            fcor_mm = fcor.open_memmap(interleave='source',  writable=False)
            flatfield = s.array(fcor_mm[0, :, :])
        else:
            flatfield = None

        if 'obs_file' in config['input']:
            obs_file = config['input']['obs_file']
            obs_hdr = obs_file + '.hdr'
            obs = envi.open(obs_hdr,  obs_file)
            if int(obs.metadata['bands']) != 11 and \
               int(obs.metadata['bands']) != 10:
                raise ValueError('Expected 10 or 11 bands in OBS file')
            if int(obs.metadata['lines']) != nl or \
               int(obs.metadata['samples']) != ns:
                raise ValueError('obs file dimensions do not match radiance')
        else:
            obs_file, obs_hdr = None, None

        if 'glt_file' in config['input']:
            glt_file = config['input']['glt_file']
            glt_hdr = glt_file + '.hdr'
            glt = envi.open(glt_hdr,  glt_file)
            if int(glt.metadata['bands']) != 2:
                raise ValueError('Expected two bands in GLT file')
            if int(glt.metadata['lines']) != nl or \
               int(glt.metadata['samples']) != ns:
                raise ValueError('GLT file dimensions do not match radiance')
        else:
            glt_file, glt_hdr = None, None

        if 'loc_file' in config['input']:
            loc_file = config['input']['loc_file']
            loc_hdr = loc_file + '.hdr'
            loc = envi.open(loc_hdr,  loc_file)
            if int(loc.metadata['bands']) != 3:
                raise ValueError('Expected three bands in LOC file')
            if int(loc.metadata['lines']) != nl or \
               int(loc.metadata['samples']) != ns:
                raise ValueError('loc file dimensions do not match radiance')
        else:
            loc_file, loc_hdr = None, None

        rfl_file = config['output']['estimated_reflectance_file']
        rfl_hdr = rfl_file+'.hdr'
        rfl_meta = meas.metadata.copy()
        if not os.path.exists(rfl_hdr):
            rfl = envi.create_image(rfl_hdr, rfl_meta, ext='', force=True)
            del rfl
        rfl = envi.open(rfl_hdr, rfl_file)

        state_file = config['output']['estimated_state_file']
        state_hdr = state_file+'.hdr'
        state_meta = meas.metadata.copy()
        state_meta['bands'] = len(fm.statevec)
        state_meta['band names'] = fm.statevec[:]
        if not os.path.exists(state_hdr):
            state = envi.create_image(
                state_hdr, state_meta, ext='', force=True)
            del state
        state = envi.open(state_hdr, state_file)

        mdl_file = config['output']['modeled_radiance_file']
        mdl_hdr = mdl_file+'.hdr'
        mdl_meta = meas.metadata.copy()
        if not os.path.exists(mdl_hdr):
            mdl = envi.create_image(mdl_hdr, mdl_meta, ext='', force=True)
            del mdl
        mdl = envi.open(mdl_hdr, mdl_file)

        path_file = config['output']['path_radiance_file']
        path_hdr = path_file+'.hdr'
        path_meta = meas.metadata.copy()
        if not os.path.exists(path_hdr):
            path = envi.create_image(path_hdr, path_meta, ext='', force=True)
            del path
        path = envi.open(path_hdr, path_file)

        post_file = config['output']['posterior_uncertainty_file']
        post_hdr = post_file+'.hdr'
        post_meta = state_meta.copy()
        if not os.path.exists(post_hdr):
            post = envi.create_image(post_hdr, post_meta, ext='', force=True)
            del post
        post = envi.open(post_hdr, post_file)

        if 'component_file' in config['output']:
            comp_file = config['output']['component_file']
            comp_hdr = comp_file+'.hdr'
            comp_meta = state_meta.copy()
            comp_meta['bands'] = 1
            comp_meta['band names'] = '{Surface Model Component}'
            if not os.path.exists(comp_hdr):
                comp = envi.create_image(
                    comp_hdr, comp_meta, ext='', force=True)
                del comp
            comp = envi.open(comp_hdr, comp_file)
        else:
            comp = None

        meas_mm, obs_mm, state_mm, rfl_mm, path_mm, mdl_mm = \
            None, None, None, None, None, None

        # Did the user specify a row,column tuple, or a row?
        if len(args.row_column) < 1:
            lines, samps = range(nl), range(ns)
        else:
            # Restrict the range of the retrieval if overridden.
            ranges = args.row_column.split(',')
            if len(ranges) == 1:
                lines, samps = [int(ranges[0])], range(ns)
            if len(ranges) == 2:
                line_start, line_end = ranges
                lines, samps = range(int(line_start), int(line_end)), range(ns)
            elif len(ranges) == 4:
                line_start, line_end, samp_start, samp_end = ranges
                lines = range(int(line_start), int(line_end))
                samps = range(int(samp_start), int(samp_end))

        # analyze the image one frame at a time
        for i in lines:
            print('line %i/%i' % (i, nl))
            # Flush cache every once in a while
            if meas_mm is None or i % 100 == 0:

                # Refresh Radiance buffer
                del meas
                meas = envi.open(meas_hdr,  meas_file)
                meas_mm = meas.open_memmap(
                    interleave='source',  writable=False)

                # Refresh OBS buffer
                if obs_hdr is not None:
                    del obs
                    obs = envi.open(obs_hdr,   obs_file)
                    obs_mm = obs.open_memmap(
                        interleave='source',   writable=False)

                # Refresh GLT buffer
                if glt_hdr is not None:
                    del glt
                    glt = envi.open(glt_hdr,   glt_file)
                    glt_mm = glt.open_memmap(
                        interleave='source',   writable=False)

                # Refresh LOC buffer
                if loc_hdr is not None:
                    del loc
                    loc = envi.open(loc_hdr,   loc_file)
                    loc_mm = loc.open_memmap(
                        interleave='source',   writable=False)

                # Refresh Output buffers
                del rfl
                rfl = envi.open(rfl_hdr,   rfl_file)
                rfl_mm = rfl.open_memmap(interleave='source',   writable=True)

                del state
                state = envi.open(state_hdr, state_file)
                state_mm = state.open_memmap(
                    interleave='source', writable=True)

                del path
                path = envi.open(path_hdr,  path_file)
                path_mm = path.open_memmap(interleave='source',  writable=True)

                del mdl
                mdl = envi.open(mdl_hdr,  mdl_file)
                mdl_mm = mdl.open_memmap(interleave='source',  writable=True)

                del post
                post = envi.open(post_hdr,  post_file)
                post_mm = post.open_memmap(interleave='source',  writable=True)

                if comp is not None:
                    del comp
                    comp = envi.open(comp_hdr,  comp_file)
                    comp_mm = comp.open_memmap(
                        interleave='source',  writable=True)

            # translate to BIP
            meas_frame = s.array(meas_mm[i, :, :]).T

            if obs_hdr is not None:
                obs_frame = s.array(obs_mm[i, :, :])

            if glt_hdr is not None:
                glt_frame = s.array(glt_mm[i, :, :])

            if loc_hdr is not None:
                loc_frame = s.array(loc_mm[i, :, :])

            init = None

            nl = int(rfl_meta['lines'])
            ns = int(rfl_meta['samples'])
            nb = int(rfl_meta['bands'])
            nsv = int(state_meta['bands'])

            if comp is not None:
                comp_frame = s.zeros((1, ns))
            post_frame = s.zeros((nsv, ns), dtype=s.float32)
            rfl_frame = s.zeros((nb, ns), dtype=s.float32)
            state_frame = s.zeros((nsv, ns), dtype=s.float32)
            path_frame = s.zeros((nb, ns), dtype=s.float32)
            mdl_frame = s.zeros((nb, ns), dtype=s.float32)

            for j in samps:
                try:

                    # Use AVIRIS-C convention?
                    if loc_hdr is not None and "t01p00r" in loc_file:
                        loc_frame[:, 2] = loc_frame[:, 2] / \
                            1000.0  # translate to km

                    rdn_meas = meas_frame[j, :]

                    # Bad data test
                    if all(rdn_meas < -49.0):
                        raise OOBError()

                    if obs_hdr is not None:
                        obs_spectrum = obs_frame[j, :]
                    else:
                        obs_spectrum = None

                    if glt_hdr is not None:
                        pc = abs(glt_frame[j, 0])-1
                        if pc < 0:
                            raise OOBError()
                        glt_spectrum = glt_frame[j, :]
                    else:
                        pc = j
                        glt_spectrum = None

                    if loc_hdr is not None:
                        loc_spectrum = loc_frame[j, :]
                    else:
                        loc_spectrum = None

                    if flatfield is not None:
                        rdn_meas = rdn_meas * flatfield[:, pc]
                    if radiance_correction is not None:
                        rdn_meas = rdn_meas * radiance_correction
                    geom = Geometry(obs_spectrum, glt_spectrum, loc_spectrum,
                                    pushbroom_column=pc)

                    # Inversion
                    if args.profile:
                        cProfile.runctx('iv.invert(rdn_meas, geom, None)',
                                        globals(), locals())
                        sys.exit(0)
                    else:
                        state_est = iv.invert(rdn_meas, geom, None, init=init)
                        rfl_est, rdn_est, path_est, S_hat, K, G =\
                            iv.forward_uncertainty(state_est, rdn_meas, geom)

                    # write spectrum
                    state_surf = state_est[iv.fm.surface_inds]
                    post_frame[:, j] = s.sqrt(s.diag(S_hat))
                    rfl_frame[:, j] = rfl_est
                    state_frame[:, j] = state_est
                    path_frame[:, j] = path_est
                    mdl_frame[:, j] = rdn_est
                    if comp is not None:
                        comp_frame[:, j] = iv.fm.surface.component(
                            state_surf, geom)

                except OOBError:
                    post_frame[:, j] = -9999*s.ones((nsv))
                    rfl_frame[:, j] = -9999*s.ones((nb))
                    state_frame[:, j] = -9999*s.ones((nsv))
                    path_frame[:, j] = -9999*s.ones((nb))
                    mdl_frame[:, j] = -9999*s.ones((nb))
                    if comp is not None:
                        comp_frame[:, j] = -9999

            post_mm[i, :, :] = post_frame.copy()
            rfl_mm[i, :, :] = rfl_frame.copy()
            state_mm[i, :, :] = state_frame.copy()
            path_mm[i, :, :] = path_frame.copy()
            mdl_mm[i, :, :] = mdl_frame.copy()
            if comp is not None:
                comp_mm[i, :, :] = comp_frame.copy()

        del post_mm
        del rfl_mm
        del state_mm
        del path_mm
        del mdl_mm
        if comp is not None:
            del comp_mm

if __name__ == '__main__':
    main()


from ..abstract_command import AbstractModelCommand

class Command(AbstractModelCommand):
    help = 'Creates new principal.'

    def add_command_arguments(self, parser):
        parser.add_argument(
            '--new-uuid',
            type=str,
            metavar='"{00000000-0000-0000-0000-000000000000}"',
            help='UUID of principal. Will be generated if not specified.')
        parser.add_argument(
            '--new-brief-name',
            type=str,
            metavar='John',
            help='Brief name of principal.')
        parser.add_argument(
            '--new-full-name',
            type=str,
            metavar='"John Doe"',
            help='Full name of principal.')
        parser.add_argument(
            '--new-email',
            type=str,
            metavar='"johndoe@example.com"',
            required=True,
            help='Email address of principal.')
        parser.add_argument(
            '--new-phone',
            type=str,
            metavar='"+1-541-754-3010"',
            required=False,
            help='Phone number of principal.')

        status_parser = parser.add_mutually_exclusive_group(required=True)
        status_parser.add_argument('--new-active', action='store_true', dest='new_is_active', help='Activate principal.')
        status_parser.add_argument('--new-passive', action='store_false', dest='new_is_active', help='Disactivate principal.')

    def handle_command(self, *args, **options):
        from ...models import Principal
        from uuid import UUID
        from uuid import uuid4

        principal = Principal()
        principal.uuid = UUID(options['new_uuid']) if options['new_uuid'] else uuid4()
        principal.brief_name = options['new_brief_name']
        principal.full_name = options['new_full_name']
        principal.email = options['new_email']
        principal.phone = options['new_phone'] if options['new_phone'] else None
        principal.is_active = options['new_is_active']
        principal.save()

        return principal


import discord
import random
import os
from discord.ext import commands

client = commands.Bot(command_prefix='.')

@client.event
async def on_member_join(member):
    print(f'{member} acaba de entrar.')

@client.event
async def on_ready():
    await client.change_presence(status=discord.Status.idle, activity=discord.Game('follarme a tu madre'))
    print('Bot is ready')

@client.event
async def on_member_remove(member):
    print(f'{member} se acaba de pirar.')

@client.command()
async def load(ctx, extension):
    client.load_extension(f'cogs.{extension}')

@client.command()
async def unload(ctx, extension):
    client.unload_extension(f'cogs.{extension}')

for filename in os.listdir('./cogs'):
    if filename.endswith('.py'):
        client.load_extension(f'cogs.{filename[:-3]}')

@client.command(aliases=['8ball', 'test'])
async def _8ball(ctx, *, question):
    responses = ['It is certain', 'Without a doubt', 'You may rely on it', 'Yes definitely',
                 'It is decidedly so', 'As I see it, yes', 'Most likely', 'Yes', 'Outlook good', 'Signs point to yes',
                 'Don’t count on it', 'Outlook not so good', 'My sources say no', 'Very doubtful', 'My reply is no',
                 'Reply hazy try again', 'Better not tell you now', 'Ask again later', 'Cannot predict now', 'Concentrate and ask again']
    await ctx.send(f'{random.choice(responses)}')

client.run(os.getenv('TOKEN'))


import statistics
import sys
import string
import collections
import random

"""
2. 2 pointers in 1 array

a. Find all pairs of 3 in an array that add to n.

b. N-sum b: Find unique triplets in duplicate array which gives the sum of n

c. Find all pairs of 4 in an array that add to n.

d. find three integers in S such that the sum is closest to a given number
Assume that there will only be one solution
Example:
given array S = {-1 2 1 -4},
and target = 1.
The sum that is closest to the target is 2. (-1 + 2 + 1 = 2)

e. Given an array of n integers nums and a target, find the number of index triplets i, j, k with 0 <= i < j < k < n
that satisfy the condition nums[i] + nums[j] + nums[k] < target.

Example:

Input: nums = [-2,0,1,3], and target = 2
Output: 2
Explanation: Because there are two triplets which sums are less than 2:
             [-2,0,1]
             [-2,0,3]
Follow up: Could you solve it in O(n2) runtime?

f. Remove duplicates from sorted array in place in O(n)
Given a sorted array, the task is to remove the duplicate elements from the array.

Examples:

Input  : arr[] = {2, 2, 2, 2, 2}
Output : arr[] = {2}
         new size = 1

Input  : arr[] = {1, 2, 2, 3, 4, 4, 4, 5, 5}
Output : arr[] = {1, 2, 3, 4, 5}
         new size = 5

g. Given an array nums and a value val, remove all instances of that value in-place and return the new length.

Do not allocate extra space for another array, you must do this by modifying the input array in-place with O(1) extra memory.

The order of elements can be changed. It doesn't matter what you leave beyond the new length.

Example 1:

Given nums = [3,2,2,3], val = 3,

Your function should return length = 2, with the first two elements of nums being 2.

It doesn't matter what you leave beyond the returned length.
Example 2:

Given nums = [0,1,2,2,3,0,4,2], val = 2,
"""

def qn2b1(arr, k):
	if len(arr) <3:
		return -1

	out = []
	my_set = set()
	for i in range(len(arr) - 1):
		for j in range(len(arr)):
			a = arr[i]
			b = arr[j]
			current_sum =  k - a - b
			if current_sum in my_set:
				out.append(current_sum, a, b)
			else:
				my_set.append(b)

	return out

def qn2b2(arr, target):
	arr.sort()
	res = []
	for i in range(len(arr)-2):
		if i==0 or (i>0 and arr[i]!= arr[i-1]):
			j, k = i+1, len(arr)-1
			while j < k:
				if arr[i] + arr[j] + arr[k] == target:
					res.append([arr[i], arr[j], arr[k]])
					while j< k and arr[j+1] == arr[j]:
						j += 1
					while j <k and arr[k-1] == arr[k]:
						k -= 1
					j+=1
					k -= 1
				elif arr[i]+arr[j]+arr[k] < target:
					j += 1
				else:
					k -= 1
	return res
arr = [-1, 0, 1, 2, -1, -4]
k = 0
#print(qn2b(arr, k))

class number:
	def __init__(self,val, i, j):
		self.sum = val
		self.first =i
		self.second = j

def qn2c(arr, k):
	pass

print(qn2c([1, 0, -1, 0, -2, 2],0))

def qn2d(s, target):
	if len(s) < 3:
		raise ValueError("length of array should be more than 2")

	s = sorted(s)

	closest = None
	current_diff = sys.maxsize

	for i in range(len(s)-2):
		j, k = i+1, len(s) - 1
		while j < k:
			current_sum = s[i] + s[j] +s[k]
			if current_sum == target:
				return current_sum
			abs_diff = abs(current_sum - target)

			if abs_diff < current_diff:
				current_diff = abs_diff
				closest = current_sum

			if current_sum > target:
				j += 1
			else:
				k -=1

	return closest

def qn2e(nums, target):

	nums.sort()
	res = 0
	for i in range(len(nums)-2):
		j,k = i+1, len(nums)-1
		while j < k:
			if nums[i] + nums[j] + nums[k] < target:
				res += k-j
				j += 1
			else:
				k -=1
	return res

def qn2f(arr):
	if len(arr) <=0:
		return []
	arr = nums
	j = 0
	for i in range(len(arr)):
		if arr[i]!=arr[j]:
			j += 1
			arr[j] = arr[i]

	return arr[:j+1]

def qn2g(arr, num):
	if len(arr) <= 0:
		return []

	j = 0
	for i in range(lem(arr)):
		if arr[i] != num:
			arr[j] = arr[i]
			j +=1

	return arr[:j+1]

"""
3. rotation
a. rotates arr[] of size n by d elements.
b. rotate NxN matrix,  by 90 degrees in place
"""
def qn3a(array, d):

	n = len(array)
	if d > n:
		d = d%n

	reversal(array, 0, d)
	reversal(array, d, n)
	reversal(array, 0, n)

	return array

def reversal(array, start, end):
	i, j = start, end - 1
	while i < j:
		temp = array[i]
		array[i] = array[j]
		array[j] = temp
		j -= 1
		i += 1

def qn3b(A):
	n = len(A)

	if n < 1:
		return -1

	layer = 0
	while layer < n//2:
		length = n - layer - 1 # layer =0, length_ = 3
		for i in range(layer, length): # loop for 0,1,2
			temp = A[layer][i]
			A[layer][i] = A[i][length]
			A[i][length] = A[length][length - i]
			A[length][length - i] = A[length - i][layer]
			A[length - i][layer] = temp
		layer += 1

	return A

"""
4.

a. Given an array,find the maximum j – i such that arr[j] > arr[i]

b.  Partition Array into Disjoint Intervals
User Accepted: 1501
User Tried: 1874
Total Accepted: 1525
Total Submissions: 4281
Difficulty: Medium
Given an array A, partition it into two (contiguous) subarrays left and right so that:

Every element in left is less than or equal to every element in right.
left and right are non-empty.
left has the smallest possible size.
Return the length of left after such a partitioning.  It is guaranteed that such a partitioning exists.

Example 1:

Input: [5,0,3,8,6]
Output: 3
Explanation: left = [5,0,3], right = [8,6]
Example 2:

Input: [1,1,1,0,6,12]
Output: 4
Explanation: left = [1,1,1,0], right = [6,12]

c.
Volume of Histogram: Imagine a histogram (bar graph). Design an algorithm to compute the
volume of water it could hold if someone poured water across the top. You can assume that each
histogram bar has width 1.
EXAMPLE
lnput{0, 0, 4, 0, 0, 6, 0, 0, 3, 0, 5, 0, 1, 0, 0, 0}
(Black bars are the histogram. Gray is water.)
Output:26

d. Replace every element with the greatest element on right side.
Since there is no element next to the last element, replace it with -1 also replace even if arr[i] is max

"""

def qn4a(arr):
	left_min = [arr[0]]
	right_max = [arr[len(arr)-1]]

	for i in range(1, len(arr)):
		left_min.append(min(left_min[i-1], arr[i]))

	for i in range(len(arr)-2, -1, -1):
		right_max.insert(0, max(right_max[0], arr[i]))

	i,j = 0, 0
	max_diff = -1
	while i < len(arr) and j < len(arr):
		if left_min[i] >= right_max[j]:
			i += 1
		else:
			max_diff = max(max_diff, j-i)
			j += 1

	return max_diff

def qn4b(arr):
	max_from_left = [0] * len(arr)
	min_from_right = [0] * len(arr)

	max_from_left[0] = arr[0]
	for i in range(1, len(arr)):
		max_from_left[i] =  max(max_from_left[i-1],arr[i])

	min_from_right[len(arr)-1] = arr[len(arr)-1]
	for i in range(len(arr)-2, -1, -1):
		min_from_right[i] =  min(min_from_right[i+1], arr[i])

	res = 1
	i = 0
	while i < len(arr)-1 and max_from_left[i] >  min_from_right[i+1]:
		i += 1
		res += 1

	return res

def qn4c(arr):
	max_from_left = [0] * len(arr)
	max_from_left[0] = arr[0]
	for i in range(1, len(arr)):
		max_from_left[i] = max(arr[i], max_from_left[i-1])

	max_from_right = [0] * len(arr)
	max_from_right[-1] = arr[-1]
	for i in range(len(arr)-2, -1, -1):
		max_from_right[i] = max(arr[i], max_from_right[i+1])

	res = 0
	for i in range(len(arr)):
		smaller = min(max_from_left[i], max_from_right[i])
		res += smaller - arr[i]

	return res

#print(qn4c([0, 0, 4, 0, 0, 6, 0, 0, 3, 0, 5, 0, 1, 0, 0, 0]))

def qn4d(arr):
	current_max = arr[len(arr) - 1]
	arr[len(arr) - 1] = -1

	for i in range(len(arr) - 2, -1, -1):
		temp = arr[i]
		arr[i] = current_max
		current_max = max(temp, current_max)

	return arr

array = [16, 17, 4, 3, 5, 2]
#print(qn10(array)) # 17,5,5,5,2,-1

"""
6. matrix

a.  Copy matrix in spiral order
input:  inputMatrix  = [ [1,    2,   3,  4,    5],
                         [6,    7,   8,  9,   10],
                         [11,  12,  13,  14,  15],
                         [16,  17,  18,  19,  20] ]

output: [1, 2, 3, 4, 5, 10, 15, 20, 19, 18, 17, 16, 11, 6, 7, 8, 9, 14, 13, 12]

b. Zero Matrix: Mark entire row/col 0 if an element in row/col = 0

 Tic Tac toe:
c. Design an algorithm to figure out if someone has won a game of tic-tac-toe.
d. Validity of a given Tic-Tac-Toe board configuration

"""
def qn6a(matrix):
	out = []
	row = len(matrix)
	col = len(matrix[0])
	left, right = 0, col - 1
	top, bottom = 0, row - 1

	while left <= right and top <= bottom:
		for i in range(left, right+1):
			out.append(matrix[top][i])
		top += 1

		for i in range(top, bottom+1):
			out.append(matrix[i][right])

		right -= 1

		for i in range(right, left - 1, -1):
			out.append(matrix[bottom][i])

		bottom -= 1

		for i in range(bottom, top -1 , -1):
			out.append(matrix[i][left])
		left += 1

	return out

def qn6b(A):
	row = len(A)
	col = len(A[0])

	first_row_has_zero = False
	first_col_has_zero = False

	for i in range(col):
		if A[[0][i]] == 0:
			first_row_has_zero = True
			break

	for i in range(row):
		if A[[i][0]] == 0:
			first_col_has_zero = True
			break

	for i in range(row):
		for j in range(col):
			if A[i][j] == 0:
				A[i][0] = 0
				A[0][j] = 0

	for i in range(col):
		if A[0][i] == 0:
			for j in range(row):
				A[j][i] = 0

	for i in range(row):
		if A[i][0] == 0:
			for j in range(col):
				A[i][j] = 0

	if first_row_has_zero:
		for i in range(col):
			A[0][i] = 0

	if first_col_has_zero:
		for i in range(row):
			A[i][0] = 0

	return A

def qn6c(matrix):
	pass

"""
11.
 Repeated String Match
Given two strings A and B, find the minimum number of times A has to be repeated such that B is a substring of it. If no such solution, return -1.

For example, with A = "abcd" and B = "cdabcdab".

Return 3, because by repeating A three times (“abcdabcdabcd”), B is a substring of it; and B is not a substring of A repeated two times ("abcdabcd").

Note:
The length of A and B will be between 1 and 10000.
"""
def qn11(A, B):
    if not is_valid(A,B):
        return -1

    count = (len(B)-1)//len(A) +1
    if B in A*count:
        return count
    elif B in A*(count+1):
        return count+1
    else:
        return -1

def is_valid(A, B):
    char_set = set()
    for c in A:
        char_set.add(c)

    for c in B:
        if c not in char_set:
            return False
    return True

"""
12. sliding window

a1. finds the smallest substring of str containing all the characters in pattern

a2. Shortest Supersequence: You are given two arrays, one shorter (with all distinct elements) and one
longer. Find the shortest subarray in the longer array that contains all the elements in the shorter
array. The items can appear in any order.
EXAMPLE
Input:
{1, 5, 9}
{7, 5, 9, 0, 2, 1, 3, 5. 7, 9. 1, 1, 5, 8, 8, 9, 7}
Output:[7, 10] (the underlined portion above)

b. longest substring : Length of the longest substring without repeating characters (quite hard)

c.  K Empty Slots
There is a garden with N slots. In each slot, there is a flower.
The N flowers will bloom one by one in N days. In each day, there will be
 exactly one flower blooming and it will be in the status of blooming since then.

Given an array flowers consists of number from 1 to N. Each number in the array represents
the place where the flower will open in that day.

For example, flowers[i] = x means that the unique flower that blooms at day i will be at position x,
 where i and x will be in the range from 1 to N.

Also given an integer k, you need to output in which day there exists two flowers
in the status of blooming, and also the number of flowers between them is k and these flowers are not blooming.

If there isn't such day, output -1.

d. Longest Substring with At Most K Distinct Characters
Given a string, find the length of the longest substring T that contains at most k distinct characters.

Example 1:

Input: s = "eceba", k = 2
Output: 3
Explanation: T is "ece" which its length is 3.
Example 2:

Input: s = "aa", k = 1
Output: 2
Explanation: T is "aa" which its length is 2.

e. Given a binary array, find the maximum number of consecutive 1s in this array if you can flip at most one 0.

Example 1:
Input: [1,0,1,1,0]
Output: 4
Explanation: Flip the first zero will get the the maximum number of consecutive 1s.
    After flipping, the maximum number of consecutive 1s is 4.
Note:

The input array will only contain 0 and 1.
The length of input array is a positive integer and will not exceed 10,000
Follow up:
What if the input numbers come in one by one as an infinite stream? In other words, you can't store all numbers coming from the stream as it's too large to hold in memory. Could you solve it efficiently?

f. You are given with an array of 1s and 0s. And you are given with an integer m, which signifies number of flips allowed.

find the position of zeros which when flipped will produce maximum continuous series of 1s.

e.g.
input:
arr={1 1 0 1 1 0 0 1 1 1 } m=1
output={1 1 1 1 1 0 0 1 1 1} position=2

arr={1 1 0 1 1 0 0 1 1 1 } m=2
output={1 1 0 1 1 1 1 1 1 1} position=5,6

g. Majority Element: A majority element is an element that makes up more than half of the items in
an array. Given a positive integers array, find the majority element. If there is no majority element,
return -1. Do this in O(N) time and 0(1) space.
Input: 1 2 5 9 5 9 5 5 5
Output: 5

"""

def qn12a1(str_input, pat):
	pat_table = {}
	str_table = {}

	# add charaters of pattern into hash table
	for c in pat:
		if c not in pat_table:
			pat_table[c] = 1
		else:
			pat_table[c] += 1

	res = ""
	counter = 0
	start = 0

	for i in range(len(str_input)):

		# check if the char is one of chracters in pattern
		char = str_input[i]

		if char not in str_table:
			str_table[char] = 1
		else:
			str_table[char] += 1

		# this is must be <= because we add the character first before check
		if char in pat_table and str_table[char] <= pat_table[char]:
			counter += 1

		# keep moving back pointer as long as counter == len(pat)
		while counter == len(pat) and start < i :
			if  res == "" or i - start + 1 < len(res):
				res = str_input[start:i+1]

			# moving start poiter
			c = str_input[start]
			str_table[c] -= 1

			if c in pat_table and str_table[c] < pat_table[c]:
				counter -= 1

			start += 1

	return res

		# so somethign here

pattern = "xyz"
str_input= "xyyyzyx"
#print(qn12a1(str_input, pattern))

def qn12a2(pat, arr):
	pass

short_arr = [1, 5, 9]
long_arr = [7, 5, 9, 0, 2, 1, 3, 5, 7, 9, 1, 1, 5, 8, 8, 9, 7, 1, 5, 9]
#print(qn12a2(short_arr, long_arr))

short_arr = [1, 5, 9]
long_arr = [7, 5, 9]
#print(qn12a2(short_arr, long_arr))

short_arr = [1, 5, 9]
long_arr = [7, 5, 9, 1]
#print(qn12a2(short_arr, long_arr))

def qn12b(string):
	my_set = set()
	start, end = 0,0
	count = 0
	out = None

	while start < len(string) and end < len(string):
		while end < len(string) and string[end] not in my_set:
			my_set.add(string[end])
			end += 1

		current = string[end]
		if end - start > count:
			count  = end-start
			out = string[start:end]

		#print(out)
		if start < end and string[start] == current:
			my_set.remove(string[start])
			start += 1
		end += 1

	if end - start > count:
		count  = end-start
		out = string[start:end]

	return out

#print(qn6("ABDEFGABEF"))
#print(qn6("BBBB"))
#print(qn6("abcabcbb")) #abc

def qn12c(flowers, k):
	# convert to 0-based index
	for i in range(len(flowers)):
		flowers[i] -=1

	days = [0] *len(flowers)

	for i in range(len(flowers)):
		days[flowers[i]] = i

	print(flowers)
	print(days)
	left, right, res = 0, k+1, sys.maxsize
	middle = 0
	while right < len(days) and left <= right - k- 1:
		if days[middle] < days[left] or days[middle] <= days[right]:
			# at the position, all the days[middle] between left and right and more than arr[left] and arr[right]
			# we update res
			if middle == right:
				res = min(res, max(days[left], days[right]))

			left = middle
			right = k + 1 + middle

		middle += 1

	return (res+1) if res != sys.maxsize else -1

#print(qn15([1,4,2,3],1))
#print(qn15([1,3,2,4],1))
#print(qn15([1,2,3],1))
#print(qn15([1,3,2],1))
#print(qn15([6,5,8,9,7,1,10,2,3,4],2))

def qn12d(s, k):

	j = 0
	hash_table = {}
	counter = 0
	res = 0
	for i in range(len(s)):

		char = s[i]
		if char not in hash_table or hash_table[char] == 0:
			hash_table[char] = 1
			counter += 1
		else:
			hash_table[char] += 1
		while counter > k:
			hash_table[s[j]] -= 1
			if hash_table[s[j]] == 0:
				counter -=1
			j +=1

		if counter <= k and j <=i:
			res = max(i-j+1, res)

	return res

#print(qn12d("aba",1))
#print(qn12d("aaa",1))
#print(qn12d("aaa",0))

def qn12e(arr):
	prev, cur = -1, 0
	res = 0
	for t in range(len(arr)):
		if arr[t] == 0:
			res = max(res, prev+current+1)
			prev = cur
			cur = 0
		else:
			cur +=1

	res = max(res, prev+current+1)

	return res

arr =[0]
#print(qn12f(arr))

arr =[1,1,0]
#print(qn12f(arr))

arr =[1,1,0,1,1,1,0]
#print(qn12f(arr))

def qn12f(arr, k):
	pass

def qn12g(arr):

	counter = 1
	current = arr[0]

	for i in range(1, len(arr)):
		if counter == 0:
			counter += 1
			current = arr[i]
		elif arr[i] != current:
			counter  -=1
		elif arr[i] == current:
			counter += 1

	# still have to validate this current value
	counter = 0
	for n in arr:
		if n == current:
			counter += 1

	return current if counter > len(arr)//2 else -1

#print(qn10([1,2,5,9, 5, 9, 5, 5, 5]))
#print(qn10([3,1,7,1,1,7,7,3,7,7,7]))
#print(qn10([3,2,3,1,1,4,5,3,7,7,7]))

"""
13. 2 pointers in 2 arr technique

a. Given n non-negative integers a1, a2, ..., an,
where each represents a point at coordinate (i, ai).
'n' vertical lines are drawn such that the two endpoints of line i is at (i, ai) and (i, 0).
Find two lines, which together with x-axis forms a container, such that the
container contains the most water.
Your program should return an integer
which corresponds to the maximum area of water that can be contained
 ( Yes, we know maximum area instead of maximum volume sounds weird. But this is
 2D plane we are working with for simplicity ).
  Note: You may not slant the container.

b1. Smallest Difference: Given two arrays of integers, compute the pair of values (one value in each
array) with the smallest (non-negative) difference. Return the difference.

b2. Word Distance: You have a large text file containing words. Given any two words, find the shortest
distance (in terms of number of words) between them in the file. If the operation will be repeated
many times for the same file (but different pairs of words), can you optimize your solution?

c. compare 2 arrays with backspace chracters

d.One Away: There are three types of edits that can be performed on strings:
	insert a character,
	remove a character
	and replace a character.
 Given two strings, write a function to check if they are within one edit of each other. ( 1 or less)
"""

def qn13a(array):

	length = len(array) - 1

	i, j = 0, length
	out = -sys.maxsize - 1
	while i != j:
		area = min(array[i], array[j]) * length
		if area > out:
			out = area
		if array[i] < array[j]:
			i += 1
		else:
			j -= 1
		length -= 1

	return out

def qn13b1(arr1, arr2):
	arr1.sort()
	arr2.sort()

	out = sys.maxsize
	i,j = 0,0
	while i < len(arr1) and j < len(arr2):
		out = min(out, abs(arr1[i] - arr2[j]))
		if arr1[i] > arr2[j]:
			j += 1
		else:
			i += 1

	while i < len(arr1) and arr1[i] < arr2[-1]:
		out = min(out, abs(arr1[i] - arr2[-1]))
		i += 1

	while j < len(arr2) and arr1[-1] > arr2[j]:
		out = min(out, abs(arr1[-1] - arr2[j]))
		j += 1

	return out

def qn13b2(w1, w2, file):
	location1 = []
	location2 = []
	for i in range(len(file)):
		if file[i] == w1:
			location1.append(i)
		elif file[i] == w2:
			location2.append(i)

	if len(location2) == 0 or len(location1) == 0:
		return -1

	i,j = 0,0
	res = abs(location1[i] - location2[j])

	while i < len(location1) and j < len(location2):
		res = min(res, abs(location1[i] - location2[j]))
		if location1[i] < location2[i]:
			i += 1
		else:
			j += 1

	while i < len(location1) and abs(location1[i] - location2[-1]) <= res:
		res = min(res, abs(location1[i] - location2[-1]))
		i += 1

	while j < len(location2) and abs(location1[-1] - location2[j]) <= res:
		res = min(res, abs(location1[-1] - location2[j]))
		j += 1

	return res

def qn13c(array1, array2):
	i, j = len(array1) - 1, len(array2) - 1

	count_of_backspace_1 = 0
	count_of_backspace_2 = 0

	while i >= 0  and j >= 0:
		while (i >= 0 and array1[i] == '\b') or (i >= 0 and count_of_backspace_1 > 0):
			if array1[i] ==  '\b':
				count_of_backspace_1 += 1
			else:
				count_of_backspace_1 -= 1
			i -= 1

		while (j >= 0 and array2[j] == '\b') or (j >= 0 and count_of_backspace_2 > 0):
			if array2[j] ==  '\b':
				count_of_backspace_2 += 1
			else:
				count_of_backspace_2 -= 1
			j -= 1

		if array1[i] != array2[j]:
			return False

		i -= 1
		j -= 1

	if i != j:
		return False

	return True

array1 = ['a','b', '\b', 'c']
array2 = ['a','b', 'c', '\b' ,'\b', 'c', 'd', '\b']

#print(qn75(array1, array2))

def qn13d(s1, s2):

	if abs(len(s1) - len(s2)) > 1:
		return False

	i = 0
	j = 0
	counter = 0
	while i < len(s1) and j < len(s2):
		if s1[i] != s2[j]:
			counter += 1
			if counter > 1:
				return False

			if len(s1) < len(s2):
				j += 1
			else:
				i += 1
		else:
			i += 1
			j += 1

	if i < len(s1) or j < len(s2):
		counter += 1

	return counter == 1

s= ""
t =""
#print(qn13d(s, t))

s= ""
t ="a"
#print(qn13d(s, t))

s= "ab"
t ="cd"
#print(qn13d(s, t))

s = "ab"
t ="cab"
#print(qn13d(s, t))

s= "a"
t ="ac"
#print(qn13d(s, t))

"""
14. mark arr technique

a. Each element of an int array points to the another element,
 eventually creating a cycle. Starting at an index, find the length of the cycle.

Examples:
Input: array = [1, 0], 0
Output: 2
Input: array = [1, 2, 1],0
Output: 2
Note how element 3 is not part of the cycle
Input:array = [1, 3, 0, 4, 1], 0
Output:3
Note how element 0 is used to find the cycle, but is not part of the cycle count.

Note: the element does not point to itself

b. You are given an array of positive and negative integers. If a number n at an index is positive,
then move forward n steps. Conversely, if it's negative (-n), move backward n steps.
Assume the first element of the array is forward next to the last element,
and the last element is backward next to the first element.
 Determine if there is a loop in this array.
  A loop starts and ends at a particular index with more than 1 element along the loop.
   The loop must be "forward" or "backward'.

Example 1: Given the array [2, -1, 1, 2, 2], there is a loop, from index 0 -> 2 -> 3 -> 0.
Example 2: Given the array [-1, 2], there is no loop.

Note:  given array is guaranteed to contain no element "0".
Can you do it in O(n) time complexity and O(1) space complexity?

= iterate array: for each i, we perform DFS and modify to the array to 0 to mark the visted node

c. Find the repeating and the missing
Given an unsorted array of size n. Array elements are in range from 1 to n. One number from set {1, 2, …n}
is missing and one number occurs twice in array. Find these two numbers.
=> there is no zero so negate works

d. Find duplicate number in unsorted array contains 0 to n-1 in O(1) space
=> negate the value does not work because you might print duplicate values more than 1 and negate does
not work for array with zero

e. Find smallest +ve number not in unsorted array in O(n) time and O(1) space ,
arr can have both -ve and +ve => 2 cases:
    1. 0 and +ve
    2. only +ve

"""

def qn14a(arr, start_index):
	count = 0
	while arr[start_index] >=0:
		count += 1
		temp = arr[start_index]
		arr[start_index] = -1 *count
		start_index = temp

	return count + arr[start_index] +1 # start index has been updated to previous element

#use fast/slow and no need to destroy originial input
def qn14a_alternative(arr, start_index):
	fast,slow = arr[arr[start_index]], arr[start_index]

	while fast != slow:
		fast = arr[arr[fast]]
		slow = arr[slow]

	count = 1
	while arr[slow] != fast:
		slow = arr[slow]
		count +=1
		#print(slow)

	return count

#print(qn14a_alternative([1, 0], 0)) #2
#print(qn14a_alternative([1, 2, 1], 0)) #2
#print(qn14a_alternative([1, 3, 0, 4, 1], 0)) #3

def qn14b(arr):

	for i in range(len(arr)):
		next_index = i
		while arr[next_index] != 0:
			previous = next_index
			next_index = (next_index + arr[next_index])%len(arr) if arr[next_index] >0 else (next_index - arr[next_index])%len(arr)
			if next_index == 0:
				break
			elif arr[next_index] == 0:
				return True
			arr[previous] = 0

	return False

#print(qn14b([2, -1, 1, 2, 2]))
#print(qn14b([-1, 2]))
#print(qn14b([0, 2,3,1]))
#print(qn14b([0, 1,2,3]))

def qn14c(arr):
	repeat = 0
	for i in range(len(arr)):
		if arr[abs(arr[i])-1] > 0:
			arr[abs(arr[i])-1] *= -1
		else:
			repeat = arr[i]

	missing = 0
	for i in range(len(arr)):
		if arr[i] > 0:
			missing = i +1

	return missing, abs(repeat)

#print(qn8b([3, 1, 3]))

#print(qn8b([ 4, 3, 6, 2, 1, 1]))

def qn14d(arr):
	length = len(arr)
	for n in arr:
		current = n%length
		arr[current] += length

	out = []
	for i in  range(len(arr)):
		if arr[i]//n > 1:
			out.append(i)

	return out

#print(qn56([1, 2, 3, 1, 3, 6, 6]))

def qn14e(arr):
    # move +ve to left using pivot idea of quicksort
    j = 0
    for i in range(len(arr)):
        if arr[i] >= 0 and j < len(arr):
            temp = arr[j]
            arr[j] = arr[i]
            arr[i] = temp
            j += 1

    # negate the value of the index that exists, assume 0 and +ve is on left

    for i in range(0, j):
        if abs(arr[i]) < len(arr):  # if zero is excluded, use arr[abs(arr[i]-1)] instead
            arr[abs(arr[i])] = -1 * arr[abs(arr[i])]

    for i in range(0, j):
        if arr[i] > 0:
            return i

    return j

"""
16

a, b print the next smallest and the previous largest number that have the same number of 1 bits in their binary
 representation.

 get next smallest step:
 1. set rightmost position 1, i.e p position, find c0 (number of 0 after p)
  and c1(no.of 1 after p)
 2. clear all bit from p to end
 3. set 1 from right

 get previous largest
 1. compute c1(number of 1s) and c0(number of 0s on left of c1), p = rightmost non-tailing 1
 2. clear from 0 to p
 3. insert c1 +1 from p+1 to the lft

c. Get the next bigger number using the same digits of a number.
Eg, For 123456, next number would be 123465

d. Swap two digits from an integer, the result should be the maximum. For example 3580 -> 8350

e. Arrange elements in array to form the biggest number by concatenating
E.g.: input = [9, 93, 24, 6], the output should be: [9,93,6,24]. This is because if you concatenate all the numbers,
 993624 is the highest number that can be formed.

"""

def qn16a_next_smallest(number):

	n= number

	c0 = 0
	while n!=0 and n&1 == 0:
		c0 += 1
		n >> 1

	c1 = 0
	while n!=0 and n&1==1:
		c1 += 1
		n >> 1

	# set pth bit as 1
	p = c0 + c1
	number =  number | (1 << p)

	number = number & (~((1<<p) - 1)) # clear all bits from right to p
	number = number | ((1 <<(c0-1)) - 1) # set c0-1 1s from right

	return number

def qn16b_previous_largest(number):
	n = number
	c1 = 0
	c0 = 0

	while (n&1 == 1) and n!=0:
		c1 += 1
		n >> 1

	while (n&1 == 0) and n!= 0:
		c0 += 1
		n >> 1

	p = c1 + c0
	number = number & (~(1<<p)) # set pth to be 0

	number = number	& ~((1<<p) - 1) # clear all bits from p+1
	mask = (1 << (c0+1)) - 1
	number = number | mask << (c0-1)

def qn16c(inp):
	pass

def qn16d(n):
	string = str(n)
	max_digit = 0
	max_index = len(string) - 1

	left_index = None
	right_index = None
	for i in range(len(string) -1, -1, -1):
		if int(string[i]) > max_digit:
			max_digit = int(string[i])
			max_index = i
		elif int(string[i]) < max_digit:
			left_index = i
			right_index = max_index

	if left_index == None:
		return n

	out = ""
	out += string[:left_index]
	out += string[right_index]
	out += string[left_index+1:right_index]
	out += string[left_index]
	out += string[right_index+1:]
	return int(out)

#print(qn16d(3580))

def qn16e(arr):
	return sorted(arr, key=functools.cmp_to_key(customized_sort), reverse=True)

def customized_sort(a, b):
	ab = str(a) + str(b)
	ba = str(b) + str(a)

	if int(ab) == int(ba):
		return 0
	elif int(ab) > int(ba):
		return 1
	else:
		return -1

"""
8. Duplicate and missing

d. Find the Missing Number
You are given a list of n-1 integers and these integers are in the range of 1 to n.
 There are no duplicates in list. One of the integers is missing in the list.
  Write an efficient code to find the missing integer.

e. Find Two Missing Numbers
Given an array of n unique integers where each element in the array is in range [1, n].
 The array has all distinct elements and size of array is (n-2).
  Hence Two numbers from the range are missing from this array. Find the two missing numbers.

Examples:

Input  : arr[] = {1, 3, 5, 6}, n = 6
Output : 2 4

Input : arr[] = {1, 2, 4}, n = 5
Output : 3 5

Input : arr[] = {1, 2}, n = 4
Output : 3 4

h. Remove only Alternate Duplicate characters from a char array you have to do it in Place. Like keeping only the odd occurences of each character. including space

Example: Input: “you got beautiful eyes”
Output: ”you gtbeaiful es”
Allowed Time Complexity was O(n)
and Space Complexity was O(1)

=> define characters of 256, this is consider O(1) since the space is constant

i.
given two string. one has exactly one more letter than the other, find this letter, the input must be valid.
    e.g. s1:'abbc', s2:'bcbad' return d

=> orders are not the same but the letters are same

j. 1D array [1,1,2,3,3,6,6,33,33,99,99], n=odd number
find number that is not duplicate, only 1 number

"""

def qn8d_sum_technique(arr):
	n = len(arr) + 1
	res = n*(n+1)/2
	for n in arr:
		res -= n
	return res

def qn8d_xor_technique(arr):
	n1, n2 = 0, 0

	for n in arr:
		n1 ^= n

	for n in range(1, n+1):
		n2 ^= n

	#using the fact that a^a =0 and a^0 =a
	return n1^n2

arr = [1, 2, 4, 6, 3, 7, 8]
#print(qn8d_xor_technique(arr, 5))

def qn8e_xor_technique(arr, n):
	n1, n2 = 0, 0

	for i in arr:
		n1 ^= i

	for i in range(1, n+1):
		n2 ^= i

	xor = n1^n2

	rightmost_set_bit = xor & ~(xor-1)

	#now separating by 2 groups where each missing group belongs to either 1
	# group 1:  the rightmost set bit is set
	# group 2: the rightmost set bit is not set
	x, y = 0, 0

	for i in arr:
		if i&rightmost_set_bit == rightmost_set_bit:
			x = x^i
		else:
			y = y^i

	for i in range(1,n+1):
		if i&rightmost_set_bit == rightmost_set_bit:
			x = x^i
		else:
			y = y^i

	return x, y

arr = [1, 2, 3]
#print(qne_xor_technique(arr, 5))

def qn8i(s1, s2):
	res = 0
	for c in s1:
		res ^= ord(c)

	for c in s2:
		res ^= ord(c)

	return string.ascii_lowercase[res-97]

s1= 'abbc'
s2= 'bcbad'

#print(qn8h(s1, s2))

def qn8j(arr):

	if arr[0] != arr[1]:
		return arr[0]

	if arr[-1] != arr[-2]:
		return arr[-1]

	l,r = 0, len(arr)-1
	m = l + (r-l)//2

	while l < r:
		if arr[m] != arr[m+1]:
			if m-1 >= 0 and arr[m-1] != arr[m]:
				return arr[m]
			else:
				r = m

		elif arr[m] == arr[m+1]:
			l = m

		m = l + (r-l)//2

	return arr[l]

#print(qn8j([1,1,2,3,3]))
#print(qn8j([1,1,2,3,3,4,4,5,5]))
#print(qn8j([1,1,2,2,3,3,4,5,5]))
#print(qn8j([1,2,2,2,3,3,4,5,5]))
#print(qn8j([1,1,2,2,3,3,4,4,5]))

"""
11. trie and suffix tree

a.  Multi Search: Given a string b and an array of smaller strings T, design a method to search b for
each small string in T.

T = {"is", "ppi", "hi", "sis", "i", "ssippi"}
b = "mississippi"

=> build trie of all words in list and search for location of each prefix of longer string b => O(bk+kt) where k is longest words in list

b. You are given a dictionary of 3 letter words and have to find a matrix of 3x3 such that each row, column and diagonal forms
 a word in the dictionary.
 The words in the dictionary are sorted and you can assume O(1) time for retrieving a word from the dictionary.

c. Given a dictionary of millions of words, give an algorithm to find the largest
 possible rectangle of letters such that every row forms a word (reading left to right)
 and every column forms a word (reading top to bottom).

https://prismoskills.appspot.com/lessons/Programming_Puzzles/Largest_rectangle_of_words.jsp

d. Given an input string and a dictionary of words, find out if the input string can be segmented
into a space-separated sequence of dictionary words.

Consider the following dictionary
{ i, like, sam, sung, samsung, mobile, ice,
  cream, icecream, man, go, mango}

Input:  ilike
Output: Yes
The string can be segmented as "i like".

e. Print all valid words that are possible using Characters of Array
Given a dictionary and a character array, print all valid words that are possible using characters from the array.

Examples:

Input : Dict - {"go","bat","me","eat","goal",
                                "boy", "run"}
        arr[] = {'e','o','b', 'a','m','g', 'l'}
Output : go, me, goal.

f.  find the largest and overlapping substring that repeats at least k times.

"""

class TrieNode:
	def __init__(self, char=""):
		self.children = [None] *26
		self.is_end_of_word = False
		self.char = char

class Qn11aTrie:
	def __init__(self):
		self.root = TrieNode()

	def insert(self, word):
		current = self.root
		for level in range(len(word)):
			index = ord(word[level]) - 97
			#if current chracter is not present
			if current.children[index] == None:
				current.children[index] = TrieNode(word[level])
			current = current.children[index]

		current.is_end_of_word = True

	# return index of the smaller string if it appears in bigger string
	def search(self, key):

		current = self.root
		res = set()
		current_string = ""

		for level in range(len(key)):

			# if the key is not end yet
			index = ord(key[level]) - 97
			if current.children[index] == None:
				break
			else:
				current = current.children[index]
				current_string += current.char
				if current.is_end_of_word and current_string != "":
					res.add(current_string)

		return res

# build suffix trie of bigger string => O(s*s + t*k ) where k is longest length in T
def qn11a(s, T):
	trie = Qn11aTrie()

	for t in T:
		trie.insert(t)

	res = {}
	for i in range(len(s)):
		if len(trie.search(s[i:])) > 0:
			res[i] = trie.search(s[i:])

	return res
T = ["is", "ppi", "hi", "sis", "i", "ssippi"]
s = "mississippi"
#print(qn11a(s, T))

class Qn11bTrie:
	def __init__(self):
		self.root = TrieNode("")

	#only inser 1st and 2nd prefix, assume every word are of 3
	def insert(self, word):
		current = self.root
		for i in range(len(word)-1):
			index = ord(word[i]) - 97
			if current.children[index] == None:
				current.children[index] = TrieNode(word[i])
			current = current.children[index]
			current.is_end_of_word = True

	# return true if the word is found
	def search(self, word):
		current = self.root
		for i in range(len(word)):
			index = ord(word[i]) - 97
			if current.children[index] == None:
				return False
			current = current.children[index]

		return current.is_end_of_word

def qn11b(files, dictionary):
	trie = Qn11bTrie()

	for w in dictionary:
		trie.insert(w)

	for i in range(len(files)):
		first = files[i]
		if trie.search(first[0]) and trie.search(first[1]) and trie.search(first[2]):

			for j in range(len(files)):
				second = files[j]
				if second != first:
					if trie.search(first[0]+second[0]) and trie.search(first[1]+second[1]) and trie.search(first[2]+second[2]):

						for t in range(len(files)):
							third = files[t]
							if third != first and third != second:
								if first[0]+second[0]+third[0] in dictionary and first[1]+second[1]+third[1] in dictionary and first[2]+second[2]+third[2] in dictionary:
									return True
	return False

files = ["abc", "def", "ghi"]
dictionary = set(["abc", "def", "ghi", "adg","beh","cfi"])
#print(qn11b(files, dictionary))

class QN11dTrie:
	def __init__(self):
		self.root = TrieNode()

	def insert(self, key):
		current = self.root

		for level in range(len(key)):
			index = ord(key[level]) - 97

			#if current chracter is not present
			if not current.children[index]:
				current.children[index] = TrieNode("")

			current = current.children[index]

		current.is_end_of_word = True

	# return True if key present in tree
	def search(self, key):
		current = self.root
		for level in range(len(key)):
			index = ord(key[level]) - 97
			if not current.children[index]:
				return False
			current = current.children[index]

		return current.is_end_of_word and current != None

def qn11d(dictinary, word):
	trie = QN11dTrie()
	for w in dictionary:
		trie.insert(w)

	cache = {}

	return qn11d_helper(word, trie, cache)

def qn11d_helper(word, trie, cache):
	if len(word) == 0:
		return True

	if word not in cache:
		for i in range(len(word)+1):
			print(word[:i],trie.search(word[:i]))
			if trie.search(word[:i]) and qn11d_helper(word[i:], trie, cache):
				return True

		cache[word] = False
	return cache[word]

dictionary = ["i", "like", "sam", "sung", "samsung", "mobile", "ice", "cream", "icecream", "man", "go", "mango"]
w = 'iiiii'
#print(qn11d(dictionary, w))
w = 'ilikesamsung'
#print(qn11d(dictionary, w))
w = 'iliketable'
#print(qn11d(dictionary, w))

"""
13.
Sparse Similarity: The similarity of two documents (each with distinct words) is defined to be the
size of the intersection divided by the size of the union. For example, if the documents consist of
integers, the similarity of {1, 5, 3} and { 1, 7, 2, 3} is 0. 4, because the intersection has size
2 and the union has size 5.
We have a long list of documents (with distinct values and each with an associated ID) where the
similarity is believed to be "sparse:'That is, any two arbitrarily selected documents are very likely to
have similarity O. Design an algorithm that returns a list of pairs of document IDs and the associated
similarity.
Print only the pairs with similarity greater than 0. Empty documents should not be printed at all. For
simplicity, you may assume each document is represented as an array of distinct integers.
EXAMPLE
Input:
13: {14, 15, 100, 9, 3}
16: {32, 1, 9, 3, 5}
19: {15, 29, 2, 6, 8, 7}
24: {7, 10}
Output:
solution
ID1, ID2 	SIMILARITY
13, 19 		0.1
13, 16 		0.25
19, 24 		0.14285714285714285
"""

def qn13(documents):
	# create hash table map each words to where it appears
	words_to_docs = {}

	#O(dw)
	for d in documents:
		for w in documents[d]:
			if w in words_to_docs:
				words_to_docs[w].append(d)
			else:
				words_to_docs[w] = [d]

	# Increment the intersection size of each document pair.
	similarity = {}
	for w in words_to_docs:
		docs_list = words_to_docs[w]
		docs_list.sort() # this is to ensure we do not have duplicate later
		for i in range(len(docs_list)-1):
			for j in range(i+1, len(docs_list)):
				if (docs_list[i], docs_list[j]) in similarity:
					similarity[(docs_list[i], docs_list[j])] += 1
				else:
					similarity[(docs_list[i], docs_list[j])] = 1

	for pair in similarity:
		intersection = similarity[pair]
		doc1 = pair[0]
		doc2 = pair[1]
		union = len(documents[doc1]) + len(documents[doc2]) - intersection
		similarity[pair] = intersection/union

	return similarity

documents = {}
documents[13] = [14, 15, 100, 9, 3]
documents[16] = [32, 1, 9, 3, 5]
documents[19] = [15, 29, 2, 6, 8, 7]
documents[24] = [7, 10]
#print(qn13(documents))

""""
13.
Game of Life
  Go to Discuss
According to the Wikipedia's article: "The Game of Life, also known simply as Life, is a cellular automaton devised by the British mathematician John Horton Conway in 1970."

Given a board with m by n cells, each cell has an initial state live (1) or dead (0). Each cell interacts with its eight neighbors (horizontal, vertical, diagonal) using the following four rules (taken from the above Wikipedia article):

Any live cell with fewer than two live neighbors dies, as if caused by under-population.
Any live cell with two or three live neighbors lives on to the next generation.
Any live cell with more than three live neighbors dies, as if by over-population..
Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction.
Write a function to compute the next state (after one update) of the board given its current state. The next state is created by applying the above rules simultaneously to every cell in the current state, where births and deaths occur simultaneously.

Example:

Input:
[
  [0,1,0],
  [0,0,1],
  [1,1,1],
  [0,0,0]
]
Output:
[
  [0,0,0],
  [1,0,1],
  [0,1,1],
  [0,1,0]
]
Follow up:

Could you solve it in-place? Remember that the board needs to be updated at the same time: You cannot update some cells first and then use their updated values to update other cells.
In this question, we represent the board using a 2D array. In principle, the board is infinite, which would cause problems when the active area encroaches the border of the array. How would you address these problems?

"""
def qn13(self, board):

	row = len(board)
	col = len(board[0])

	for i in range(row):
		for j in range(col):
			n = board[i][j]
			live_neighbors = self.counter(board, i, j)
			if self.is_alive(board, (i,j)):
				if live_neighbors in [2,3]:
					board[i][j] = 3
				else:
					board[i][j] = 1
			else:
				if live_neighbors == 3:
					board[i][j] = 2
	for i in range(row):
		for j in range(col):
			if board[i][j] & 2 == 2:
				board[i][j] = 1
			else:
				board[i][j] = 0
	return board

def counter(self, board, i, j):
	counter = 0

	for pair in [(i-1,j-1),(i+1,j+1),(i+1,j),(i-1,j),(i,j+1),(i,j-1),(i+1,j-1),(i-1,j+1)]:
		if self.is_alive(board, pair):
			counter += 1

	return counter

def is_alive(self, board, pair):
	(i,j) = pair
	if  i >=0 and i < len(board):
		if j >=0 and j < len(board[0]):
			if board[i][j] & 1 == 1:
				return True
	return False

"""
14.

a. Generate all palindromic numbers less than n
Find all numbers less than n, which are palindromic. Numbers can be printed in any order.

Examples :

Input : n = 12
Output : 1, 2, 3, 4, 5, 6, 7, 8, 9, 11

Input : n = 104
Output : 1, 2, 3, 4, 5, 6, 7, 8, 9, 11,
         22, 33, 44, 55, 66, 77, 88, 99, 101

b. Given, a number 'n' generate all palindromes of that length.
=> we can start from 10^m where m = (n-1)//2+1 if n is odd else m == n//2
=> iterate from 10^m

a.Given the number n, find all mirror imaged palindromes (consisting of digits {1-9} only) from
length 1 to length 'n'. Here mirror image palindrome referred to flipping the numer,
i.e., 101 as well as 609 is considered a palindrome.

c. Minimum insertions to form shortest palindrome
Given a string S, determine the least number of characters that should be added on to the left side of S so that the complete string becomes a palindrome.
Examples:

Input: S = "LOL"
Output: 0
LOL is already a palindrome

Input: S = "JAVA"
Output: 3
We need to add 3 characters to form AVAJAVA.
"""
def qn14a(n):

	res = []
	# create even
	for j in range(2):
		i = 1
		while create_palindrome(i, j%2) < n:
			res.append(create_palindrome(i, j%2))
			i += 1

	return res

def create_palindrome(inp, is_even):
	n = inp
	palin = inp

	if is_even == False:
		n = n//10

	while n >0:
		palin = palin * 10 + (n%10)
		n = n//10
	return palin

#print(qn14a(104))

def qn14d(s):
	j = len(s)
	while j  >= 0:
		if is_palindrom(s[:j]):
			break
		j-=1

	added = "".join(s[i] for i in range(len(s)-1, j -1,-1))
	return len(s) - j, added+s

def is_palindrom(s):
	i,j = 0, len(s)-1

	while i < j:
		if s[i] != s[j]:
			return False
		i += 1
		j -= 1

	return True

s = "java"
#print(qn12(s))

s = "lol"
#print(qn12(s))

s = "abcd"
#print(qn12(s))

s = "aacecaaa"
#print(qn12(s))

s = ""
#print(qn12(s))

s = "aaba"
#print(qn12(s))

"""
15. median

a. find the median from 2 sorted arrays : same size n

b. find the median from 2 sorted arrays : different size

c. There is a row of seats. Assume that it contains N seats adjacent to each other.
 There is a group of people who are already seated in that row randomly.
  i.e. some are sitting together & some are scattered.

An occupied seat is marked with a character 'x' and an unoccupied seat is marked with a dot ('.')

Now your target is to make the whole group sit together i.e. next to each other,
 without having any vacant seat between them in such a way that the total
  number of hops or jumps to move them should be minimum.

Return minimum value % MOD where MOD = 10000003
Here is the row having 15 seats represented by the String (0, 1, 2, 3, ......... , 14) -

              . . . . x . . x x . . . x . .

Now to make them sit together one of approaches is -
                  . . . . . . x x x x . . . . .

Following are the steps to achieve this -
1 - Move the person sitting at 4th index to 6th index -
    Number of jumps by him =   (6 - 4) = 2

2 - Bring the person sitting at 12th index to 9th index -
    Number of jumps by him = (12 - 9) = 3

So now the total number of jumps made =
    ( 2 + 3 ) % MOD =
    5 which is the minimum possible jumps to make them seat together.

There are also other ways to make them sit together but the number of jumps will exceed 5 and that will not be minimum.

For example bring them all towards the starting of the row i.e. start placing them from index 0.
In that case the total number of jumps will be
    ( 4 + 6 + 6 + 9 )%MOD
    = 25 which is very costly and not an optimized way to do this movement
 """

def qn15a(arr1, arr2):
	median1 = find_median_in_sorted_arr(arr1, 0, len(arr1))
	median2 = find_median_in_sorted_arr(arr2, 0, len(arr2))

	l1,r1 = 0, len(arr1)
	l2,r2 = 0, len(arr2)

	while median1 != median2 and r1-l1>2 and r2-l2 >2:
		m1 = l1 + (r1-l1)//2
		m2 = l2 + (r2-l2)//2

		if median1 > median2:
			r1 = m1+1
			l2 = m2

		elif median1 < median2:
			l1 = m1
			r2 = m2+1

		median1 = find_median_in_sorted_arr(arr1, l1, r1)
		median2 = find_median_in_sorted_arr(arr2, l2, r2)

	if median1 == median2:
		return median1
	return (max(arr1[l1], arr2[l2]) + min(arr1[r1-1], arr2[r2-1]))/2

def find_median_in_sorted_arr(arr, l, r):
	middle = l+(r-l)//2

	if len(arr[l:r])%2 == 1:
		return arr[middle]
	else:
		previous = middle-1
		return (arr[middle] + arr[previous])/2

arr1 = [1, 12, 15, 26, 38]
arr2 = [2, 13, 17, 30, 45]
#print(qn19(arr1, arr2))

arr1 = [1, 2, 3, 6]
arr2 = [4, 6, 8, 10]
#print(qn19(arr1, arr2))

def qn15c(seats):

	out = []
	for i in range(len(seats)):
		if seats[i] == 'x':
			out.seats[i]

	med = statistics.median(out)
	res = 0

	for n in out:
		if n != med:
			res += abs(n - med) -1

	return res

"""
17. Data stream

b1 - Reservoir sampling :Random Set: Write a method to randomly generate a set of m integers from an array of size n. Each
element must have equal probability of being chosen

b2.
Select a random number from stream, with O(1) space
Given a stream of numbers, generate a random number from the stream. You are allowed to use only O(1)
space and the input is in the form of stream, so can’t store the previously seen numbers.

c.  Numbers are randomly generated and stored into an (expanding) array.
 How would you keep track of the median?

d. Compute a reasonable approximation to a “streaming median” of a potentially infinite sequence of integers.

e. Rank from Stream: Imagine you are reading in a stream of integers. Periodically, you wish to be able
to look up the rank of a numberx (the number of values less than or equal to x). lmplement the data
structures and algorithms to support these operations. That is, implement the method track ( int
x), which is called when each number is generated, and the method getRankOfNumber(int
x), which returns the number of values less than or equal to x (not including x itself).
EXAMPLE
Stream (in order of appearance): 5, 1, 4, 4, 5, 9, 7, 13, 3
getRankOfNumber(l) 0
getRankOfNumber(3) = 1
getRankOfNumber(4) 3

"""
def qn15b1(arr, k):
	res = [0] * k
	for i in range(len(arr)):
		if i < k:
			res[i] = arr[i]
		else:
			rand = random.randint(0,i) # include i
			if rand < k:
				res[ran] = arr[i]

	return res

# assume return list of res from list of sequence
def qn15b2(seq):
	res = [0] * len(seq)
	count = 0

	for i in range(len(seq)):
		count += 1
		if i == 0:
			res[i] = seq[i]
		else:
			rand = random.randint(0,count-1)

			if rand == count - 1:
				res[i] = seq[i]
			else:
				res[i] = res[i-1]

	return res

seq = [1,2,3,4,5]
#print(qn15b2(seq))

def qn15c(arr):
	min_heap_of_large = []
	heapq.heapify(min_heap_of_large)
	max_heap_of_small = []
	heapq.heapify(max_heap_of_small)

	out = []
	for n in arr:
		if len(max_heap_of_small) > 0 and n < max_heap_of_small[0] *(-1):
			heapq.heappush(max_heap_of_small, n *(-1))
			if len(max_heap_of_small) == len(min_heap_of_large) + 2:
				current = heapq.heappop(max_heap_of_small) * (-1)
				heapq.heappush(min_heap_of_large, current)
		else:
			heapq.heappush(min_heap_of_large, n)
			if len(min_heap_of_large) == len(max_heap_of_small) + 2:
				current = heapq.heappop(min_heap_of_large) * (-1)
				heapq.heappush(max_heap_of_small, current)

		# calculate median
		if len(max_heap_of_small) == len(min_heap_of_large):
				out.append((max_heap_of_small[0] * (-1)+ min_heap_of_large[0])/2)
		elif len(max_heap_of_small) > len(min_heap_of_large):
			out.append(max_heap_of_small[0] * (-1))
		else:
			out.append(min_heap_of_large[0])
		print(min_heap_of_large)
		print(max_heap_of_small)

	return out

arr = [1,2,5,4,0]
#print(qn1(arr))

def qn15d(seq):
	m = 0
	for next_number in seq:
		m += 1 if m < next_number else -1

		yield m

class RankNode:
	def __init__(self, val, left_size):
		self.val = val
		self.left_size = 0
		self.left = None
		self.right = None

	def insert(self, d):
		if d <= self.val:
			if self.left != None:
				self.left.insert(d)
			else:
				self.left = RankNode(d)
				self.left_size += 1
		else:
			if self.right != None:
				self.right.insert(d)
			else:
				self.right = RankNode(d)

	def get_rank(self, d):
		if d == self.val:
			return self.left_size
		elif d < self.val:
			return self.left.get_rank(d) if self.left != None else 0
		else:
			right_size = self.right.get_rank(d) if self.right != None else 0
			return self.left_size + 1

def qn15g_get_rank(root, x):
	return root.get_rank(x)

def qn15g_insert(root,x):
	if root == None:
		root = RankNode(x)
	else:
		root.insert(x)


# scirpt used in the project "Plowing"
import os, sys
from collections import defaultdict, namedtuple

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
from sklearn.cluster import KMeans

colors = cm.Set1.colors

Graph = namedtuple("Graph", ["vertices", "edges", "adj_matrix"])

def build_graph(vertices, single_edges, double_edges):
    edges = gather_edges(single_edges, double_edges)
    adj_matrix = create_adj_matrix(vertices, edges)
    return Graph(vertices, edges, adj_matrix)

def gather_edges(single_edges, double_edges):
    edges = set(single_edges)
    edges.update(double_edges)
    edges.update((j, i) for i, j in double_edges)
    return edges

def create_adj_matrix(vertices, edges):
    adj_matrix = np.zeros((30, 30))
    for i, j in edges:
        adj_matrix[i, j] = np.linalg.norm(vertices[i] - vertices[j])
    return 5 * adj_matrix

def show_graph(G, *, directed=False):
    plt.figure(dpi=120)

    for i, j in G.edges:
        x1, y1 = G.vertices[i]
        x2, y2 = G.vertices[j]
        if directed:
            plt.arrow(
                x1,
                y1,
                x2 - x1,
                y2 - y1,
                color="k",
                head_length=20,
                head_width=12,
                length_includes_head=True,
                aa=True,
            )
        else:
            plt.plot([x1, x2], [y1, y2], color="k")

    plt.scatter(G.vertices[:, 0], G.vertices[:, 1], c="b", s=20, zorder=3)

    for i, v in enumerate(G.vertices):
        plt.annotate(i, v + 10)

    plt.ylim((100, 700))
    plt.xlim((70, 1200))

    plt.savefig(os.path.join("wyniki", "dzielnica.png"))
    plt.show()

def partition_graph(G, n_clusters, *, directed=False, show=True):
    clustering = KMeans(n_clusters=n_clusters, random_state=0).fit(G.vertices)

    partition = defaultdict(set)
    for i, j in G.edges:
        p1, p2 = G.vertices[i], G.vertices[j]
        n1, n2 = clustering.predict(np.vstack([p1, p2]))
        if n1 == n2:
            partition[n1].add(i)
            partition[n1].add(j)
        else:
            d1 = np.linalg.norm(p2 - clustering.cluster_centers_[n1])
            d2 = np.linalg.norm(p1 - clustering.cluster_centers_[n2])
            if d1 <= d2:
                partition[n1].add(i)
                partition[n1].add(j)
            else:
                partition[n2].add(i)
                partition[n2].add(j)

    partition = {k: sorted(vs) for k, vs in partition.items()}

    if show:
        show_partition(G, partition, directed=directed)

        plt.scatter(G.vertices[:, 0], G.vertices[:, 1], c="k", s=20, zorder=3)
        plt.scatter(
            clustering.cluster_centers_[:, 0],
            clustering.cluster_centers_[:, 1],
            marker="x",
            c=clustering.predict(clustering.cluster_centers_),
            zorder=3,
            cmap="Set1",
            vmin=0,
            vmax=8,
        )

        plt.savefig(os.path.join("wyniki", str(n_clusters), "podział.png"))

        plt.show()

    return partition

def show_partition(G, partition, *, directed=False):
    plt.figure(dpi=120)

    for i, j in G.edges:
        for k, vs in partition.items():
            if i in vs and j in vs:
                break
        x1, y1 = G.vertices[i]
        x2, y2 = G.vertices[j]
        if directed:
            plt.arrow(
                x1,
                y1,
                x2 - x1,
                y2 - y1,
                color=colors[k],
                head_length=20,
                head_width=12,
                length_includes_head=True,
            )
        else:
            plt.plot([x1, x2], [y1, y2], color=colors[k])

    for i, v in enumerate(G.vertices):
        plt.annotate(i, v + 10)

    plt.ylim((100, 700))
    plt.xlim((70, 1200))

def score(G, partition, wa, wc, va, cd, f):
    ts = {}  # potrzebny czas dla pługu k
    ds = {}  # dystans do przebycia dla pługu k
    eps = {}  # ścieżka Eulera dla pługu k
    for k, vs in partition.items():
        sub_adj_matrix = G.adj_matrix[vs][:, vs]

        d = np.sum(sub_adj_matrix)
        t = d / va
        ep = eulerian_path(sub_adj_matrix)
        ts[k] = t
        ds[k] = d
        eps[k] = [vs[i] for i in ep]

    for k in partition:
        print("Podgraf", k)
        print("Ściezka Eulera:")
        print(*eps[k], sep=" -> ")
        print(f"Całkowity dystans do przebycia: {round(ds[k], 2)} m")
        print(f"Potrzebny czas: {round(ts[k], 2)} s\n")

    zr = max(ts.values())  # czas odśnieżania
    zc = sum(ts.values())  # czas użytkowania pługów

    print(f"Całkowity czas odśnieżania: {round(zr, 2)} s")
    print(f"Całkowity czas użytkowania pługów: {round(zc, 2)} s\n")

    objective = wa * zr + wc * (cd * zc + f * len(partition))

    print(f"Funkcja celu: {round(objective, 2)}")

    return objective

def eulerian_path(adj_matrix):
    # algorytm Hierholzera
    edges = {i: list(adj_matrix[i].nonzero()[0]) for i in range(len(adj_matrix))}

    curr_path = [0]
    circuit = []
    curr_v = 0
    while len(curr_path) != 0:
        if len(edges[curr_v]) != 0:
            curr_path.append(curr_v)
            curr_v = edges[curr_v].pop()
        else:
            circuit.append(curr_v)
            curr_v = curr_path[-1]
            curr_path.pop()
    circuit.reverse()

    return circuit

def main(G, n_clusters, *, va, cd, f, wa, wc, directed):
    if str(n_clusters) not in os.listdir("wyn"):
        os.mkdir(os.path.join("wyn", str(n_clusters)))
        sys.stdout = open(
            os.path.join("wyn", str(n_clusters), "wynik.txt"), "w", encoding="UTF-8"
        )

        partition = partition_graph(G, n_clusters, directed=True)
        return score(G, partition, wa, wc, va, cd, f)

if __name__ == "__main__":
    vertices = np.array(
        [
            (119, 410),
            (233, 417),
            (237, 259),
            (357, 269),
            (353, 417),
            (358, 544),
            (494, 409),
            (633, 311),
            (510, 218),
            (782, 320),
            (798, 204),
            (711, 194),
            (791, 150),
            (930, 198),
            (930, 149),
            (1061, 187),
            (935, 315),
            (1083, 312),
            (1118, 439),
            (1084, 491),
            (949, 457),
            (789, 459),
            (633, 466),
            (695, 609),
            (551, 621),
            (950, 626),
            (947, 556),
            (944, 499),
            (1055, 543),
            (1035, 617),
        ]
    )
    double_edges = [
        (0, 1),
        (1, 2),
        (1, 4),
        (4, 3),
        (4, 5),
        (4, 6),
        (6, 7),
        (7, 8),
        (7, 9),
        (9, 10),
        (9, 16),
        (10, 11),
        (10, 12),
        (10, 13),
        (13, 14),
        (13, 15),
        (15, 17),
        (13, 16),
        (16, 17),
        (17, 18),
        (18, 19),
        (19, 28),
        (28, 29),
        (29, 25),
        (25, 26),
        (26, 28),
        (26, 27),
        (27, 19),
        (27, 20),
        (20, 18),
        (20, 21),
        (21, 22),
        (22, 6),
        (22, 24),
        (22, 23),
        (23, 25),
    ]
    single_edges = []

    G = build_graph(vertices, single_edges, double_edges)
    show_graph(G, directed=True)

    n_clusters = 3
    va, cd, f = 4, 1, 400
    wa, wc = 1, 2
    directed = True

    main(G, n_clusters, va=va, cd=cd, f=f, wa=wa, wc=wc, directed=directed)