# -*- coding: utf-8 -*-
# Description: unbound netdata python.d module
# Author: Austin S. Hemmelgarn (Ferroin)
# SPDX-License-Identifier: GPL-3.0-or-later

import os
import sys

from copy import deepcopy

from bases.FrameworkServices.SocketService import SocketService
from bases.loaders import YamlOrderedLoader

PRECISION = 1000

ORDER = [
    'queries',
    'recursion',
    'reqlist',
]

CHARTS = {
    'queries': {
        'options': [None, 'Queries Processed', 'queries', 'Unbound', 'unbound.queries', 'line'],
        'lines': [
            ['ratelimit', 'ratelimited', 'absolute', 1, 1],
            ['cachemiss', 'cache_miss', 'absolute', 1, 1],
            ['cachehit', 'cache_hit', 'absolute', 1, 1],
            ['expired', 'expired', 'absolute', 1, 1],
            ['prefetch', 'prefetched', 'absolute', 1, 1],
            ['recursive', 'recursive', 'absolute', 1, 1]
        ]
    },
    'recursion': {
        'options': [None, 'Recursion Timings', 'seconds', 'Unbound', 'unbound.recursion', 'line'],
        'lines': [
            ['recursive_avg', 'average', 'absolute', 1, PRECISION],
            ['recursive_med', 'median', 'absolute', 1, PRECISION]
        ]
    },
    'reqlist': {
        'options': [None, 'Request List', 'items', 'Unbound', 'unbound.reqlist', 'line'],
        'lines': [
            ['reqlist_avg', 'average_size', 'absolute', 1, 1],
            ['reqlist_max', 'maximum_size', 'absolute', 1, 1],
            ['reqlist_overwritten', 'overwritten_requests', 'absolute', 1, 1],
            ['reqlist_exceeded', 'overruns', 'absolute', 1, 1],
            ['reqlist_current', 'current_size', 'absolute', 1, 1],
            ['reqlist_user', 'user_requests', 'absolute', 1, 1]
        ]
    }
}

# These get added too if we are told to use extended stats.
EXTENDED_ORDER = ['cache']

EXTENDED_CHARTS = {
    'cache': {
        'options': [None, 'Cache Sizes', 'items', 'Unbound', 'unbound.cache', 'stacked'],
        'lines': [
            ['cache_message', 'message_cache', 'absolute', 1, 1],
            ['cache_rrset', 'rrset_cache', 'absolute', 1, 1],
            ['cache_infra', 'infra_cache', 'absolute', 1, 1],
            ['cache_key', 'dnssec_key_cache', 'absolute', 1, 1],
            ['cache_dnscss', 'dnscrypt_Shared_Secret_cache', 'absolute', 1, 1],
            ['cache_dnscn', 'dnscrypt_Nonce_cache', 'absolute', 1, 1]
        ]
    }
}

# This is used as a templates for the per-thread charts.
PER_THREAD_CHARTS = {
    '_queries': {
        'options': [None, '{longname} Queries Processed', 'queries', 'Queries Processed',
                    'unbound.threads.queries', 'line'],
        'lines': [
            ['{shortname}_ratelimit', 'ratelimited', 'absolute', 1, 1],
            ['{shortname}_cachemiss', 'cache_miss', 'absolute', 1, 1],
            ['{shortname}_cachehit', 'cache_hit', 'absolute', 1, 1],
            ['{shortname}_expired', 'expired', 'absolute', 1, 1],
            ['{shortname}_prefetch', 'prefetched', 'absolute', 1, 1],
            ['{shortname}_recursive', 'recursive', 'absolute', 1, 1]
        ]
    },
    '_recursion': {
        'options': [None, '{longname} Recursion Timings', 'seconds', 'Recursive Timings',
                    'unbound.threads.recursion', 'line'],
        'lines': [
            ['{shortname}_recursive_avg', 'average', 'absolute', 1, PRECISION],
            ['{shortname}_recursive_med', 'median', 'absolute', 1, PRECISION]
        ]
    },
    '_reqlist': {
        'options': [None, '{longname} Request List', 'items', 'Request List', 'unbound.threads.reqlist', 'line'],
        'lines': [
            ['{shortname}_reqlist_avg', 'average_size', 'absolute', 1, 1],
            ['{shortname}_reqlist_max', 'maximum_size', 'absolute', 1, 1],
            ['{shortname}_reqlist_overwritten', 'overwritten_requests', 'absolute', 1, 1],
            ['{shortname}_reqlist_exceeded', 'overruns', 'absolute', 1, 1],
            ['{shortname}_reqlist_current', 'current_size', 'absolute', 1, 1],
            ['{shortname}_reqlist_user', 'user_requests', 'absolute', 1, 1]
        ]
    }
}

# This maps the Unbound stat names to our names and precision requiremnets.
STAT_MAP = {
    'total.num.queries_ip_ratelimited': ('ratelimit', 1),
    'total.num.cachehits': ('cachehit', 1),
    'total.num.cachemiss': ('cachemiss', 1),
    'total.num.zero_ttl': ('expired', 1),
    'total.num.prefetch': ('prefetch', 1),
    'total.num.recursivereplies': ('recursive', 1),
    'total.requestlist.avg': ('reqlist_avg', 1),
    'total.requestlist.max': ('reqlist_max', 1),
    'total.requestlist.overwritten': ('reqlist_overwritten', 1),
    'total.requestlist.exceeded': ('reqlist_exceeded', 1),
    'total.requestlist.current.all': ('reqlist_current', 1),
    'total.requestlist.current.user': ('reqlist_user', 1),
    'total.recursion.time.avg': ('recursive_avg', PRECISION),
    'total.recursion.time.median': ('recursive_med', PRECISION),
    'msg.cache.count': ('cache_message', 1),
    'rrset.cache.count': ('cache_rrset', 1),
    'infra.cache.count': ('cache_infra', 1),
    'key.cache.count': ('cache_key', 1),
    'dnscrypt_shared_secret.cache.count': ('cache_dnscss', 1),
    'dnscrypt_nonce.cache.count': ('cache_dnscn', 1)
}

# Same as above, but for per-thread stats.
PER_THREAD_STAT_MAP = {
    '{shortname}.num.queries_ip_ratelimited': ('{shortname}_ratelimit', 1),
    '{shortname}.num.cachehits': ('{shortname}_cachehit', 1),
    '{shortname}.num.cachemiss': ('{shortname}_cachemiss', 1),
    '{shortname}.num.zero_ttl': ('{shortname}_expired', 1),
    '{shortname}.num.prefetch': ('{shortname}_prefetch', 1),
    '{shortname}.num.recursivereplies': ('{shortname}_recursive', 1),
    '{shortname}.requestlist.avg': ('{shortname}_reqlist_avg', 1),
    '{shortname}.requestlist.max': ('{shortname}_reqlist_max', 1),
    '{shortname}.requestlist.overwritten': ('{shortname}_reqlist_overwritten', 1),
    '{shortname}.requestlist.exceeded': ('{shortname}_reqlist_exceeded', 1),
    '{shortname}.requestlist.current.all': ('{shortname}_reqlist_current', 1),
    '{shortname}.requestlist.current.user': ('{shortname}_reqlist_user', 1),
    '{shortname}.recursion.time.avg': ('{shortname}_recursive_avg', PRECISION),
    '{shortname}.recursion.time.median': ('{shortname}_recursive_med', PRECISION)
}

# Used to actually generate per-thread charts.
def _get_perthread_info(thread):
    sname = 'thread{0}'.format(thread)
    lname = 'Thread {0}'.format(thread)
    charts = dict()
    order = []
    statmap = dict()

    for item in PER_THREAD_CHARTS:
        cname = '{0}{1}'.format(sname, item)
        chart = deepcopy(PER_THREAD_CHARTS[item])
        chart['options'][1] = chart['options'][1].format(longname=lname)

        for index, line in enumerate(chart['lines']):
            chart['lines'][index][0] = line[0].format(shortname=sname)

        order.append(cname)
        charts[cname] = chart

    for key, value in PER_THREAD_STAT_MAP.items():
        statmap[key.format(shortname=sname)] = (value[0].format(shortname=sname), value[1])

    return (charts, order, statmap)

class Service(SocketService):
    def __init__(self, configuration=None, name=None):
        # The unbound control protocol is always TLS encapsulated
        # unless it's used over a UNIX socket, so enable TLS _before_
        # doing the normal SocketService initialization.
        configuration['tls'] = True
        self.port = 8935
        SocketService.__init__(self, configuration, name)
        self.ext = self.configuration.get('extended', None)
        self.ubconf = self.configuration.get('ubconf', None)
        self.perthread = self.configuration.get('per_thread', False)
        self.threads = None
        self.order = deepcopy(ORDER)
        self.definitions = deepcopy(CHARTS)
        self.request = 'UBCT1 stats\n'
        self.statmap = deepcopy(STAT_MAP)
        self._parse_config()
        self._auto_config()
        self.debug('Extended stats: {0}'.format(self.ext))
        self.debug('Per-thread stats: {0}'.format(self.perthread))
        if self.ext:
            self.order = self.order + EXTENDED_ORDER
            self.definitions.update(EXTENDED_CHARTS)
        if self.unix_socket:
            self.debug('Using unix socket: {0}'.format(self.unix_socket))
        else:
            self.debug('Connecting to: {0}:{1}'.format(self.host, self.port))
            self.debug('Using key: {0}'.format(self.key))
            self.debug('Using certificate: {0}'.format(self.cert))

    def _auto_config(self):
        if self.ubconf and os.access(self.ubconf, os.R_OK):
            self.debug('Unbound config: {0}'.format(self.ubconf))
            conf = YamlOrderedLoader.load_config_from_file(self.ubconf)[0]
            if self.ext is None:
                if 'extended-statistics' in conf['server']:
                    self.ext = conf['server']['extended-statistics']
            if 'remote-control' in conf:
                if conf['remote-control'].get('control-use-cert', False):
                    self.key = self.key or conf['remote-control'].get('control-key-file')
                    self.cert = self.cert or conf['remote-control'].get('control-cert-file')
                    self.port = self.port or conf['remote-control'].get('control-port')
                else:
                    self.unix_socket = self.unix_socket or conf['remote-control'].get('control-interface')
        else:
            self.debug('Unbound configuration not found.')
        if not self.key:
            self.key = '/etc/unbound/unbound_control.key'
        if not self.cert:
            self.cert = '/etc/unbound/unbound_control.pem'
        if not self.port:
            self.port = 8953

    def _generate_perthread_charts(self):
        tmporder = list()
        for thread in range(0, self.threads):
            charts, order, statmap = _get_perthread_info(thread)
            tmporder.extend(order)
            self.definitions.update(charts)
            self.statmap.update(statmap)
        self.order.extend(sorted(tmporder))

    def check(self):
        # Check if authentication is working.
        self._connect()
        result = bool(self._sock)
        self._disconnect()
        # If auth works, and we need per-thread charts, query the server
        # to see how many threads it's using.  This somewhat abuses the
        # SocketService API to get the data we need.
        if result and self.perthread:
            tmp = self.request
            if sys.version_info[0] < 3:
                self.request = 'UBCT1 status\n'
            else:
                self.request = b'UBCT1 status\n'
            raw = self._get_raw_data()
            for line in raw.splitlines():
                if line.startswith('threads'):
                    self.threads = int(line.split()[1])
                    self._generate_perthread_charts()
                    break
            if self.threads is None:
                self.info('Unable to auto-detect thread counts, disabling per-thread stats.')
                self.perthread = False
            self.request = tmp
        return result

    @staticmethod
    def _check_raw_data(data):
        # The server will close the connection when it's done sending
        # data, so just keep looping until that happens.
        return False

    def _get_data(self):
        raw = self._get_raw_data()
        data = dict()
        tmp = dict()
        for line in raw.splitlines():
            stat = line.split('=')
            tmp[stat[0]] = stat[1]
        for item in self.statmap:
            if item in tmp:
                data[self.statmap[item][0]] = float(tmp[item]) * self.statmap[item][1]
        return data


from PIL import Image
import sys, os
import datetime
import random

# print(parr)
thresh = 5

def val(c, hue):
	if hue:
		return brightness(c);
	return gethue(c);

def brightness(c):
	return (c[0] + c[1] + c[2])/3;

def cont(st,curr, hue):
	return abs(val(st, hue) - val(curr, hue)) <= thresh;

def gethue(col):
	hsv = RGB_2_HSV(col);
	return hsv[0];

def sort(carr,hue, flipbool):#sort the array by brightness
	flip = 1;
	if flipbool:
		flip = posorneg();#if they want alternating, we assign random value
	if hue:
		return sorted(carr, key=lambda el: gethue(el) * flip);#sort by brightness
	# return sorted(carr, key=lambda el: gethue(el[0]));#sort by brightness
	return sorted(carr, key=lambda el: brightness(el) * flip);#sort by brightness

def posorneg():
	if random.random() > 0.5:
		return -1;
	return 1;

def RGB_2_HSV(RGB):# RGB-HSL code taken from http://stackoverflow.com/a/24153899/2009336
    ''' Converts an integer RGB tuple (value range from 0 to 255) to an HSV tuple '''

    # Unpack the tuple for readability
    R, G, B = RGB

    # Compute the H value by finding the maximum of the RGB values
    RGB_Max = max(RGB)
    RGB_Min = min(RGB)

    # Compute the value
    V = RGB_Max;
    if V == 0:
        H = S = 0
        return (H,S,V)

    # Compute the saturation value
    S = 255 * (RGB_Max - RGB_Min) // V

    if S == 0:
        H = 0
        return (H, S, V)

    # Compute the Hue
    if RGB_Max == R:
        H = 0 + 43*(G - B)//(RGB_Max - RGB_Min)
    elif RGB_Max == G:
        H = 85 + 43*(B - R)//(RGB_Max - RGB_Min)
    else: # RGB_MAX == B
        H = 171 + 43*(R - G)//(RGB_Max - RGB_Min)

    return (H, S, V)

def magic(hue, thresh=50, flip=True):#if hue is true, we sort by hue

	if len(sys.argv) > 1:
		fname = sys.argv[1];
	else:
		fname = "n.jpg"
	img = Image.open(fname);

	parr = img.load()

	for i in range(img.size[0]):	# for every pixel:
		start = 0;
		length = int(random.random() * img.size[1] / 20 + img.size[1] / 10)
		for j in range(img.size[1]):

			# parr[i,j] = (i, j, 100) # set the colour accordingly

			tp = parr[i,j];

			if (not cont(tp, parr[i,start], hue) and abs(start - j) > length) or j + 1 == img.size[1]:#broke the chain or last pixel
				shades = [];
				for e in range(start, j):
					shades.append(parr[i,e]);

				newshades = sort(shades[:], hue, flip);
				for e in range(len(newshades)):
					parr[i,e+start] = newshades[e];

				start = j+1
				length = int(random.random() * img.size[1] / 20 + img.size[1] / 10)

			# parr[i,j] = (0,100,100)

			# if (len(shades) < 100 or cont(parr[i,start], tp)) and len(shades) < 500:#if it's in threshold, add to array
			# 	shades.append( (tp, i, j) );
			# else:#we're done, apply sorted array
			# 	newshades = sort(shades);

			# 	for ind in range(len(shades)):#element is (color, i, j)
			# 		parr[newshades[ind][1],newshades[ind][2]] = newshades[ind][0];#applies new colors

			# 	shades = [];#clear old shades
			# 	start = j;
	img.show()

	detail = "brightness"
	if hue:
		detail = "hue"

	img.save(sys.argv[1] + str(datetime.datetime.now()).replace(":","") + detail + ".jpg", "JPEG");

magic(True);
magic(False);# a lot more smooth

# img = Image.merge(img.mode, parr)


#!/usr/bin/env python
# coding: utf-8

import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression

def getmacros():
    '''
    Returns a dataframe of excess returns from macro factors
    '''
    xls_file = r'C:\Users\bmcdonald\Desktop\MSCI Factors.xlsm'
    xls = pd.ExcelFile(xls_file)
    macros = xls.parse('AssetClass', index_col=0, header=None, skiprows=1,
                       names='MKT INT CRD HY CUR RF'.split())
    macros['CRD'] = 0.5*macros.CRD + 0.5*macros.HY  #TurnsC CRD returns into half IG half HY
    macros.drop('HY', axis=1, inplace=True)
    macros['RF'] = macros['RF'] / 12  #3m treasuries are annualized, so this converts to monthly
    macros.index = pd.to_datetime(macros.index).strftime('%Y-%m')
    macros = isolate_credit(macros)  #Residualizes credit returns
    macros.dropna(inplace=True)
    return macros

def isolate_credit(df, rolling=120, halflife=36):
    '''
    Takes df of macro factors as well as optional rolling periods, model (wls), and halflife arguments
    Returns df of macro factors that have isolated credit returns
    '''

    weights = [1 * (0.5 ** (1 / halflife)) ** i for i in range(rolling)][::-1]

    wls = LinearRegression()

    betas = [wls.fit(df[['MKT', 'INT']].iloc[i:i + rolling],
                     df['CRD'].iloc[i:i + rolling],
                     sample_weight=weights).coef_
             for i in np.arange(df.shape[0] - rolling)]

    mkt_crd_betas = [b[0] for b in betas]
    int_crd_betas = [b[1] for b in betas]

    # This section uses betas to adjust credit and commodity returns to isolate those factor returns
    df = df.iloc[rolling:].copy()
    df.loc[:, 'CRD'] = df['CRD'] - df['MKT'] * mkt_crd_betas - df['INT'] * int_crd_betas

    return df

def macro_prep(return_series, macros, start=None, end=None):
    returns = pd.DataFrame(return_series)

    # Opens macro factors, concats return series, subs rf, drops rf

    df = pd.concat([macros, returns], axis=1, sort=False)
    df.iloc[:, -1] = df.iloc[:, -1] - df.RF

    # Adjust Date range here if applicable
    df = df.loc[start:end]
    df.dropna(inplace=True)

    ex_macros = df.drop('RF', axis=1)

    # Creates separate df for excess returns
    ex_returns = ex_macros.iloc[:, -1]
    ex_returns = pd.DataFrame(ex_returns)
    assert ex_returns.shape[0] > 0, 'Manager returns df is empty'

    ex_macros = ex_macros.iloc[:, :4]
    assert ex_macros.shape[0] > 0, 'Macro returns df is empty'

    return ex_returns, ex_macros

# Opens Excel file with returns
xls_file = r'C:\Users\bmcdonald\Desktop\OMG Manager Returns.xlsx'
xls = pd.ExcelFile(xls_file)

# his section sets month-end date of dats
import datetime
today = datetime.date.today()
first = today.replace(day=1)
lastMonth = first - datetime.timedelta(days=1)
date = lastMonth.strftime("%Y-%m")

# Pulls macro factor data and sets ols model
macros = getmacros()
ols = LinearRegression()

# Creates an empty df for betas and errors
beta_df = pd.DataFrame(columns=['MKT INT CRD CUR'.split()])
excess_returns_df = pd.DataFrame()
predict_df = pd.DataFrame()
error_df = pd.DataFrame()

# Loops through each sheet of returns
for category in 'Index Fund Alt'.split():

    cat = xls.parse(category, index_col=0, skiprows=1)
    cat.index = pd.to_datetime(cat.index).strftime('%Y-%m')
    name = cat.columns  #Creates list of series names

    start = cat.index[120]  #Sets start date to 10 years ago

    betas = []

    #Loops through each series name
    for n in name:
        # Uses factorprep to calc excess manager and factor returns
        ex_returns, ex_macros = macro_prep(cat[n], macros, start)

        model = ols.fit(ex_macros.values,
                        ex_returns.values)

        beta = model.coef_.flatten()
        betas.append(beta)

        errors = ex_returns - model.predict(ex_macros)
        predictions = ex_returns - errors

        excess_returns_df = pd.concat([excess_returns_df, ex_returns], axis=1, sort=False)
        predict_df = pd.concat([predict_df, predictions], axis=1, sort=False)
        error_df = pd.concat([error_df, errors], axis=1, sort=False)

    df = pd.DataFrame(np.stack(betas), columns=['MKT INT CRD CUR'.split()], index=name)

    beta_df = pd.concat([beta_df, df], sort=False)

path = r'O:\Investment Research\Ben\Tools and Concepts WIP\Macro Factors'

beta_df.to_excel(path + f'\Macro Betas.xlsx')

import os
newpath = r'O:\Investment Research\Ben\Tools and Concepts WIP\Macro Factors\\' + date
if not os.path.exists(newpath):
    os.makedirs(newpath)

beta_df.to_excel(newpath + f'\ {date} - Macro Betas.xlsx')

excess_returns_df.sort_index(inplace=True)
predict_df.sort_index(inplace=True)
error_df.sort_index(inplace=True)

excess_returns_df.to_excel(newpath + f'\ {date} - Excess Returns.xlsx')
predict_df.to_excel(newpath + f'\ {date} - Model Predictions.xlsx')
error_df.to_excel(newpath + f'\ {date} - Errors.xlsx')
error_df.corr().to_excel(newpath + f'\ {date} - Macro Error Correlation.xlsx')


#!/usr/bin/python3
"""Adds the State object “Louisiana” to the database.

Here we use Session as interface to the database.

Args:
    user (str): mysql username
    pass (str): mysql password
    database (str): mysql database name
"""
import sys
from model_state import Base, State
from sqlalchemy.orm import Session
from sqlalchemy import create_engine

if __name__ == "__main__":
    engine = create_engine('mysql+mysqldb://{}:{}@localhost/{}'
                           .format(sys.argv[1], sys.argv[2],
                                   sys.argv[3]), pool_pre_ping=True)

    Base.metadata.create_all(engine)
    session = Session(engine)

    louisiana = State(name='Louisiana')
    session.add(louisiana)
    session.commit()
    print(louisiana.id)
    session.close()


from collections import deque

def solution(cacheSize, cities):
    cache = deque(maxlen=cacheSize)
    answer = 0

    for c in cities:
        c = c.lower()

        if c in cache:
            cache.remove(c)
            answer += 1
        else:
            answer += 5

        cache.append(c)

    return answer


# Used X_test, y_test, and model.predict(X_test) to calculate MSE and R2

from sklearn.metrics import mean_squared_error

MSE = mean_squared_error(y_test_Half, predictions_Half)
r2 = model_Half.score(X_test_Half, y_test_Half)

print(f"MSE: {MSE}, R2: {r2}")

# create lists of MSE and r2 values for each model

boston_models.append('Half')
boston_mse.append(MSE)
boston_r2.append(r2)


import logging
from typing import List, Literal

from dash import ctx, dcc
from dash.exceptions import MissingCallbackContextException
from plotly import graph_objects as go

try:
    from pydantic.v1 import Field, PrivateAttr, validator
except ImportError:  # pragma: no cov
    from pydantic import Field, PrivateAttr, validator

import vizro.plotly.express as px
from vizro import _themes as themes
from vizro.managers import data_manager
from vizro.models import Action, VizroBaseModel
from vizro.models._action._actions_chain import _action_validator_factory
from vizro.models._components._components_utils import _process_callable_data_frame
from vizro.models._models_utils import _log_call
from vizro.models.types import CapturedCallable

logger = logging.getLogger(__name__)

class Graph(VizroBaseModel):
    """Wrapper for `dcc.Graph` to visualize charts in dashboard.

    Args:
        type (Literal["graph"]): Defaults to `"graph"`.
        figure (CapturedCallable): See [`CapturedCallable`][vizro.models.types.CapturedCallable].
        actions (List[Action]): See [`Action`][vizro.models.Action]. Defaults to `[]`.
    """

    type: Literal["graph"] = "graph"
    figure: CapturedCallable = Field(..., import_path=px)
    actions: List[Action] = []

    # Component properties for actions and interactions
    _output_property: str = PrivateAttr("figure")

    # Re-used validators
    _set_actions = _action_validator_factory("clickData")
    _validate_callable = validator("figure", allow_reuse=True)(_process_callable_data_frame)

    # Convenience wrapper/syntactic sugar.
    def __call__(self, **kwargs):
        kwargs.setdefault("data_frame", data_manager._get_component_data(str(self.id)))
        fig = self.figure(**kwargs)

        # Remove top margin if title is provided
        if fig.layout.title.text is None:
            fig.update_layout(margin_t=24)

        # Possibly we should enforce that __call__ can only be used within the context of a callback, but it's easy
        # to just swallow up the error here as it doesn't cause any problems.
        try:
            # At the moment theme_selector is always present so this if statement is redundant, but possibly in
            # future we'll have callbacks that do Graph.__call__() without theme_selector set.
            if "theme_selector" in ctx.args_grouping.get("external", {}):
                fig = self._update_theme(fig, ctx.args_grouping["external"]["theme_selector"]["value"])
        except MissingCallbackContextException:
            logger.info("fig.update_layout called outside of callback context.")
        return fig

    # Convenience wrapper/syntactic sugar.
    def __getitem__(self, arg_name: str):
        # pydantic discriminated union validation seems to try Graph["type"], which throws an error unless we
        # explicitly redirect it to the correct attribute.
        if arg_name == "type":
            return self.type
        return self.figure[arg_name]

    @_log_call
    def build(self):
        # The empty figure here is just a placeholder designed to be replaced by the actual figure when the filters
        # etc. are applied. It only appears on the screen for a brief instant, but we need to make sure it's
        # transparent and has no axes so it doesn't draw anything on the screen which would flicker away when the
        # graph callback is executed to make the dcc.Loading icon appear.
        return dcc.Loading(
            dcc.Graph(
                id=self.id,
                figure=go.Figure(
                    layout={
                        "paper_bgcolor": "rgba(0,0,0,0)",
                        "plot_bgcolor": "rgba(0,0,0,0)",
                        "xaxis": {"visible": False},
                        "yaxis": {"visible": False},
                    }
                ),
                config={
                    "autosizable": True,
                    "frameMargins": 0,
                    "responsive": True,
                },
                className="chart_container",
            ),
            color="grey",
            parent_className="loading-container",
        )

    @staticmethod
    def _update_theme(fig: go.Figure, theme_selector: bool):
        # Basically the same as doing fig.update_layout(template="vizro_light/dark") but works for both the call in
        # self.__call__ and in the update_graph_theme callback.
        fig["layout"]["template"] = themes.dark if theme_selector else themes.light
        return fig


from typing import Dict, List, Literal, Optional

from dash import ClientsideFunction, Input, Output, State, clientside_callback, dcc, html

try:
    from pydantic.v1 import Field, PrivateAttr, validator
except ImportError:  # pragma: no cov
    from pydantic import Field, PrivateAttr, validator

from vizro.models import Action, VizroBaseModel
from vizro.models._action._actions_chain import _action_validator_factory
from vizro.models._components.form._form_utils import (
    set_default_marks,
    validate_max,
    validate_slider_value,
    validate_step,
)
from vizro.models._models_utils import _log_call

class RangeSlider(VizroBaseModel):
    """Numeric multi-selector `RangeSlider`.

    Can be provided to [`Filter`][vizro.models.Filter] or
    [`Parameter`][vizro.models.Parameter]. Based on the underlying
    [`dcc.RangeSlider`](https://dash.plotly.com/dash-core-components/rangeslider).

    Args:
        type (Literal["range_slider"]): Defaults to `"range_slider"`.
        min (Optional[float]): Start value for slider. Defaults to `None`.
        max (Optional[float]): End value for slider. Defaults to `None`.
        step (Optional[float]): Step-size for marks on slider. Defaults to `None`.
        marks (Optional[Dict[float, str]]): Marks to be displayed on slider. Defaults to `{}`.
        value (Optional[List[float]]): Default start and end value for slider. Must be 2 items. Defaults to `None`.
        title (str): Title to be displayed. Defaults to `""`.
        actions (List[Action]): See [`Action`][vizro.models.Action]. Defaults to `[]`.
    """

    type: Literal["range_slider"] = "range_slider"
    min: Optional[float] = Field(None, description="Start value for slider.")
    max: Optional[float] = Field(None, description="End value for slider.")
    step: Optional[float] = Field(None, description="Step-size for marks on slider.")
    marks: Optional[Dict[float, str]] = Field({}, description="Marks to be displayed on slider.")
    value: Optional[List[float]] = Field(
        None, description="Default start and end value for slider", min_items=2, max_items=2
    )
    title: str = Field("", description="Title to be displayed.")
    actions: List[Action] = []

    # Component properties for actions and interactions
    _input_property: str = PrivateAttr("value")

    # Re-used validators
    _validate_max = validator("max", allow_reuse=True)(validate_max)
    _validate_value = validator("value", allow_reuse=True)(validate_slider_value)
    _validate_step = validator("step", allow_reuse=True)(validate_step)
    _set_default_marks = validator("marks", allow_reuse=True, always=True)(set_default_marks)
    _set_actions = _action_validator_factory("value")

    @_log_call
    def build(self):
        value = self.value or [self.min, self.max]  # type: ignore[list-item]

        output = [
            Output(f"{self.id}_start_value", "value"),
            Output(f"{self.id}_end_value", "value"),
            Output(self.id, "value"),
            Output(f"temp-store-range_slider-{self.id}", "data"),
        ]
        inputs = [
            Input(f"{self.id}_start_value", "value"),
            Input(f"{self.id}_end_value", "value"),
            Input(self.id, "value"),
            State(f"temp-store-range_slider-{self.id}", "data"),
            State(f"{self.id}_callback_data", "data"),
        ]

        clientside_callback(
            ClientsideFunction(namespace="clientside", function_name="update_range_slider_values"),
            output=output,
            inputs=inputs,
        )

        return html.Div(
            [
                dcc.Store(
                    f"{self.id}_callback_data",
                    data={
                        "id": self.id,
                        "min": self.min,
                        "max": self.max,
                    },
                ),
                html.P(self.title) if self.title else None,
                html.Div(
                    [
                        dcc.RangeSlider(
                            id=self.id,
                            min=self.min,
                            max=self.max,
                            step=self.step,
                            marks=self.marks,
                            value=value,
                            persistence=True,
                            persistence_type="session",
                            className="range_slider_control" if self.step else "range_slider_control_no_space",
                        ),
                        html.Div(
                            [
                                dcc.Input(
                                    id=f"{self.id}_start_value",
                                    type="number",
                                    placeholder="start",
                                    min=self.min,
                                    max=self.max,
                                    step=self.step,
                                    value=value[0],
                                    size="24px",
                                    persistence=True,
                                    persistence_type="session",
                                    className="slider_input_field_left"
                                    if self.step
                                    else "slider_input_field_no_space_left",
                                ),
                                dcc.Input(
                                    id=f"{self.id}_end_value",
                                    type="number",
                                    placeholder="end",
                                    min=self.min,
                                    max=self.max,
                                    step=self.step,
                                    value=value[1],
                                    persistence=True,
                                    persistence_type="session",
                                    className="slider_input_field_right"
                                    if self.step
                                    else "slider_input_field_no_space_right",
                                ),
                                dcc.Store(id=f"temp-store-range_slider-{self.id}", storage_type="session"),
                            ],
                            className="slider_input_container",
                        ),
                    ],
                    className="range_slider_inner_container",
                ),
            ],
            className="selector_container",
            id=f"{self.id}_outer",
        )


#!/usr/bin/env python
# -*- coding: utf8 -*-

import os
from glob import glob
import pickle
import pandas as pd
from multiprocessing import Pool, cpu_count
import json
import collections

def loadPythonSyntaxFile(filepath):
    """Loads data stored in a text file, where each line is a python dict.

    Parameters
    ----------
    filepath : str
        Path of file to be read.

    Returns
    -------
    list
        Return a list containing all the dicts from the file.

    Raises
    ------
    FileNotFoundError
    If filepath doesn't point to a .txt-file.
    """
    if not os.path.isfile(filepath) or not filepath.lower().endswith(".txt"):
        raise FileNotFoundError("The file {} doesn't seem to exist".format(filepath))
    localNamespace = dict()
    with open(filepath) as fid:
        cl0 = fid.read()
        cl1 = cl0.replace("\n{", ",\n{")  # Add "," to seperate dicts
        cl2 = cl1.strip()
        cl3 = "data = [" + cl2 + "]"  # Put all lines into a list.
        # set_trace()
        exec(cl3, localNamespace)  # Execute the python command
        return localNamespace["data"]  # Return values from localNamespace-dict

def loadUser2(user, datapath='/lscr_paper/allan/telephone',
              dataFilter=('sms', 'question', 'gps', 'bluetooth', 'screen', 'facebook', 'call')):
    """Loads a users data as dict.

    Parameters
    ----------
    user : str
        Name of user data folder
    datapath : str, optional
        Path to folder which contains user data folder.
    dataFilter : Iterable containing str, optional
        Only return certain datasets from a user. Allowed values are 'sms', 'question',
        'gps', 'bluetooth', 'screen', 'facebook' and 'call'.

    Returns
    -------
    dict
        User data in a dict, maps to None for missing data types.

    Raises
    ------
    ValueError
    If a wrong parameter is passed to dataFilter
    """
    userPath = os.path.join(datapath, user)
    userDict = dict()
    loadedFilekeySet = set()
    datafileList = os.listdir(userPath)

    # Check that dataFilter arguments are valid, raise ValueError if they aren't
    validFilterSet = {'sms', 'question', 'gps', 'bluetooth', 'screen', 'facebook', 'call'}
    if any({el not in validFilterSet for el in dataFilter}):
        raise ValueError("Invalied filter argument provided. Allowed values are %r"
                         % validFilterSet)

    for filename in datafileList:
        filekey = filename.split('.')[0]
        if filekey not in dataFilter:
            continue
        with open(os.path.join(userPath, filename)) as fid:
            userDict.update(json.load(fid))
        loadedFilekeySet.add(filekey)
    for missingFileKey in (set(dataFilter) - loadedFilekeySet):
        userDict[missingFileKey] = None
    return userDict if userDict else None  # Return None if there's no contents in userDict

def loadUser(user, datapath='/lscr_paper/allan/data/Telefon/userfiles',
             dataFilter=('sms', 'question', 'gps', 'bluetooth', 'screen', 'facebook', 'call')):
    """Loads a users data as dict.

    Parameters
    ----------
    user : str
        Name of user data folder
    datapath : str, optional
        Path to folder which contains user data folder.
    dataFilter : Iterable containing str, optional
        Only return certain datasets from a user. Allowed values are 'sms', 'question',
        'gps', 'bluetooth', 'screen', 'facebook' and 'call'.

    Returns
    -------
    dict
        User data in a dict, maps to None for missing data types.

    Raises
    ------
    ValueError
    If a wrong parameter is passed to dataFilter
    """
    # Turn "/foo/bar/baz/gps_log.txt" -> "gps"
    _filepath2dictkey = lambda el: el.rsplit(os.path.sep, maxsplit=1)[1].split("_")[0]
    userPath = os.path.join(datapath, user)
    userDict = dict()
    loadedFilekeySet = set()
    datafileList = glob(os.path.join(userPath, "*.txt"))

    # Check that dataFilter arguments are valid, raise ValueError if they aren't
    validFilterSet = {'sms', 'question', 'gps', 'bluetooth', 'screen', 'facebook', 'call'}
    if any({el not in validFilterSet for el in dataFilter}):
        raise ValueError("Invalied filter argument provided. Allowed values are %r"
                         % validFilterSet)

    for filepath in datafileList:
        dictkey = _filepath2dictkey(filepath)
        if dictkey not in dataFilter:
            continue
        userDict[dictkey] = loadPythonSyntaxFile(filepath)
        loadedFilekeySet.add(dictkey)
    for missingFileKey in (set(dataFilter) - loadedFilekeySet):
        userDict[missingFileKey] = None
    return userDict if userDict else None  # Return None if there's no contents in userDict

def _loadUserHandler(userSpec):
    """
    Helper function for loadUserParallel.
    """
    if len(userSpec) == 2:
        user, alias = userSpec
        return (alias, loadUser2(user))
    else:
        user, alias, dataFilter = userSpec
        return (alias, loadUser2(user, dataFilter=dataFilter))

def loadUserParallel(userSpec, n=None):
    """Loads users in parallel.

    Parameters
    ----------
    userSpec : tuple
        Contains (username, useralias) or (username, useralias, dataFilter).
        See the documentation for loadUser2 (which uses theese arguments).
    n : None, optional
        Number of provessor cores to use when loading the users in parallel.
        Default is 16, but will fall back to number of cores minus 1 if 16 cores
        aren't avaiable.

    Returns
    -------
    dict
        Dictionary representation of all users. Can easily be converted to pandas DataFrame.
    """
    if n is None:
        n = 16 if 16 < cpu_count() else cpu_count() - 1
    pool = Pool(n)
    users = None
    try:
        users = pool.map(_loadUserHandler, userSpec)
    finally:
        pool.terminate()
    return dict(users)

def loadUserPhonenumberDict(useralias=None, filepath="/lscr_paper/allan/phonenumbers.p"):
    """Loads the dictionary which relates a phone number to a user.
    Format is phoneID -> userID

    Parameters
    ----------
    useralias : Useralias, optional
        An Useralias instance, may be None.
    filepath : str, optional
        Path to the phonenumbers.p pickle-file.

    Returns
    -------
    dict
        phoneID -> userID
    """
    with open(filepath, "rb") as fid:
        data = pickle.load(fid)
        if useralias is not None:
            try:
                return {k: useralias[v] for (k, v) in data.items()}
            except KeyError:
                return {useralias[v]: k for (k, v) in data.items()}
        return data

def getUserList(datapath='/lscr_paper/allan/data/Telefon/userfiles'):
    """Get list of users conforming to the standard naming convention at a given path.

    Parameters
    ----------
    datapath : str, optional
        Path to search for users.

    Returns
    -------
    list
        List of valid usernames.
    """
    folders = (pth for pth in os.listdir(datapath) if os.path.isdir(os.path.join(datapath, pth)))
    userFolders = (fld for fld in folders if (len(fld) == 30))
    validUsernames = [fld for fld in userFolders if not (set(fld) - set('0123456789abcdef'))]
    return validUsernames

class Useralias(object):
    """Class used to rename user from the human ureadable hash values.
    Works like a dict for lookups, but return a new (sequentially generetad) alias for
    unknown users, whereas previous look-up users aliases is saved, and returned when
    asked for.

    Attributes
    ----------
    formatStr : str
        String template for returned aliases. Must be compatible with
    i : int
        Holds value for the next user in the sequence
    userdct : dict
        Holds previously seen hash -> alias pairs the .format()-method
    """

    def __init__(self, formatStr="u{:04d}"):
        super(Useralias, self).__init__()
        self.formatStr = formatStr
        self._json_file_path = '/lscr_paper/allan/allan_data/user_aliases.json'
        if not os.path.isfile(self._json_file_path):
            raise FileNotFoundError("The file user_aliases.json could not be found!")
        with open(self._json_file_path) as fid:
            self.userdct = json.load(fid)
        self.i = len(self.userdct)
        self.reversed = dict()

    def __setitem__(self, key, value):
        raise IndexError("The username aliases are frozen!")

    def __getitem__(self, key):
        return self.userdct[key]

    def lookup(self, alias):
        """Reverse lookup: given a useralias, return the username (hash-like string)

        Parameters
        ----------
        alias : str
            useralias, default on the form u0001, u0123, u0435, u1023 and so on....

        Returns
        -------
        dict
            Original username (hash-like), that is, the name of the user data-folder.
        """
        if len(self.reversed) != len(self.userdct):
            self.reversed = {v: k for (k, v) in self.userdct.items()}
        return self.reversed[alias]

def dict2DataFrame(dct, useralias):
    """Convert the dict-based output from loadUser to a DataFrame

    Parameters
    ----------
    dct : dict
        A single communication typy dict from a user, as returned by loadUser.
    useralias : Useralias
        A Useralias-instance or a dict mapping hashlike usernames to a human
        readable format.

    Returns
    -------
    DataFrame
        Pandas DataFrame with 'user' and 'comtype' as the index, and columns:
        - body: Hash of SMS body, NaN for calls
        - duration: Duration of call, NaN for SMS
        - number: Hash of recieving number
        - timestamp: Datetime for when the event occured
        - weekday: Weekday on which the event occured
        - hour: Hour of the day when the envent occured
    """
    df = pd.DataFrame(dct)
    df.rename(columns={'timestamp': 'timeint'}, inplace=True)
    df['timestamp'] = df.timeint.astype('datetime64[s]')  # convert Unix integer timestamps to datetime-objects
    df["weekday"] = df.timestamp.dt.weekday  # Add weekday column
    df["hour"] = df.timestamp.dt.hour  # Add hour column
    df.user = df.user.apply(lambda x: useralias[x])  # convert hex-usernames to enumerated usernames
    # Set comtype and rename 'address' to 'number' for datatype 'sms'
    if 'address' in df.columns:
        df["comtype"] = "sms"  # create the column "comtype", set it to "sms"
        df.rename(columns={'address': 'number'}, inplace=True)
    else:
        df["comtype"] = "call"
    df = df.set_index(["user", "comtype"])  # move columns "user" and "comtype" to (multi)index
    return df

def _user2DataFrameHandler(args):
    """Helper function for users2DataFrame"""
    return dict2DataFrame(*args)

def users2DataFrame(dct, useralias, n=None):
    """Convert a dict of users into a DataFrame using multiple CPU cores.

    Parameters
    ----------
    dct : dict
        Dict of users like the one returned by loadUserParallel.
    useralias : Useralias
        An Useralias-instance.
    n : None, optional
        Number of CPU cores to use.
        Default is 16, but will fall back to number of cores minus 1 if 16 cores
        aren't avaiable.

    Returns
    -------
    DataFrame
        All the users from dct as a DataFrame, as the one returned from dict2DataFrame.
    """
    if n is None:
        n = 16 if 16 < cpu_count() else cpu_count() - 1
    gen = ((comDct, useralias) for user in dct.values()
                      for comDct in user.values() if comDct)  # noqa
    try:
        pool = Pool(processes=n)
        call = pool.map_async(_user2DataFrameHandler, gen)
        call.wait()
        toConcatenate = call.get()
    finally:
        pool.terminate()
    df = pd.concat(toConcatenate)
    df.sortlevel(level=0, inplace=True)
    return df

def quickSaveHdf5(filepath, *data):
    """Given a path to a HDF5 file, save data to file.
    Data is converted to a pandas Series or DataFrame if it's too high dimmensioal for
    a Series to be used.

    Parameters
    ----------
    filepath : str
        Path to HDF5 file.
    *data
        Data to be saved. Can either be a dict or two iterables containing keys and the
        data to save.

    Raises
    ------
    ValueError
    If *data input is not correct.
    """

    def saveElement(key, data, store):
        """Save 'data' to HDF5 'store' with 'key'.
        Tries to convert everything to a Pandas Series or DataFrame if it's too
        high dimmensioal.

        Parameters
        ----------
        key : str
            Key to save data under.
        data
            Data to save.
        store : HDFStore
            The HDFStore in which the file is saved.
        """
        if not isinstance(data, (pd.DataFrame, pd.Series)):
            try:
                data = pd.Series(data)
                print("Attention! The data {} was convert to a Series".format(key))
            except Exception:
                data = pd.DataFrame(data)
                print("Attention! The data {} was convert to a DataFrame".format(key))
        store[key] = data

    with pd.HDFStore(filepath, mode='w') as store:
        if len(data) == 1 and isinstance(data[0], dict):
            for key, value in data[0].items():
                saveElement(key, value, store)
        # It's two iterables of equal length, containing the data names and the data
        elif all( len(data) == 2,  # noqa
                  all([isinstance(data[i], collections.Iterable) for i in range(2)]),
                  len(data[0] == len(data[1])) ):  # noqa
            for key, value in data:
                saveElement(key, value, store)
        else:
            raise ValueError("Incorrect data input.")

def loadUserBluetooth(userhash, useralias):
    """Function for loading an users bluetooth data.

    Parameters
    ----------
    userhash : str
        Hash value for user (username)
    useralias : Useralias
        Useralias instance.

    Returns
    -------
    pd.DataFrame
        DataFrame with users Bluetooth data.
    """
    def _load_bluetooth(user):
        try:
            with open(f'/lscr_paper/allan/telephone/{user}/bluetooth.json') as fid:
                data = json.load(fid)
            return data['bluetooth']
        except Exception as err:
            print(f"Couldn't read user {user}")
            raise err

    btdata = _load_bluetooth(userhash)
    if not btdata:
        return None
    df = pd.DataFrame(btdata)
    df['timestamp'] = df.timestamp.astype('datetime64[s]')
    df = df[df.bt_mac != '-1']
    # ua = loaders.Useralias()
    if isinstance(useralias, dict):
        df['scanned_user'] = df.scanned_user.map(useralias)
        df['user'] = df.user.map(useralias)
    else:
        df['scanned_user'] = df.scanned_user.map(useralias.userdct)
        df['user'] = df.user.map(useralias.userdct)
    df = df.set_index('timestamp')
    df = df[df.index.year >= 2013]
    return df


# @Author : bamtercelboo
# @Datetime : 2018/3/27 9:50
# @File : stoke.py
# @Last Modify Time : 2019/01/22 19:56
# @Contact : bamtercelboo@{gmail.com, 163.com}

"""
    FILE :  stoke.py
    FUNCTION : None
    EXAMPLE:
        Source:
        中
        TO:
        中: 丨フ一丨
"""

# from handian import Handian
from Stoke.handian import Handian
import urllib.request as urllib2
import urllib
from bs4 import BeautifulSoup
# solve encoding
from imp import reload
import sys
defaultencoding = 'utf-8'
if sys.getdefaultencoding() != defaultencoding:
    reload(sys)
    sys.setdefaultencoding(defaultencoding)

class Stoke(object):
    # dictionary_filepath = "./default_stoke.txt"
    dictionary_filepath = "./Stoke/default_stoke.txt"
    # baiduhanyu_url = 'http://hanyu.baidu.com/zici/s?ptype=zici&wd=%s'
    # hanzi5_url = "http://www.hanzi5.com/bishun/%s.html"
    # hanzi5_url = "http://www.zdic.net/z/?/js/%s.htm"
    handian_url = None

    def __init__(self):
        self.dictionary = {}
        self.read_dictionary()
        self.handian = Handian()

    def read_dictionary(self):
        """
        :return:
        """
        self.dictionary = {}
        with open(self.dictionary_filepath, encoding="UTF-8") as f:
            for line in f:
                line = line.strip("\n")
                line = line.split(" ")
                self.dictionary[line[0]] = line[1:]
        f.close()
        # print(self.dictionary)

    def get_stoke(self, word):
        """
        :param word:
        :return:
        """
        if word in self.dictionary:
            return self.dictionary[word]
        else:
            print("From handian:    word {}".format(word), end=" ")
            self.handian_url = self.handian.get_url(word=word)
            word_utf = word
            word = hex((ord(word)))[2:]
            word = urllib.parse.quote(word)
            return self.get_stoke_from_handian(word_utf)

    def get_stoke_from_handian(self, word):
        """
        :param word:
        :return:
        """
        url = self.handian_url
        print("url", url)
        if url == "http://www.zdic.net/sousuo/":
            return None

        html = self.post_baidu(url)
        # print(html)
        if html is None:
            return None
        char_stoke = self.anlysis_stoke_from_html(html)
        if char_stoke is not None:
            self.dictionary[word] = char_stoke
        # print("char_stoke {}".format(char_stoke))
        return char_stoke

    @staticmethod
    def anlysis_stoke_from_html(html_doc):
        """
        :param html_doc:
        :return:
        """
        soup = BeautifulSoup(html_doc, 'html.parser')
        zh_stoke = soup.find(id="z_i_t2_bis")
        zh_stoke = zh_stoke.contents
        zh_stoke_list = []
        for st in zh_stoke[0]:
            zh_stoke_list.append(st)
        return zh_stoke_list

    def post_baidu(self, url):
        """
        :param url:
        :return:
        """
        try:
            timeout = 10
            # request = urllib2.Request(url)
            request = urllib2.Request(url)
            request.add_header('User-agent', 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36')
            request.add_header('connection','keep-alive')
            request.add_header('referer', url)
            response = urllib2.urlopen(request, timeout=timeout)
            html = response.read()
            response.close()
            return html
        except Exception as e:
            print('URL Request Error:', e)
            return None

if __name__ == "__main__":
    print("extract character stoke from [http://www.zdic.net/]")

    stoke = Stoke()
    print("中", stoke.get_stoke("中"))
    print("王", stoke.get_stoke("王"))
    print("吋", stoke.get_stoke("吋"))
    print("緉", stoke.get_stoke("緉"))


'''

-Medium-
$$$
*Two Pointers*
*Greedy*

You are given an array nums consisting of positive integers.

You can perform the following operation on the array any number of times:

Choose any two adjacent elements and replace them with their sum.
For example, if nums = [1,2,3,1], you can apply one operation to make it [1,5,1].
Return the minimum number of operations needed to turn the array into a palindrome.

Example 1:

Input: nums = [4,3,2,1,2,3,1]
Output: 2
Explanation: We can turn the array into a palindrome in 2 operations as follows:
- Apply the operation on the fourth and fifth element of the array, nums becomes equal to [4,3,2,3,3,1].
- Apply the operation on the fifth and sixth element of the array, nums becomes equal to [4,3,2,3,4].
The array [4,3,2,3,4] is a palindrome.
It can be shown that 2 is the minimum number of operations needed.
Example 2:

Input: nums = [1,2,3,4]
Output: 3
Explanation: We do the operation 3 times in any position, we obtain the array [10] at the end which is a palindrome.

Constraints:

1 <= nums.length <= 105
1 <= nums[i] <= 106

'''

from typing import List

class Solution:
    def minimumOperations(self, nums: List[int]) -> int:
        A, n = nums, len(nums)
        i, j, ans = 0, n-1, 0
        a, b = A[i], A[j]
        while i < j:
            if a == b:
                i += 1
                j -= 1
                a, b = A[i], A[j]
            elif a < b:
                i += 1
                a += A[i]
                ans += 1
            else:
                j -=1
                b += A[j]
                ans += 1

        return ans

if __name__ == "__main__":
    print(Solution().minimumOperations(nums = [4,3,2,1,2,3,1]))
    print(Solution().minimumOperations(nums = [4,3,2,1]))


from enum import Enum

from bin.datatools import TimeWindowFunc
from bin.preprocessing import MinMaxScale, StandardScale

class ScaleType(str, Enum):
    MINMAX = 'minmax'
    STANDARD = 'standardize'

class InsType(str, Enum):
    TRAIN = 'train'
    VAL = 'val'
    TEST = 'test'

def scale_func(data, estimator, ins_type='train'):
    if ins_type == InsType.TRAIN:
        scale_data = estimator.fit(data)

    elif ins_type == InsType.VAL or ins_type == InsType.TEST:
        scale_data = estimator.transform(data)
    else:
        raise TypeError(f'{ins_type} is not support')

    return scale_data

def keras_processing(loader, config):
    dataloader, scale_dict = {}, {}
    if ScaleType.MINMAX == config['scale_type']:
        fscale = MinMaxScale(feature_range=(config['minmax_range']['min'], config['minmax_range']['max']))
        tscale = MinMaxScale(feature_range=(config['minmax_range']['min'], config['minmax_range']['max']))

    if ScaleType.STANDARD == config['scale_type']:
        fscale = StandardScale()
        tscale = StandardScale()

    x_train, y_train = loader['train'][0], loader['train'][1]
    x_test, y_test = loader['test'][0], loader['test'][1]

    x_train, y_train = scale_func(x_train, fscale), scale_func(y_train, tscale)
    x_test, y_test = scale_func(x_test, fscale, 'test'), scale_func(y_test, tscale, 'test')

    x_train, y_train = TimeWindowFunc(x_train, y_train, **config['window_setting']).get_data()
    x_test, y_test = TimeWindowFunc(x_test, y_test, **config['window_setting'],
                                    step=config['window_setting']['target_len']).get_data()

    if 'val' in loader:
        x_val, y_val = loader['val'][0], loader['val'][1]
        x_val, y_val = scale_func(x_val, fscale, 'val'), scale_func(y_val, tscale, 'val')
        x_val, y_val = TimeWindowFunc(x_val, y_val, **config['window_setting']).get_data()

        dataloader['val'] = x_val, y_val

    dataloader['train'] = x_train, y_train
    dataloader['test'] = x_test, y_test
    scale_dict['feature'], scale_dict['target'] = fscale, tscale

    return dataloader, scale_dict


from pathlib import Path

import pandas as pd
from autumn.core.db import Database

from .fetch import KIR_BCG_COV

def preprocess_tb_kir(input_db: Database):

    df = process_kiribati_bcg(KIR_BCG_COV)
    input_db.dump_df("tb_kir_bcg_cov", df)

def process_kiribati_bcg(data_path: Path) -> pd.DataFrame:
    """Processes Kiribati BCG vaccination coverage data
    and dumps it into the parquet db.
    """
    df = pd.read_csv(data_path)
    df.columns = ["year", "prop_coverage"]
    df["prop_coverage"] = df["prop_coverage"] / 100
    df = df.sort_values("year", ascending=True)
    df.reset_index(inplace=True, drop=True)

    return df


from google.appengine.ext import db
import iso8601
from pytz import timezone
from datetime import datetime
from time import time

from web import form

class SPOTUser(db.Model):
	#userid = db.StringProperty(required=True) # this is google.appengine.api.users.get_current_user().user_id()
	# NOTE: using key_name <- userid
	userDispName = db.StringProperty()
	glId = db.StringProperty(required=True) # may be empty string in future for non-SPOT expansion
	userEmail = db.EmailProperty(required=True)
	userWebsite = db.LinkProperty()
	userImage = db.BlobProperty()
	# userIM = db.IMProperty()
	# NOTE: Reference to SPOTUser by SPOTMessageRecord

	@staticmethod
	def buildForm():
		return form.Form(
			form.Textbox("userDispName", form.notnull, description="Display name"),
			form.Textbox("glId", form.notnull, description="SPOT key"),
			form.Textbox("userEmail", form.regexp(r".*@.*", "Must be a valid email address"), description="Email address"),
			form.Textbox("userWebsite", description="Website"),
			form.File("userImage", description="Optional custom user image"),
			# form.Textbox("userIM", description="IM nickname")
			)

# model based on XML schema at http://faq.findmespot.com/index.php?action=showEntry&data=69
# see also included file spot_schema.xsd
class SPOTMessageRecord(db.Model):
	spotuser = db.ReferenceProperty(SPOTUser, collection_name='spotmessages')

	esn = db.StringProperty(required=True)
	esnName = db.StringProperty()
	messageType = db.StringProperty(required=True, choices=["TEST", "TRACK", "HELP", "SOS"])
	messageDetail = db.StringProperty()
	timestamp = db.DateTimeProperty(required=True) # XML is an ISO8601 timestamp string
	timeInGMTSecond = db.IntegerProperty()
	location = db.GeoPtProperty() # combination of two float values
	nearestTown = db.StringProperty()
	nearestTownDistance = db.StringProperty()

	def getSerializeable(self):
		outdict = db.to_dict(self)
		outdict['latitude'] = outdict['location'].lat
		outdict['longitude'] = outdict['location'].lon
		del outdict['location']
		outdict['spotuser'] = str(outdict['spotuser'])
		outdict['timestamp'] = outdict['timestamp'].strftime("%Y-%m-%dT%H:%M:%S")
		outdict['key'] = str(self.key()) # to_dict doesn't actually include this because it's not a property
		return outdict

	@classmethod
	def fromMessageTag(recordClass, msgElement):
		return recordClass( **dictFromMessageTag(msgElement) )

	@staticmethod
	def dictFromMessageTag(msgElement, userkey=None):
		"""
		Generate a dictionary from an XML element, munging types as needed.

		msgElement: an xml.etree.Element object of type Message. see spot_schema.xsd for details.
		userkey: the key identifying the user who owns this message

		"""

		recordDict = {}
		for datum in msgElement:
			recordDict[datum.tag] = datum.text
		if userkey:
			recordDict['spotuser'] = userkey

		utc_tz = timezone('UTC')
		gmt_tz = timezone('GMT')
		recordDict['timestamp'] = iso8601.parse_date(recordDict['timestamp'], gmt_tz).astimezone(utc_tz) # convert timestamp from GMT to UTC
		#utc_dt = iso8601.parse_date(msgElement.find('timestamp').text, gmt_tz).astimezone(utc_tz)
		if 'timeInGMTSecond' not in recordDict:
			recordDict['timeInGMTSecond'] = int( utcdatetime_to_ts(utc_dt) )
		else:
			recordDict['timeInGMTSecond'] = int( recordDict['timeInGMTSecond'] )

		try:
			recordDict['location'] = db.GeoPt( float(recordDict['latitude']), float(recordDict['longitude']) )
			del recordDict['latitude']
			del recordDict['longitude']
		except:
			pass

		return recordDict

	@staticmethod
	def fixFormTypes(inputDict):
		"""
		A dirty little helper method to turn the unicode strings that come from forms into other types
		as necessary."""
		inputDict['timeInGMTSecond'] = int(inputDict['timeInGMTSecond'])
		inputDict['timestamp'] = iso8601.parse_date(inputDict['timestamp'])
		inputDict['spotuser'] = db.Key(encoded=inputDict['spotuser'])
		return inputDict

# dealing with time zones in a pain in the dick
def utcdatetime_to_ts(dt):
   return time.mktime(dt.utctimetuple()) - time.timezone


from pathlib import Path
from argparse import ArgumentParser

def rm_dir(path:Path):
    for subpath in path.iterdir():
        if subpath.is_file():
            subpath.unlink()
        elif subpath.is_dir():
            print(subpath)
            rm_dir(subpath)
            subpath.rmdir()
    path.rmdir()

if __name__ == "__main__":
    parser = ArgumentParser(description="rm all directories in source which contain keyword")
    parser.add_argument("source")
    parser.add_argument("-k", "--keywords", nargs='+')
    parser.add_argument('-n', action="store_true", help="negate")
    args = parser.parse_args()

    source = Path(args.source)
    keywords = args.keywords
    negate = args.n

    if keywords:
        for subpath in source.iterdir():
            if subpath.is_dir():
                names = subpath.name.split("_")
                should_rm = not negate

                for keyword in keywords:
                    if not keyword in names:
                        should_rm = negate
                        break

                if should_rm:
                    rm_dir(subpath)


from collections import deque
import copy
import sys
input = sys.stdin.readline

dx = [-1, 1, 0, 0]
dy = [0, 0, -1, 1]

n, m, fuel = map(int, input().rstrip().split())

board = [list(map(int, input().rstrip().split())) for _ in range(n)]

r, c = map(int, input().rstrip().split())
start = (r-1, c-1)

S = []
E = []
for i in range(m):
    x1, y1, x2, y2 = map(int, input().rstrip().split())
    S.append((x1-1, y1-1))
    E.append((x2-1, y2-1))

def select(taxi):
    global fuel
    if taxi in S:
        return taxi
    visited = [[0]*n for _ in range(n)]
    queue = deque()
    queue.append((taxi))
    visited[taxi[0]][taxi[1]] = 1
    dist = 0
    customer = []
    while queue:
        dist += 1
        if dist >= fuel:
            return 0
        length = len(queue)
        for _ in range(length):
            x, y = queue.popleft()
            for i in range(4):
                nx = x+dx[i]
                ny = y+dy[i]
                if 0 <= nx < n and 0 <= ny < n and board[nx][ny] == 0 and visited[nx][ny] == 0:
                    if (nx, ny) in S:
                        customer.append((nx, ny))
                    queue.append((nx, ny))
                    visited[nx][ny] = 1
        if customer:
            break
    if not customer:
        return 0
    fuel -= dist
    customer.sort(key=lambda x: (x[0], x[1]))
    return customer[0]

def move(customer):
    global fuel
    idx = S.index(customer)
    visited = [[-1]*n for _ in range(n)]
    queue = deque()
    queue.append(customer)
    visited[customer[0]][customer[1]] = 0
    while queue:
        x, y = queue.popleft()
        if visited[x][y] >= fuel:
            return 0
        for i in range(4):
            nx = x+dx[i]
            ny = y+dy[i]
            if 0 <= nx < n and 0 <= ny < n and board[nx][ny] == 0 and visited[nx][ny] == -1:
                queue.append((nx, ny))
                visited[nx][ny] = visited[x][y]+1
                if (nx, ny) == E[idx]:
                    # 연료 충전
                    fuel += visited[nx][ny]
                    S.pop(idx)
                    E.pop(idx)
                    return (nx, ny)
    return 0

for _ in range(m):
    customer = select(start)
    # 선택할 수 있는 손님 없는 경우
    if customer == 0:
        fuel = -1
        break
    # 연료가 부족한 경우
    if fuel < 0:
        fuel = -1
        break
    start = move(customer)
    # 도착지까지 갈 수 없는 경우
    if start == 0:
        fuel = -1
        break
print(fuel)


from typing import Callable

from tsdemux.es import Es
from tsdemux.psi import PsiTableReader

class PmtTableReader(PsiTableReader):

    def __init__(self, pid, program_id,
                 on_pcr_pid_changed: Callable[[int, int], None],
                 on_stream_added: Callable[[int, int, Es], None],
                 on_stream_removed: Callable[[int, int, Es], None]):
        super().__init__(pid, PsiTableReader.TABLE_ID_PMT)
        self.log_prefix = f"[PMT:0x{self.pid:04x}] "
        self.program_id = program_id
        self.prev_streams = {}
        self.streams = {}
        self.pcr_pid = -1
        self.on_pcr_pid_changed = on_pcr_pid_changed
        self.on_stream_added = on_stream_added
        self.on_stream_removed = on_stream_removed

    def check_section_headers(self, table_id: int, section_length: int, ext_id: int) -> bool:
        # additional checks
        if ((section_length >> 10) & 0x3) != 0:
            self.error(f"section length first 2 bits should be 0 got {section_length}")
            return False

        if section_length > 1021:
            self.error(f"section length is too long {section_length}")
            return False

        if self.program_id != ext_id:
            self.error(f"program_id mismatch {ext_id} vs {self.program_id}")
            return False

        return True

    def on_new_version(self, version: int):
        self.streams = {}

    def on_section(self, section_id: int, data: bytearray, crc32: int) -> bool:
        offset = 0
        data_len = len(data)

        if section_id != 0:
            self.warning(f"section id should be 0, got {section_id}")
            return False

        self.verbose(f"section {section_id} len: {data_len}")
        if data_len < 4:
            self.error(f"section length should not be < 4 bytes, got {data_len}")
            return False

        pcr_pid = ((data[offset] & 0x1F) << 8) | (data[offset + 1] & 0xFF)
        if pcr_pid != self.pcr_pid:
            self.info(f"pcr pid is now: {pcr_pid}")
            self.pcr_pid = pcr_pid
            self.on_pcr_pid_changed(self.program_id, self.pcr_pid)

        offset += 2
        data_len -= 2

        program_info_len = ((data[offset] & 0x0F) << 8) | (data[offset + 1] & 0xFF)
        offset += 2
        data_len -= 2

        if (program_info_len >> 10) != 0:
            self.error("first two bits of program info len should be 0")
            return False

        if program_info_len > data_len:
            self.error(f"info len out of bounds {program_info_len} vs {data_len}")
            return False

        self.verbose(f"program_info_len: {program_info_len}")
        # read program info
        if program_info_len > 0:
            # TODO: parse
            offset += program_info_len
            data_len -= program_info_len

        # read program entries
        while data_len >= 5:
            stream_type = data[offset] & 0xFF
            offset += 1

            es_pid = ((data[offset] & 0x1F) << 8) | (data[offset + 1] & 0xFF)
            offset += 2

            info_len = ((data[offset] & 0x0F) << 8) | (data[offset + 1] & 0xFF)
            offset += 2
            data_len -= 5

            if (info_len >> 10) != 0:
                self.error("first two bits of entry info len should be 0")
                return False

            if info_len > data_len:
                self.error(f"info_len out of bounds {info_len} vs {data_len}")
                return False

            self.streams[es_pid] = Es(es_pid, stream_type, data[offset:offset+info_len])

            if info_len > 0:
                offset += info_len
                data_len -= info_len

        if data_len:
            self.warning(f"left after reading {data_len}")

        return True

    def on_table_complete(self):
        self.info(f"============= PMT ({self.current_version}) =============")
        for pid, es in self.streams.items():
            self.info(f"> pid 0x{pid:04x} ==> {es}")
        self.info("===================================")

        for pid in self.streams.keys() - self.prev_streams.keys():
            self.info(f'  [+] pid: 0x{pid:04x} : {self.streams[pid]}')
            self.on_stream_added(self.program_id, pid, self.streams[pid])

        for pid in self.prev_streams.keys() - self.streams.keys():
            self.info(f'  [-] pid: 0x{pid:04x} : {self.prev_streams[pid]}')
            self.on_stream_removed(self.program_id, pid, self.prev_streams[pid])

        for pid in self.streams.keys() & self.prev_streams.keys():
            prev_es = self.prev_streams[pid]
            es = self.streams[pid]
            if prev_es != es:
                self.info(f'  [U] pid: 0x{pid:04x} => es changed from {prev_es} to {es}')
                self.on_stream_removed(self.program_id, pid, self.prev_streams[pid])
                self.on_stream_added(self.program_id, pid, self.streams[pid])

        self.prev_streams = self.streams.copy()


import copy
import torch
import math
# from math import ceil as up

def HeteroAvg(w): # [0]: weight, [1]: dropout probability
    w_glob = copy.deepcopy(w[0][0])
    w_avg = copy.deepcopy(w[0][0])

    '''initialization'''
    for key in w_avg.keys():
        w_avg[key] = 0*w_avg[key]

    for key in w_glob.keys():
        cnt = 0
        for i in range(1, len(w)):
            w_local = copy.deepcopy(w[i][0])
            model_idx = w[i][1]

            if key in w_local.keys():
                cnt += 1
                w_avg[key] += w_local[key]
        if cnt == 0:
            w_avg[key] = w_glob[key]
        else:
            w_avg[key] = w_avg[key] / cnt
    return w_avg

def HeteroAvg_auxnet(w, auxiliary_models):
    # w는 [[aux_net 1 weight 모음],[aux_net 2 weight 모음],[aux_net 3 weight 모음],[aux_net 4 weight 모음],[aux_net 5 weight 모음]]으로 이뤄진 list임
    aux_models = copy.deepcopy(auxiliary_models)
    for idx in range(len(w)):
        if not w[idx]:    # w[i]가 비어있음
            continue
        else:
            num = len(w[idx])   # w[idx]를 사용한 클라이언트 수
            w_avg = copy.deepcopy(w[idx][0])
            # Initialization
            for key in w_avg.keys():
                w_avg[key] = 0*w_avg[key]
            # Aggregation
            for key in w_avg.keys():
                for i in range(num):
                    w_local = copy.deepcopy(w[idx][i])
                    w_avg[key] += w_local[key]
                w_avg[key] = w_avg[key] / num
            aux_models[idx].load_state_dict(w_avg)
    return aux_models

def HeteroAvg_cs(w_c, w_s): # [0]: weight, [1]: dropout probability
    w_c_glob = copy.deepcopy(w_c[0][0])

    w_c_avg = copy.deepcopy(w_c[0][0])
    # w_s_avg = copy.deepcopy(w_s[0][0])

    '''initialization'''
    for key in w_c_avg.keys():
        w_c_avg[key] = 0*w_c_avg[key]

    for key in w_c_glob.keys():
        cnt = 0
        for i in range(1, len(w_c)):    # len(w_c) = 한 라운드에 참여한 클라이언트 수 + 1
            w_c_local = copy.deepcopy(w_c[i][0])
            w_s_local = copy.deepcopy(w_s[i][0])
            if key in w_c_local.keys():
                cnt += 1
                w_c_avg[key] += w_c_local[key]
            elif key in w_s_local.keys():
                cnt += 1
                w_c_avg[key] += w_s_local[key]
        if cnt == 0:
            w_c_avg[key] = w_c_glob[key]
        else:
            w_c_avg[key] = w_c_avg[key] / cnt
    return w_c_avg

def HeteroAvg_new(w, BN_layers, com_layers, sing_layers,bn_keys, args, cs_opt): # [0]: weight, [1]: dropout probability
    w_glob = copy.deepcopy(w[0][0])
    w_avg = copy.deepcopy(w[0][0])

    b = [0] * args.num_models
    for i in range(1, len(w)):
        ind = w[i][1]
        b[ind] += 1
    '''initialization'''
    # 1) w/o bn layer 제외
    for key in w_avg.keys():
        if 'bn' not in key:
            w_avg[key] = 0*w_avg[key]
    # 2) bn layer
    for k in range(len(BN_layers)):
        if b[k]!=0:
            for key in BN_layers[k].keys():
                if 'num_batches_tracked' not in key:
                    BN_layers[k][key] = 0*BN_layers[k][key]

    for i in range(1, len(w)):
        index = w[i][1]
        for key in BN_layers[index].keys():
            if 'num_batches_tracked' not in key:
                BN_layers[index][key] += w[i][0][key]/b[index]
            else:
                BN_layers[index][key] += w[i][0][key]

    '''Averaging'''
    for key in w_avg.keys():
        if 'bn' in key or 'downsample.1' in key:
            pass
        cnt = 0
        for i in range(1, len(w)):
            w_local = copy.deepcopy(w[i][0])
            if key in w_local.keys():
                cnt +=1
                w_avg[key] += w_local[key]
        if cnt!=0:
            w_avg[key] = torch.div(w_avg[key], cnt)
        else:
            w_avg[key] = w_glob[key]
    if cs_opt == 'c':
        for key in BN_layers[-1].keys():
            w_avg[key] = copy.deepcopy(BN_layers[-1][key])
    elif cs_opt == 's':
        for key in BN_layers[0].keys():
            w_avg[key] = copy.deepcopy(BN_layers[0][key])
    return w_avg, BN_layers

def F_Avg(w, bn_keys, args): # [0]: weight, [1]: dropout probability

    w_avg = copy.deepcopy(w[0][0]) # global model

    a = [0]*(len(w)-1) # 10
    acc_num = [len(w)-1]*(args.num_models+1) # [10]*5+1. accumulated number of models.
    b = [0]*args.num_models # 5. number of model idxs for federation.

    for n in range(1, len(w)):
        a[n-1] = w[n][1]

    for i in range(len(b)):
        b[i] = a.count(i)
        acc_num[i+1] = acc_num[i] - b[i]

    zero_idx = 0
    for o in range(1,args.num_models):
        if acc_num[o] == 0:
            break
        zero_idx += 1
    max_p_updated = args.ps[zero_idx] # define a maximum size of model that is updated
    # print(max_p_updated)
    '''
      Initialization
    '''
    for key in w_avg.keys():
        shape = w_avg[key].shape
        if len(shape) == 4:
            if key == 'conv1.weight':
                w_avg[key][0:up(shape[0]*max_p_updated), :, :, :] = 0*w_avg[key][0:up(shape[0]*max_p_updated), :, :, :]
            else: # other conv layers (layer1.0.conv1.weight -> shape = [16, 16, 3, 3]. layer2.0.conv1.weight -> hsape = [32, 16, 3, 3])
                w_avg[key][0:up(shape[0]*max_p_updated), 0:up(shape[1]*max_p_updated), :, :] = 0*w_avg[key][0:up(shape[0]*max_p_updated), 0:up(shape[1]*max_p_updated), :, :]

        elif len(shape) == 2: # linear.weight,
            w_avg[key][:, 0:up(shape[1]*max_p_updated)] = 0*w_avg[key][:, 0:up(shape[1]*max_p_updated)]

        else: # bn1.weight
            w_avg[key] = 0*w_avg[key]
    list_ps = copy.deepcopy(args.ps)
    list_ps.insert(0, 0)
    '''Aggregation'''
    for key in w_avg.keys():
        if key not in bn_keys:
            shape = w_avg[key].shape
            for i in range(1, len(w)):
                index = w[i][1]
                for j in range(index+1):
                    if len(shape) > 1:  # conv1.weight & linear.weight
                        li = up(shape[0]*list_ps[j])
                        hi = up(shape[0]*list_ps[j+1])
                        li2 = up(shape[1]*list_ps[j])
                        hi2 = up(shape[1]*list_ps[j+1])
                        if len(shape) == 4:
                            if key == 'conv1.weight': # w[key][out_ch, in_ch, kernel_size, kernel_size]
                                w_avg[key][li:hi, :, :, :] += w[i][0][key][li:hi, :, :, :]/acc_num[j]
                            else: # other conv layers
                                w_avg[key][li:hi, li2:hi2, :, :] += w[i][0][key][li:hi, li2:hi2, :, :]/acc_num[j]
                                if j!=0:
                                    w_avg[key][0:li, li2:hi2, :, :] += w[i][0][key][0:li, li2:hi2, :, :]/acc_num[j]
                                    w_avg[key][li:hi, 0:li2, :, :] += w[i][0][key][li:hi, 0:li2, :, :]/acc_num[j]
                        elif len(shape) == 2: # linear.weight
                            w_avg[key][:, li2:hi2] += w[i][0][key][:, li2:hi2]/acc_num[j]
                if key == 'linear.bias': # independent of j
                    w_avg[key] += w[i][0][key]/acc_num[0]
        else:
            shape = w_avg[key].shape
            for i in range(1, len(w)):
                index = w[i][1]
                for j in range(index+1):
                    if len(shape) > 0:
                        li = up(shape[0]*list_ps[j])
                        hi = up(shape[0]*list_ps[j+1])
                        w_avg[key][li:hi] += w[i][0][key][li:hi]/acc_num[j]
                    else:
                        w_avg[key] = w_avg[key] +  w[i][0][key]/acc_num[j]
    return w_avg

def up(value):
  return math.ceil(value)