from wrapped import Wrapped
from pylons import c, request, g
from utils import  query_string, timeago
from strings import StringHandler, plurals
from r2.lib.db import operators
from r2.lib.filters import _force_unicode
from pylons.i18n import _
#from r2.config import cache

class MenuHandler(StringHandler):
    """Bastard child of StringHandler and plurals.  Menus are
    typically a single word (and in some cases, a single plural word
    like 'moderators' or 'contributors' so this class first checks its
    own dictionary of string translations before falling back on the
    plurals list."""
    def __getattr__(self, attr):
        try:
            return StringHandler.__getattr__(self, attr)
        except KeyError:
            return getattr(plurals, attr)

# selected menu styles, primarily used on the main nav bar
menu_selected=StringHandler(hot          = _("Popular"),
                            new          = _("What's new"),
                            top          = _("Top scoring"),
                            controversial= _("Most controversial"),
                            saved        = _("Saved"),
                            recommended  = _("Recommended"),
                            promote      = _('Promote'),
                            )

# translation strings for every menu on the site
menu =   MenuHandler(hot          = _('Popular'),
                     new          = _('New'),
                     old          = _('Old'),
                     ups          = _('Ups'),
                     downs        = _('Downs'),
                     top          = _('Top'),
                     more         = _('More'),
                     relevance    = _('Relevance'),
                     controversial  = _('Controversial'),
                     confidence   = _('Best'),
                     interestingness = ('Leading'),
                     saved        = _('Saved'),
                     recommended  = _('Recommended'),
                     rising       = _('Rising'),
                     admin        = _('Admin'),
                     drafts       = _('Drafts'),
                     blessed      = _('Promoted'),
                     comments     = _('Comments'),
                     posts        = _('Posts'),
                     topcomments     = _('Top Comments'),
                     newcomments     = _('New Comments'),
                     leadingsubscribed = _('Leading Subscribed'),
                     leadingcomments = _('Leading Comments'),
                     leadingposts = _('Leading Posts'),

                     # time sort words
                     hour         = _('This hour'),
                     day          = _('Today'),
                     week         = _('This week'),
                     month        = _('This month'),
                     quarter      = _('Last three months'),
                     year         = _('This year'),
                     all          = _('All time'),
                     last         = _('Since last visit'),

                     # "kind" words
                     spam         = _("Spam"),
                     autobanned   = _("Autobanned"),

                     # reddit header strings
                     adminon      = _("Turn admin on"),
                     adminoff     = _("Turn admin off"),
                     prefs        = _("Preferences"),
                     stats        = _("Stats"),
                     submit       = _("Create new article"),
                     submitlink   = _("Submit a new link"),
                     meetupsnew   = _("Add new meetup"),
                     help         = _("Help"),
                     blog         = _("Blog"),
                     logout       = _("Log out"),

                     #reddit footer strings
                     feedback     = _("Feedback"),
                     bookmarklets = _("Bookmarklets"),
                     socialite    = _("Socialite"),
                     buttons      = _("Buttons"),
                     widget       = _("Widget"),
                     code         = _("Code"),
                     mobile       = _("Mobile"),
                     store        = _("Store"),
                     ad_inq       = _("Advertise"),

                     #preferences
                     options      = _('Options'),
                     friends      = _("Friends"),
                     update       = _("Password/email"),
                     delete       = _("Delete"),
                     wikiaccount  = _("Wiki Account"),

                     # messages
                     compose      = _("Compose"),
                     inbox        = _("Inbox"),
                     sent         = _("Sent"),

                     # comments
                     related      = _("Related"),
                     details      = _("Details"),

                     # reddits
                     main         = _("Main"),
                     discussion   = _("Discussion"),
                     wiki         = _("Wiki"),
                     sequences    = _("Sequences"),
                     about        = _("About"),
                     edit         = _("Edit"),
                     banned       = _("Banned"),
                     banusers     = _("Ban users"),

                     popular      = _("Popular"),
                     create       = _("Create"),
                     mine         = _("My reddits"),

                     i18n         = _("Translate site"),
                     promoted     = _("Promoted"),
                     reporters    = _("Reporters"),
                     reports      = _("Reports"),
                     reportedauth = _("Reported authors"),
                     info         = _("Info"),
                     share        = _("Share"),

                     overview     = _("Overview"),
                     submitted    = _("Submitted"),
                     liked        = _("Liked"),
                     disliked     = _("Disliked"),
                     hidden       = _("Hidden"),
                     deleted      = _("Deleted"),
                     reported     = _("Reported"),

                     promote      = _('Promote'),
                     new_promo    = _('New promoted link'),
                     current_promos = _('Promoted links'),
                     )

class Styled(Wrapped):
    """Rather than creating a separate template for every possible
    menu/button style we might want to use, this class overrides the
    render function to render only the <%def> in the template whose
    name matches 'style'.

    Additionally, when rendering, the '_id' and 'css_class' attributes
    are intended to be used in the outermost container's id and class
    tag.
    """
    def __init__(self, style, _id = '', css_class = '', **kw):
        self._id = _id
        self.css_class = css_class
        self.style = style
        Wrapped.__init__(self, **kw)

    def render(self, **kw):
        """Using the canonical template file, only renders the <%def>
        in the template whose name is given by self.style"""
        style = kw.get('style', c.render_style or 'html')
        return Wrapped.part_render(self, self.style, style = style, **kw)

def menu_style(type):
    """Simple manager function for the styled menus.  Returns a
    (style, css_class) pair given a 'type', defaulting to style =
    'dropdown' with no css_class."""
    default = ('select', '')
    d = dict(heavydrop = ('dropdown', 'heavydrop'),
             lightdrop = ('dropdown', 'lightdrop'),
             tabdrop = ('dropdown', 'tabdrop'),
             srdrop = ('dropdown', 'srdrop'),
             flatlist =  ('flatlist', ''),
             tabmenu = ('tabmenu', ''),
             buttons = ('userlinks', ''),
             select  = ('select', ''),
             navlist  = ('navlist', ''),
             dropdown2 = ('dropdown2', ''))
    return d.get(type, default)

class NavMenu(Styled):
    """generates a navigation menu.  The intention here is that the
    'style' parameter sets what template/layout to use to differentiate, say,
    a dropdown from a flatlist, while the optional _class, and _id attributes
    can be used to set individualized CSS."""

    def __init__(self, options, default = None, title = '', type = "dropdown",
                 base_path = '', separator = '|', **kw):
        self.options = options
        self.base_path = base_path
        kw['style'], kw['css_class'] = menu_style(type)

        #used by flatlist to delimit menu items
        self.separator = separator

        # since the menu contains the path info, it's buttons need a
        # configuration pass to get them pointing to the proper urls
        for opt in self.options:
            opt.build(self.base_path)

        # selected holds the currently selected button defined as the
        # one whose path most specifically matches the current URL
        # (possibly None)
        self.default = default
        self.selected = self.find_selected()
        self.enabled = True

        Styled.__init__(self, title = title, **kw)

    def find_selected(self):
        maybe_selected = [o for o in self.options if o.is_selected()]
        if maybe_selected:
            # pick the button with the most restrictive pathing
            maybe_selected.sort(lambda x, y:
                                len(y.bare_path) - len(x.bare_path))
            return maybe_selected[0]
        elif self.default:
            #lookup the menu with the 'dest' that matches 'default'
            for opt in self.options:
                if opt.dest == self.default:
                    return opt

    def __repr__(self):
        return "<NavMenu>"

    def __iter__(self):
        for opt in self.options:
            yield opt

class NavButton(Styled):
    """Smallest unit of site navigation.  A button once constructed
    must also have its build() method called with the current path to
    set self.path.  This step is done automatically if the button is
    passed to a NavMenu instance upon its construction."""
    def __init__(self, title, dest, sr_path = True,
                 nocname=False, opt = '', aliases = [],
                 target = "", style = "plain", dest_params = {}, **kw):

        # keep original dest to check against c.location when rendering
        self.aliases = set(a.rstrip('/') for a in aliases)
        self.aliases.add(dest.rstrip('/'))
        self.dest = dest
        self.dest_params = dest_params

        Styled.__init__(self, style = style, sr_path = sr_path,
                        nocname = nocname, target = target,
                        title = title, opt = opt, **kw)

    def build(self, base_path = ''):
        '''Generates the href of the button based on the base_path provided.'''
        if self.style == "external":
            self.path = self.dest
            self.bare_path = self.dest
            return

        # append to the path or update the get params dependent on presence
        # of opt
        if self.opt:
            p = request.get.copy()
            p[self.opt] = self.dest
        else:
            p = {}
            base_path = ("%s/%s/" % (base_path, self.dest)).replace('//', '/')
        p.update(self.dest_params)

        self.bare_path = _force_unicode(base_path.replace('//', '/')).lower()
        self.bare_path = self.bare_path.rstrip('/')

        # append the query string
        base_path += query_string(p)

        # since we've been sloppy of keeping track of "//", get rid
        # of any that may be present
        self.path = base_path.replace('//', '/')

    def is_selected(self):
        """Given the current request path, would the button be selected."""
        if hasattr(self, 'name') and self.name == 'home':
            return False
        if self.opt:
            return request.params.get(self.opt, '') in self.aliases
        else:
            stripped_path = request.path.rstrip('/').lower()
            ustripped_path = _force_unicode(stripped_path)
            if stripped_path == self.bare_path:
                return True
            if stripped_path in self.aliases:
                return True

    def selected_title(self):
        """returns the title of the button when selected (for cases
        when it is different from self.title)"""
        return self.title

class AbsButton(NavButton):
    """A button for linking to an absolute URL"""
    def __init__(self, title, dest):
        self.path = dest
        NavButton.__init__(self, title, dest, False)

    def build(self, base_path = ''):
        pass

    def is_selected(self):
        return False

class SubredditButton(NavButton):
    def __init__(self, sr):
        self.sr = sr
        NavButton.__init__(self, sr.name, sr.path, False)

    def build(self, base_path = ''):
        self.path = self.sr.path

    def is_selected(self):
        return c.site == self.sr

class NamedButton(NavButton):
    """Convenience class for handling the majority of NavButtons
    whereby the 'title' is just the translation of 'name' and the
    'dest' defaults to the 'name' as well (unless specified
    separately)."""

    def __init__(self, name, sr_path = True, nocname=False, dest = None, **kw):
        self.name = name.replace('/', '')
        NavButton.__init__(self, menu[self.name], name if dest is None else dest,
                           sr_path = sr_path, nocname=nocname, **kw)

    def selected_title(self):
        """Overrides selected_title to use menu_selected dictionary"""
        try:
            return menu_selected[self.name]
        except KeyError:
            return NavButton.selected_title(self)

class ExpandableButton(NamedButton):
    def __init__(self, name, sr_path = True, nocname=False, dest = None,
                 sub_reddit = "/", sub_menus=[], **kw):
        self.sub = sub_menus
        self.sub_reddit  = sub_reddit
        NamedButton.__init__(self,name,sr_path,nocname,dest,**kw)

    def sub_menus(self):
        return self.sub

    def is_selected(self):
        return c.site.path == self.sub_reddit

class JsButton(NavButton):
    """A button which fires a JS event and thus has no path and cannot
    be in the 'selected' state"""
    def __init__(self, title, style = 'js', **kw):
        NavButton.__init__(self, title, '', style = style, **kw)

    def build(self, *a, **kw):
        self.path = 'javascript:void(0)'

    def is_selected(self):
        return False

class PageNameNav(Styled):
    """generates the links and/or labels which live in the header
    between the header image and the first nav menu (e.g., the
    subreddit name, the page name, etc.)"""
    pass

class SimpleGetMenu(NavMenu):
    """Parent class of menus used for sorting and time sensitivity of
    results.  More specifically, defines a type of menu which changes
    the url by adding a GET parameter with name 'get_param' and which
    defaults to 'default' (both of which are class-level parameters).

    The value of the GET parameter must be one of the entries in
    'cls.options'.  This parameter is also used to construct the list
    of NavButtons contained in this Menu instance.  The goal here is
    to have a menu object which 'out of the box' is self validating."""
    options   = []
    get_param = ''
    title     = ''
    default = None

    def __init__(self, type = 'select', **kw):
        kw['default'] = kw.get('default', self.default)
        kw['base_path'] = kw.get('base_path') or request.path
        buttons = [NavButton(self.make_title(n), n, opt = self.get_param)
                   for n in self.options]
        NavMenu.__init__(self, buttons, type = type, **kw)
        #if kw.get('default'):
        #    self.selected = kw['default']

    def make_title(self, attr):
        return menu[attr]

    @classmethod
    def operator(self, sort):
        """Converts the opt into a DB-esque operator used for sorting results"""
        return None

class SortMenu(SimpleGetMenu):
    """The default sort menu."""
    get_param = 'sort'
    default   = 'hot'
    options   = ('hot', 'new', 'top', 'old', 'controversial')

    def __init__(self, **kw):
        kw['title'] = _("Sort By") + ':'
        SimpleGetMenu.__init__(self, **kw)

    @classmethod
    def operator(self, sort):
        if sort == 'hot':
            return operators.desc('_hot')
        elif sort == 'new':
            return operators.desc('_date')
        elif sort == 'old':
            return operators.asc('_date')
        elif sort == 'top':
            return operators.desc('_score')
        elif sort == 'controversial':
            return operators.desc('_controversy')
        elif sort == 'confidence':
            return operators.desc('_confidence')
        elif sort == 'interestingness':
            return operators.desc('_interestingness')

class CommentSortMenu(SortMenu):
    """Sort menu for comments pages"""
    default   = 'confidence'
    options   = ('confidence', 'hot', 'new', 'controversial', 'top', 'old', 'interestingness')

class SearchSortMenu(SortMenu):
    """Sort menu for search pages."""
    default   = 'relevance'
    mapping   = dict(relevance = None,
                     hot = 'hot desc',
                     new = 'date desc',
                     old = 'date asc',
                     top = 'points desc')
    options   = mapping.keys()

    @classmethod
    def operator(cls, sort):
        return cls.mapping.get(sort, cls.mapping[cls.default])

class RecSortMenu(SortMenu):
    """Sort menu for recommendation page"""
    default   = 'new'
    options   = ('hot', 'new', 'top', 'controversial', 'relevance')

class NewMenu(SimpleGetMenu):
    get_param = 'sort'
    default   = 'new'
    options   = ('new', 'rising')
    type = 'flatlist'

    def __init__(self, **kw):
        kw['title'] = _("Sort by")
        SimpleGetMenu.__init__(self, **kw)

    @classmethod
    def operator(self, sort):
        if sort == 'new':
            return operators.desc('_date')

class KindMenu(SimpleGetMenu):
    get_param = 'kind'
    default = 'all'
    options = ('links', 'comments', 'messages', 'all')

    def __init__(self, **kw):
        kw['title'] = _("Kind")
        SimpleGetMenu.__init__(self, **kw)

    def make_title(self, attr):
        if attr == "all":
            return _("All")
        return menu[attr]

class TimeMenu(SimpleGetMenu):
    """Menu for setting the time interval of the listing (from 'hour' to 'all')"""
    get_param = 't'
    default   = 'all'
    options   = ('hour', 'day', 'week', 'month', 'quarter', 'year', 'all')

    def __init__(self, **kw):
        kw.setdefault('title', _("Links from"))
        SimpleGetMenu.__init__(self, **kw)

    @classmethod
    def operator(self, time):
        from r2.models import Link
        if time != 'all':
            return Link.c._date >= timeago(time)

class DashboardTimeMenu(SimpleGetMenu):
    """Menu for setting the time interval of the listing (from 'hour' to 'all').
       Has option for since last visit."""
    get_param = 't'
    default = 'last'
    options = ('hour', 'day', 'week', 'month', 'quarter', 'year', 'all', 'last')

    def __init__(self, **kw):
        kw.setdefault('title', _("Links from"))
        SimpleGetMenu.__init__(self, **kw)

    @classmethod
    def operator(self, time):
        from r2.models import Link
        if time != 'all':
            return Link.c._date >= timeago(time)

class NumCommentsMenu(SimpleGetMenu):
    """menu for toggling between the user's preferred number of
    comments and the max allowed in the display, assuming the number
    of comments in the listing exceeds one or both."""
    get_param = 'all'
    default   = 'false'
    options   = ('true', 'false')

    def __init__(self, num_comments, **context):
        context['title'] = _("Show") + ':'
        self.num_comments = num_comments
        SimpleGetMenu.__init__(self, **context)

    def make_title(self, attr):
        user_num = c.user.pref_num_comments
        if user_num > self.num_comments:
            # no menus needed if the number of comments is smaller
            # than any of the limits
            return ""
        elif self.num_comments > g.max_comments:
            # if the number present is larger than the global max,
            # label the menu as the user pref and the max number
            return dict(true=str(g.max_comments),
                        false=str(user_num))[attr]
        else:
            # if the number is less than the global max, display "all"
            # instead for the upper bound.
            return dict(true=_("All"),
                        false=str(user_num))[attr]

    def render(self, **kw):
        user_num = c.user.pref_num_comments
        if user_num > self.num_comments:
            return ""
        return SimpleGetMenu.render(self, **kw)

class SubredditMenu(NavMenu):
    def find_selected(self):
        """Always return False so the title is always displayed"""
        return None

class TagSortMenu(SimpleGetMenu):
    """Menu for listings by tag"""
    get_param = 'sort'
    default   = 'new'
    options   = ('old', 'new', 'top')

    def __init__(self, **kw):
        kw['title'] = _("Sort By") + ':'
        SimpleGetMenu.__init__(self, **kw)

    @classmethod
    def operator(self, sort):
        if sort == 'new':
            return operators.desc('_t1_date')
        elif sort == 'old':
            return operators.asc('_t1_date')
        elif sort == 'top':
            return operators.desc('_t1_score')

# --------------------
# TODO: move to admin area
class AdminReporterMenu(SortMenu):
    default = 'top'
    options = ('hot', 'new', 'top')

class AdminKindMenu(KindMenu):
    options = ('all', 'links', 'comments', 'spam', 'autobanned')

class AdminTimeMenu(TimeMenu):
    get_param = 't'
    default   = 'day'
    options   = ('hour', 'day', 'week')


from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Conv2D, Flatten, Dropout, MaxPooling2D, Reshape, Conv1D, LSTM
from tensorflow.keras.datasets import mnist

#1. 데이터

#2. 모델 구성
model = Sequential()
model.add(Conv2D(10, kernel_size = (2,2), strides =1,
                 padding = 'same',input_shape = (28,28,1)))
model.add(MaxPooling2D())
model.add(Conv2D(5,(2,2), activation = 'relu'))   # 13, 13, 5
model.add(Conv2D(7, (2,2), activation='relu'))    # 12, 12, 7
model.add(Conv2D(7, (2,2), activation='relu'))    # 12, 12, 7
model.add(Conv2D(10, (2,2), activation='relu'))    # 12, 12, 7
model.add(Flatten())                              # (None, 1000)
model.add(Reshape(target_shape= (100,10)))                      # (None, 100, 10)
model.add(Conv1D(5,2))
model.add(LSTM(15))
model.add(Dense(10,activation='softmax'))
model.summary()

#input_shape = (a,b,c) > kernel_size = (d,e) = (a-d+1, b-e+1)  //

# model.add(Conv2D(5, (3,3), activation='relu'))                     # 3,3,5
# model.add(Dropout(0.2))
# model.add(Conv2D(7, (2,2), activation='relu'))                     # 2,2,7
# model.add(Flatten())  # flatten (Flatten)            (None, 252)               0
# model.add(Dense(64))
# model.add(Dropout(0.2))
# model.add(Dense(16))
# model.add(Dense(5, activation = 'softmax'))


# @Time   : 2021/03/20
# @Author : Yushuo Chen
# @Email  : chenyushuo@ruc.edu.cn

"""
Case study example
===================
Here is the sample code for the case study in RecBole.
"""

import torch
from recbole.config import Config
from recbole.data import create_dataset, data_preparation
from recbole.utils import get_model, init_seed
from recbole.utils.case_study import full_sort_topk, full_sort_scores

if __name__ == '__main__':
    # this part is to load saved model.
    config_dict = {
        # here you can set some parameters such as `gpu_id` and so on.
    }
    config = Config(model='BPR', dataset='ml-100k', config_dict=config_dict)
    init_seed(config['seed'], config['reproducibility'])
    dataset = create_dataset(config)
    train_data, valid_data, test_data = data_preparation(config, dataset)
    # Here you can also use `load_split_dataloaders` to load data.
    # The example code for `load_split_dataloaders` can be found in `save_and_load_example.py`.

    model = get_model(config['model'])(config, train_data)
    checkpoint = torch.load('RecBole/saved/BPR-Dec-08-2020_15-37-37.pth')  # Here you can replace it by your model path.
    model.load_state_dict(checkpoint['state_dict'])
    model.eval()

    # uid_series = np.array([1, 2])  # internal user id series
    # or you can use dataset.token2id to transfer external user token to internal user id
    uid_series = dataset.token2id(dataset.uid_field, ['200'])

    topk_score, topk_iid_list = full_sort_topk(uid_series, model, test_data, k=10)
    print(topk_score)  # scores of top 10 items
    print(topk_iid_list)  # internal id of top 10 items
    external_item_list = dataset.id2token(dataset.iid_field, topk_iid_list)
    print(external_item_list)  # external tokens of top 10 items
    print()

    score = full_sort_scores(uid_series, model, test_data)
    print(score)  # score of all items
    print(score[0, dataset.token2id(dataset.iid_field, ['242', '302'])])  # score of item ['242', '302'] for user '196'.


# Multivariate Timeseries
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import plotly.express as px
from sklearn import metrics
from sklearn.preprocessing import LabelEncoder, MinMaxScaler
import tensorflow as tf

def custom_data_prep(dataset, target, start, end, window, horizon):
    x = []
    y = []
    start = start + window
    if end is None:
        end = len(dataset) - horizon
    for i in range(start, end):
        indices = range(i-window, i)
        x.append(dataset[indices])
        indicey = range(i+1, i+1+horizon)
        y.append(target[indicey])
    return np.array(x), np.array(y)

def timeseries_evaluation_metrics_func(y_true, y_pred):
    def mean_absolute_percentage_error(y_true, y_pred):
        y_true, y_pred = np.array(y_true), np.array(y_pred)
        return np.mean(np.abs((y_true - y_pred) / y_true)) * 100
    print('Evaluation metric results:-')
    print(f'MSE is : {metrics.mean_squared_error(y_true, y_pred)}')
    print(f'MAE is : {metrics.mean_absolute_error(y_true, y_pred)}')
    print(
        f'RMSE is : {np.sqrt(metrics.mean_squared_error(y_true, y_pred))}')
    print(f'MAPE is : {mean_absolute_percentage_error(y_true, y_pred)}')
    print(f'R2 is : {metrics.r2_score(y_true, y_pred)}', end='\n\n')

data = pd.read_csv("data.csv", parse_dates=["date"])

print(data.dtypes)

x_scaler = MinMaxScaler()
y_scaler = MinMaxScaler()
x_data = x_scaler.fit_transform(data[["dprice", "nupl"]])
y_data = y_scaler.fit_transform(data[["dprice"]])

hist_window = 30
horizon = 5
train_split = 1500

x_train, y_train = custom_data_prep(
        x_data, y_data, 0, train_split, hist_window, horizon)
x_vali, y_vali = custom_data_prep(
        x_data, y_data, train_split, None, hist_window, horizon)

print('Multiple window of past history')
print(x_train[0])
print('Target horizon')
print(y_train[0])

batch_size = 60
buffer_size = 45
train_data = tf.data.Dataset.from_tensor_slices((x_train, y_train))
train_data = train_data.cache().shuffle(buffer_size).batch(batch_size).repeat()
val_data = tf.data.Dataset.from_tensor_slices((x_vali, y_vali))
val_data = val_data.batch(batch_size).repeat()

lstm_model = tf.keras.models.Sequential([tf.keras.layers.Bidirectional(tf.keras.layers.LSTM(200, return_sequences=True),
                                                                       input_shape=x_train.shape[-2:]),
                                         tf.keras.layers.Dense(
                                         20, activation='tanh'),
                                         tf.keras.layers.Bidirectional(
                                         tf.keras.layers.LSTM(150)),
                                         tf.keras.layers.Dense(
                                         20, activation='tanh'),
                                         tf.keras.layers.Dense(
                                         20, activation='tanh'),
                                         tf.keras.layers.Dropout(0.25),
                                         tf.keras.layers.Dense(
                                            units=horizon),
                                         ])

lstm_model.compile(optimizer='adam', loss='mse')
lstm_model.summary()

model_path = 'Bidirectional_LSTM_Multivariate.h5'
early_stopings = tf.keras.callbacks.EarlyStopping(
        monitor='val_loss', min_delta=0, patience=10, verbose=1, mode='min')
checkpoint = tf.keras.callbacks.ModelCheckpoint(
        model_path, monitor='val_loss', save_best_only=True, mode='min', verbose=0)
callbacks = [early_stopings, checkpoint]
history = lstm_model.fit(train_data, epochs=150, steps_per_epoch=60,
                         validation_data=val_data, validation_steps=30, verbose=1)

plt.figure(figsize=(16, 9))
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('Model loss')
plt.ylabel('loss')
plt.xlabel('epoch')
plt.legend(['train loss', 'validation loss'])
plt.show()

data_val = x_scaler.fit_transform(data[["dprice", "nupl"]].tail(14))
val_rescaled = data_val.reshape(
                             1, data_val.shape[0], data_val.shape[1])
pred = lstm_model.predict(val_rescaled)
pred_Invers = y_scaler.inverse_transform(pred)
print(pred_Invers)

timeseries_evaluation_metrics_func(data_val["dprice"], pred_Invers[0])

plt.figure(figsize=(16, 9))
plt.plot(list(data_val['dprice']))
plt.plot(list(pred_Invers[0]))
plt.title("Actual vs Predicted")
plt.ylabel("BTC Price Change")
plt.legend(('Actual', 'predicted'))
plt.show()


# -*- coding: utf-8 -*-
"""
Created on Mon May 29 15:36:20 2017

@author: m-ali
"""

import numpy as np
import scipy as sp;
import itertools
from random import randint
import sys,os
Dir=os.getenv("HOME")+\
'/Documents/Notebooks/Notes/Note/Proposal/Bidirectional_Wireless_multi-channel_recording_&_stimulation_system/\
3._Rec&Stim_System/Recording/Online_Spike_Sorting/Source Codes/Simulation/'
sys.path.append(Dir);

from Spike_Isolation.Event_Classification import Impurity
from Spike_Isolation.Event_Classification.Yang_NSP.dtree_spikes_master.tree_split import tree_split

__all__ = ['WindowDiscrimination', 'TemplateMatch']

class TemplateMatch():
    def __init__(self, *args, **kwargs):
        if(len(args)>0):            self.bs   = args[0];# End with bin/segment size (Resolution)
        else:                       self.bs   = 1;
        if(len(args)>1):            self.norm = args[1]; #L1-norm,L2-norm,L∞,Rho (Correlation in multi dimentional space)
        else:                       self.norm = 2;
        if(len(args)>2):            self.dl = args[2]; #Decision Rule: 'Min-Distance', 'Opt-Distance'
        else:                       self.dl = ['min-distance','opt-distance'][0];
        if(len(args)>3):            self.calc_mode = args[3];
        else:                       self.calc_mode = 1;
    def fit(self, Data, Tag, sample_weight=None):
        Data = np.array(Data);
        self.Labels = sorted(list(set(Tag)));
        C_No   = len(set(Tag));
        self.template = [];
        self.r        = [];
        for iC in self.Labels:
            self.template.append( Data[np.where(Tag==iC)].mean(0) );

        if(self.dl.lower() == 'opt-distance'):
            if([type(x)==bool or type(x)==np.bool_ for x in self.Labels] == [True, True]):C_range = [True];      # 'multi-label'
            else:                                                    C_range = range(C_No); # 'multi-class'
            for iC in C_range:
                if(self.norm==1):       Err =          np.abs(   Data - self.template[iC] ).sum(1);
                elif(self.norm==2):     Err = np.sqrt( np.square(Data - self.template[iC] ).sum(1) );
                elif(self.norm=='rho'): Rho = np.apply_along_axis(lambda x: np.corrcoef(x,self.template)[0,1], axis=1, arr=Data);

                if(self.norm==1 or self.norm==2):
                    r_max   = np.int16(np.floor(np.max( Err )))+1;
                    R_range = list(np.r_[0:r_max+self.bs:self.bs]);#      list(range(0, r_max,  self.bs));
                elif(self.norm=='rho'):           R_range = list(np.r_[0:1+self.bs:self.bs]);

                if(self.calc_mode==1):
                    if(self.norm==1 or self.norm==2): label_out = ( [list(Err)]*len(R_range) - np.transpose([list(R_range)]*len(Err)) ) <=0;
                    elif(self.norm=='rho'):           label_out = ( [list(Err)]*len(R_range) - np.transpose([list(R_range)]*len(Err)) ) >=0;
                    TP = np.sum( label_out*( Tag==iC)+0, 1);
                    FP = np.sum( label_out*( Tag!=iC)+0, 1);
                    TN = ( Tag!=iC+0 ).sum() - FP;
                    _CA= TP+TN;
                elif(self.calc_mode==2):
                    _CA = [];
                    for r in R_range:
                        if(self.norm==1 or self.norm==2): label_out = Err<=r;
                        elif(self.norm=='rho'):           label_out = Rho>=r;
                        TP = np.sum( label_out*( Tag==iC)+0 );
                        FP = np.sum( label_out*( Tag!=iC)+0 );
                        TN = ( Tag!=iC+0 ).sum() - FP;
                        _CA.append( (TP+TN)/len(Tag) );
                iCA = np.where(_CA>=np.max(_CA))[0].min();
                self.r.append( R_range[iCA] );
        return self;

    def predict(self, Data):
        Data = np.array(Data);
        C_No = len(self.template);
        Tag  = [];
        Err  = [];
        Rho  = [];
        for iC in range(C_No):
            if(  self.norm==1):     Err.append(        np.abs(    Data - self.template[iC] ).sum(1) );
            elif(self.norm==2):     Err.append(np.sqrt(np.square( Data - self.template[iC] ).sum(1)));
            elif(self.norm=='rho'): Rho.append(np.apply_along_axis(lambda x: np.corrcoef(x,self.template[iC])[0,1], axis=1, arr=Data));

        if(self.dl.lower() == 'min-distance'):
            if(self.norm==1 or self.norm==2):     Tag = np.argmin(Err ,axis=0);
            elif(self.norm=='rho'):               Tag = np.argmax(Rho ,axis=0);

        if(self.dl.lower() == 'opt-distance'):
            if([type(x)==bool or type(x)==np.bool_ for x in self.Labels] == [True, True]):C_range = [True];      # 'multi-label'
            else:                                                                         C_range = range(C_No); # 'multi-class'

            for iC in C_range:
                ir = 0 if([type(x)==bool or type(x)==np.bool_ for x in self.Labels] == [True, True]) else iC; #if(len(self.r)==1) elif(len(self.r)==C_No)
                if(self.norm==1 or self.norm==2): label_out = np.array(Err[iC])<=self.r[ir];
                elif(self.norm=='rho'):           label_out = np.array(Rho[iC])>=self.r[ir];
                if([type(x)==bool or type(x)==np.bool_ for x in self.Labels] == [True, True]):Tag = label_out;        # 'multi-label'
                else:                                                    Tag.append(label_out);  # 'multi-class'
        return Tag;

#Fast WD: Use Bayes decision theory to calculate decision boundries (BND)?????
class WindowDiscrimination():
    def __init__(self, *args, **kwargs):
        if(len(args)>0):            self.bs   = args[0];# End with bin/segment size
        else:                       self.bs   = 1;
        if(len(args)>1):            self.bn   = args[1];# Start with number of bins/segments in each feature dimension
        else:                       self.bn   = 8;
        if(len(args)>2):            self.sn   = args[2]; #split no per dimensions
        else:                       self.sn   = -1;
        if(len(args)>3):            self.mode = args[3];#search modes: detailed span, fast (max CA)
        else:                       self.mode = ['min-area','mid-area','max-area','sequence','span','knn-l1','knn-l2'][0];
        if(len(args)>4):            self.calc_mode = args[4];
        else:                       self.calc_mode = 0;
        if(len(args)>5):            self.wd  = args[5];# Window discriminators per stage
        else:                       self.wd  = [-1,5000][0];

        self.Labels = [];
        self.Bounds = [];
        self.AlternativeBounds = [];

    def fit(self, Data, Tag, sample_weight=None):
        Data   = np.array(Data);
        Tag    = np.array(Tag);

        self.Labels = sorted(list(set(Tag)));
        self.Bounds = [];
        self.AlternativeBounds = [];

        C_No   = len(set(Tag));
        O_No   = Data.shape[1]

        if([type(x)==bool or type(x)==np.bool_ for x in self.Labels] == [True, True]):  C_range = [True];      # 'multi-label'
        else:                                                                           C_range = range(C_No); # 'multi-class'

        if(self.bs=='auto'):          bs_End = Data.std(0)/10;#[Data[:,iO].std()/100 for iO in range(O_No)]; #??
        elif(np.isscalar(self.bs)):   bs_End = [self.bs]*O_No;
        elif(len(self.bs)==1):        bs_End = [self.bs[0]]*O_No;
        elif(len(self.bs)==O_No):     bs_End = self.bs;
        else:                         print('Problem with bs parameter value');return -1;

        x_min, x_max = np.int16( np.ceil( np.max([Data.min(0), Data.mean(0)-4*Data.std(0)],0)) ) - 1,\
                       np.int16( np.floor(np.min([Data.max(0), Data.mean(0)+4*Data.std(0)],0)) ) + 1;

        if(  self.mode.lower() in ['span','sequence']):                                  bs = [self.bs]*O_No;
        elif(self.mode.lower() in ['min-area','mid-area','max-area','knn-l1','knn-l2']): bs = 2**np.ceil(np.log2((x_max-x_min)/self.bn));

        if(self.mode.lower() in ['min-area','mid-area','max-area','span','knn-l1','knn-l2']):
            X_range = [];
            for iO in range(O_No):
                xbl_range = np.r_[np.floor(x_min[iO]/bs[iO])*bs[iO]: x_max[iO]+bs[iO]: bs[iO]]
                X_range.append([[0,0]]);
                for x_bl in xbl_range:
                    xbu_range = np.r_[x_bl: x_max[iO]:  bs[iO]]+bs[iO];
                    for x_bu in xbu_range:  X_range[iO] +=[(x_bl,x_bu)];

        if(self.mode.lower() in ['min-area','mid-area','max-area','knn-l1','knn-l2']):
            Xrange = '';
            for iO in range(O_No): Xrange += 'X_range[%d],'%iO;
            Xrange = np.array(eval('list(itertools.product(%s))'%(Xrange[:-1])));

            for iC in C_range:
                while(True):
                    _CA, label_out = [], [];
                    if(self.calc_mode==0):
                        label_out = np.array([[1]*len(Tag)]*len(Xrange));
                        for iO in range(O_No):
                            _bnd = [np.transpose([Xrange[:,iO,0]]*len(Data)),\
                                    np.transpose([Xrange[:,iO,1]]*len(Data))];
                            _data = [Data[:,iO]]*len(Xrange);
                            label_out *= (_bnd[0] < _data)&(_data < _bnd[1]);
                        TP = np.sum( label_out*( Tag==iC)+0 ,1);
                        FP = np.sum( label_out*( Tag!=iC)+0 ,1);
                        TN = ( Tag!=iC+0 ).sum() - FP;
                        _CA = (TP+TN)/len(Tag);
                    elif(self.calc_mode==1):
                        _label_out = np.zeros((O_No,len(Xrange),len(Data)),bool)
                        for iO in range(O_No):
                            for ib,bnd in enumerate(Xrange):
                                _label_out[iO,ib,:] = (bnd[iO][0] < Data[:,iO])&(Data[:,iO] < bnd[iO][1]);

                        label_out = np.array([[1]*len(Tag)]*len(Xrange));
                        for iO in range(O_No): label_out *= np.array(_label_out[iO]);

                        TP = np.sum( label_out*( Tag==iC)+0 ,1);
                        FP = np.sum( label_out*( Tag!=iC)+0 ,1);
                        TN = ( Tag!=iC+0 ).sum() - FP;
                        _CA = (TP+TN)/len(Tag);
                    elif(self.calc_mode==2):
                        for bnd in Xrange:
                            label_out.append( [1]*len(Data[:,0]) );
                            for iO in range(O_No):
                                label_out[-1] *= (bnd[iO][0] < Data[:,iO])&(Data[:,iO] < bnd[iO][1]);
                        TP = np.sum( label_out*( Tag==iC)+0 ,1);
                        FP = np.sum( label_out*( Tag!=iC)+0 ,1);
                        TN = ( Tag!=iC+0 ).sum() - FP;
                        _CA = (TP+TN)/len(Tag);
                    elif(self.calc_mode==3):
                        for bnd in Xrange:
                            label_out = [1]*len(Data[:,0]);
                            for iO in range(O_No):
                                label_out *= (bnd[iO][0] < Data[:,iO])&(Data[:,iO] < bnd[iO][1]);
                            TP = np.sum( label_out*( Tag==iC)+0 );
                            FP = np.sum( label_out*( Tag!=iC)+0 );
                            TN = ( Tag!=iC+0 ).sum() - FP;
                            _CA.append( (TP+TN)/len(Tag) );

                    iCA = np.where(_CA>=np.max(_CA))[0];
                    np.random.shuffle(iCA);
                    if(self.wd>0): iCA = iCA[:self.wd]

                    if(self.mode.lower()[:3]=='knn'):
                        K = self.bn; #Number of neighbours
                        C = self.sn; #Number of Clusters
                        X = np.array([[Xrange[iCA][i].ravel() for i in range(len(iCA))] for j in range(len(iCA))]);
                        if(self.mode.lower()=='knn-l1'):
                            d = np.sum( abs(X - np.transpose(X,axes=(1,0,2)))   , 2);
                            dnn = np.sort(d)[:,:K].sum(1);

                        elif(self.mode.lower()=='knn-l2'):
                            d = np.sum(    (X - np.transpose(X,axes=(1,0,2)))**2, 2)**.5;
                            dnn = (((np.sort(d)[:,:K]**2).sum(1))**.5);

                        idn = np.where(np.array(dnn)==min(dnn))[0];
                        np.random.shuffle(idn);
                        idn = np.argsort(d[idn])[:C,:K];
                        iCA =  np.unique(iCA[ idn ].ravel());
                    #Choosing WDs based on their area/volume
                    elif(self.mode.lower() in ['min-area','mid-area','max-area']):
                        Area = np.product( np.diff(Xrange[iCA])[:,:,0],1);
                        if(self.mode.lower()=='max-area'):      iCA = iCA[ np.argsort(Area)[:-1-self.sn:-1] ];
                        elif(self.mode.lower()=='min-area'):    iCA = iCA[ np.argsort(Area)[:self.sn] ];
                        elif(self.mode.lower()=='mid-area'):    iCA = iCA[ np.argsort(Area)[(len(iCA)-self.sn)//2:(len(iCA)+self.sn)//2] ];

                    if(np.prod([bs[iO]<= bs_End[iO] for iO in range(O_No)])):    break;
                    for iO in range(O_No):
                        if(bs[iO]> bs_End[iO]): bs[iO] = bs[iO]/2;

                    _bs = '';
                    for iO in range(O_No): _bs += '[-bs[%d],0,bs[%d]],[-bs[%d],0,bs[%d]],'%(iO,iO,iO,iO);
                    _bs = eval('list(itertools.product(%s))'%(_bs));
                    _bs = np.reshape(_bs,(len(_bs),O_No,2));

                    X_range = np.transpose( [_bs.tolist()]*len(iCA), (1,0,2,3) ) + np.array( [Xrange[iCA]]*len(_bs) );
                    X_range = np.concatenate(X_range);
                    X_range = X_range.reshape((X_range.shape[0],X_range.shape[1]*X_range.shape[2])).tolist();
                    X_range = list(set(map( tuple,X_range)));
                    Xrange  = np.reshape(X_range,(len(X_range),O_No,2));

                self.AlternativeBounds = Xrange[iCA];
                iCA = iCA[np.argmin(np.product(np.diff(Xrange[iCA]),1))];
                self.Bounds.append( Xrange[iCA].tolist() );
        elif(self.mode.lower()=='sequence'):
            for ic, iC in enumerate(C_range):
                self.Bounds.append([]);
                for iO in range(O_No):
                    label_out = [];
                    Bnd = [];
                    for x_bl in list(np.r_[np.int(np.floor(x_min[iO]/bs[iO])*bs[iO]): x_max[iO]: bs[iO]]):
                        for x_bu in list(np.r_[x_bl+bs[iO]:x_max[iO]+bs[iO]:bs[iO]]):
                            label_out.append( (x_bl<= Data[:,iO])&(Data[:,iO] <=x_bu) );
                            Bnd.append([x_bl,x_bu]);
                    if(self.calc_mode==0):
                        _label = [1]*len(Data[:,iO]);
                        for io in range(iO):
                            _label *= (self.Bounds[ic][io][0]<= Data[:,io])&(Data[:,io] <=self.Bounds[ic][io][1]);
                        label_out = np.array(label_out)*_label;

                        TP = np.mean( label_out*(Tag==iC),1 );
                        FP = np.mean( label_out*(Tag!=iC),1 );
                        TN = np.mean( Tag!=iC ) - FP;
                        _CA = (TP + TN);

                        iCA = np.where(_CA>=np.max(_CA))[0];
                        Area = np.diff(np.array(Bnd)[iCA])[:,0];
                        iCA = iCA[ np.argmax(Area) ];
                        self.Bounds.append( [Bnd[iCA]] );

                    elif(self.calc_mode==1):
                        if(iO==0):  B_No = 1;
                        else:       B_No = len(self.Bounds[ic]);
                        _label = np.array([[1]*len(Data[:,iO])]*B_No);
                        for io in range(iO):
                            for ib in range(B_No):
                                _label[ib] *= (self.Bounds[ic][ib][io][0]<= Data[:,io])&(Data[:,io] <=self.Bounds[ic][ib][io][1]);

                        TP = np.tensordot( np.array(label_out)*(Tag==iC)+0, _label+0, axes=[1, 1])
                        FP = np.tensordot( np.array(label_out)*(Tag!=iC)+0, _label+0, axes=[1, 1])
                        TN = (Tag!=iC).sum() - FP;
                        _CA = (TP + TN)/len(Tag);

                        iCA = np.array(np.where(_CA>=np.max(_CA)));
                        if(iO==0):  self.Bounds[ic] = [[Bnd[ica]] for ica,ib in iCA.T];
                        else:       self.Bounds[ic] = [self.Bounds[ic][ib]+[Bnd[ica]] for ica,ib in iCA.T];
                        iBs = np.argsort(np.prod(np.diff(self.Bounds[ic]),axis=1)[:,0])[:-1-self.sn:-1];
                        self.Bounds[ic] = np.array(self.Bounds[ic])[iBs].tolist();
                if(self.calc_mode==1):
                    iB = np.argmin(np.prod(np.diff(self.Bounds[ic]),axis=1));
                    self.AlternativeBounds = [self.Bounds[ic][ib] for ib in range(len(self.Bounds[ic])) if ib!=iB];
                    self.Bounds[ic] = self.Bounds[ic][iB];

        elif(self.mode.lower()=='bayes'):pass;
        elif(self.mode.lower()=='auto'):pass;#auto-range bs based on std
        elif(self.mode.lower()=='span'):
            for iC in C_range:
                label_out = [];
                Bnd       = [];
                for iO in range(O_No):
                    label_out.append([]);
                    Bnd.append([]);

                    for ib,[x_bl,x_bu] in enumerate(X_range[iO]):
                        label_out[-1].append( (x_bl<= Data[:,iO])&(Data[:,iO] <=x_bu) );
                        Bnd[-1].append([x_bl,x_bu]);

                if(self.calc_mode==0):
                    if(O_No==1):
                        TP = np.tensordot( np.array(label_out[0])+0, (np.array([Tag])==iC)+0, axes=[1, 1])/len(Tag);
                        TN = np.tensordot(~np.array(label_out[0])+0, (np.array([Tag])!=iC)+0, axes=[1, 1])/len(Tag);
                    elif(O_No>1):
                        CA_arg = "";
                        for iO in range(1,O_No):    CA_arg += ", np.array( label_out[%d]),[%d,0]"%(iO,iO+1);
                        TP = eval('np.einsum(np.array( label_out[0])*(Tag==iC)+0,[1,0]'+CA_arg + ')')/len(Tag);
                        FP = eval('np.einsum(np.array( label_out[0])*(Tag!=iC)+0,[1,0]'+CA_arg + ')')/len(Tag);
                        TN = (np.array( Tag )!=iC).sum()/len(Tag) - FP;

                elif(self.calc_mode==1):
                    Xrange = '';
                    for iO in range(O_No): Xrange += 'X_range[%d],'%iO;
                    Xrange = np.array(eval('list(itertools.product(%s))'%(Xrange[:-1])));

                    _label_out = np.zeros((O_No,len(Xrange),len(Data)),bool)
                    for iO in range(O_No):
                        for ib,bnd in enumerate(Xrange):
                            _label_out[iO,ib,:] = (bnd[iO][0] < Data[:,iO])&(Data[:,iO] < bnd[iO][1]);

                    label_out = np.array([[1]*len(Tag)]*len(Xrange));
                    for iO in range(O_No): label_out *= np.array(_label_out[iO]);

                    TP = np.sum( label_out*( Tag==iC)+0 ,1);
                    FP = np.sum( label_out*( Tag!=iC)+0 ,1);
                    TN = ( Tag!=iC+0 ).sum() - FP;
                    _CA = (TP+TN)/len(Tag);

                elif(self.calc_mode==2):
                    pass;

                _CA = TP + TN;

                if(O_No==1):   self.Bounds.append( [Bnd[0][_CA.argmax()]] );
                elif(O_No>1):
                    iB = np.unravel_index(_CA.argmax(),_CA.shape);
                    self.Bounds.append( [Bnd[iO][iB[iO]] for iO in range(O_No)] );
        return self;

    def predict(self, Data):
        Data = np.array(Data);
        C_No = len(self.Bounds);
        O_No = Data.shape[1];
        Tag= [];

        for iC in range(C_No):
            label_out = [1]*len(Data[:,0]);
            for iO in range(O_No):
                label_out *= (self.Bounds[iC][iO][0]<= Data[:,iO])&(Data[:,iO] <=self.Bounds[iC][iO][1]);
            if(C_No==1):            Tag = label_out;
            elif(C_No>1):           Tag.append(label_out);
        return Tag;

# No of trees=6, No of clusters, No of perturbance(5,10,20,50), metric=gini, entropy(information gain)
"""see https://github.com/KDercksen/pyblique"""
class ObliqueClassifier:
    def __init__(self, metric, Rs=2, Np=10):
        self.metric = metric;
        try:                                        self.Rs = np.abs(np.round(Rs));
        except AttributeError or ValueError:        self.Rs = None;
        except:                                     self.Rs = None;
        self.Np = Np;
        self.tree = {};

    def fit(self, data, label):
        _train = np.concatenate((data,np.transpose([label])),axis=1)
        self.tree = self.__create_decision_tree( _train )
        return self;

    def predict(self, data):
        label = [];
        for record in data:
            cls = self.tree
            while type(cls) is dict:
                Coefs = cls["Coefs"];
                v = self.__checkrel(record, Coefs) > 0;
                if v:	cls = cls["high"];
                else:	cls = cls["low"];
            label.append(cls);
        return label;

    def __create_decision_tree(self, _train):
        train_data =_train[:,:-1]; #train_label=_train[:, -1];
        if len(_train) == 0:	return -1;

        isleaf, leaf = self.__is_leaf_node(_train);
        if isleaf:            return leaf;#Class label
        else:
            n_attrs = train_data.shape[1];# _train.shape[1] - 1
            #First, it finds the best axis-parallel split of T (train data) at a node before looking for an oblique split.
            result = np.array([self.__best_split(_train, attr) for attr in range(n_attrs)]);
            index, split = min(enumerate(result), key=lambda x: x[1][1])

            # in order to make this oblique, we first have to build a vector to enable the linear combination split
            Coefs = np.zeros((len(_train[0]),));#np.zeros((len(train_data[0])+1,));
            Coefs[-1] = -split[0]
            Coefs[index] = 1; #oblique line coefs
            low, high = self. __split_data(_train, Coefs);

            imp = Impurity.impurity([low[:, -1], high[:, -1]], self.metric);

            for r in range(self.Np):
                attr = randint(0, len(train_data[0]));
                imp, Coefs = self.__perturb(_train, Coefs, attr, imp);

            # Splitter at this node is calculated, go to next nodes
            tree = {"Coefs": Coefs}
            low, high = self. __split_data(_train, Coefs)
            subtree_low = self.__create_decision_tree(low)
            tree["low"] = subtree_low
            subtree_high = self.__create_decision_tree(high)
            tree["high"] = subtree_high;
        return tree

    def __best_split(self, data, attr):
        # Will return a tuple of (split test, split value).
        splits = np.convolve( np.sort(data[:, attr]), np.repeat(1.0, 2)/2 )[1:-1];

        split_evals = {}
        for s in splits:
            cond = data[:, attr] <= s
            left, right = data[cond], data[~cond]
            split_evals[s] = Impurity.impurity([left[:, -1], right[:, -1]], self.metric);#amount of impurity
        return min(split_evals.items(), key=lambda x: x[1])

    def __split_data(self, _train, Coefs):
        high = np.zeros(_train.shape);
        low = np.zeros(_train.shape);
        ihigh, ilow = 0, 0
        for record in _train:
            v = self.__checkrel(record[:-1], Coefs) > 0;
            if v:    high[ihigh] = record;  ihigh += 1;
            else:    low[ilow]   = record;  ilow  += 1;
        high = high[:ihigh];
        low  = low [:ilow];
        return low, high

    def __is_leaf_node(self, _train):
        # Returns true/false and the class label (useful if this was a leaf)
        labels = _train[:, -1]
        data   = _train[:,:-1]; #train_label=_train[:, -1];
        return all(label == labels[0] for label in labels) or np.all(data == data[0]), labels[0];

    def __checkrel(self, record, Coefs):
        return np.sum(np.multiply(record, Coefs[:-1])) + Coefs[-1]

    def __perturb(self, _train, Coefs, attr, imp):
        # first calculate all values of U with the current value in Coefs for attr
        us = np.array(sorted([[(Coefs[attr]*record[attr] -self.__checkrel(record[:-1], Coefs))/record[attr]\
                               ] for record in _train if(record[attr])]));

        # now find the best of these splits...
        splits = np.convolve(np.sort(us[:, 0]), [ 0.5,  0.5])[1:-1];
        if(len(splits)==0): return imp, Coefs;#splits = us+[-.5,0,.5]

        amvalues = {}
        for s in set(splits):
            _Coefs_new = np.array(Coefs);
            _Coefs_new[attr] = s;
            _Coefs_new = _Coefs_new/np.abs(_Coefs_new[:-1]).max();
            if(self.Rs!=None): _Coefs_new[:-1] = np.round(_Coefs_new[:-1]*self.Rs)/self.Rs
            _Coefs_new[np.where(_Coefs_new[:-1]==-1)[0]]=-1+2**(-self.Rs+1);

            low, high = self.__split_data(_train, _Coefs_new)
            _imp = Impurity.impurity([low[:, -1], high[:, -1]], self.metric);#amount of impurity
            amvalues[s] = (_imp, _Coefs_new)
        imp_new, Coefs_new = min(amvalues.values(), key=lambda x: x[0])
        if imp_new < imp:       return imp_new, Coefs_new;
        else:                   return imp, Coefs;

#EC = WC.ObliqueQDT()
#http://www.egr.msu.edu/amsac/nsp.htm
class ObliqueQDT:
    def __init__(self, Rs=2, Np=50):
        self.Rs = Rs;
        self.Np = Np;
        self.coef = {};
        self.Leaf_label = {};
        self.Label_set = [];

    def fit(self, Data, Tag):
        self.Label_set = np.unique(Tag);
        self.coef, self.Leaf_label = tree_split(np.array(Data),np.array(Tag),self.Rs,self.Np );
        return self;

    def predict(self, Data):
        N = len(Data);
        Nclass= len(self.Label_set);
        depth = int(np.ceil(np.log2(Nclass)));

        label = np.array( [None]*N );
        for i in range(N):
            inode = 0;
            for idepth in range(1,depth+1):
                V = sum(np.concatenate([Data[i,:],[1]])*self.coef[idepth][inode]);
                if V >= 0:  child=0;
                else:       child=1;
                inode = 2*inode +child;

                if idepth == depth -1:
                    if self.Leaf_label[1][inode] != None:
                        label[i] = self.Leaf_label[1][inode];
                        break;
                if idepth == depth:
                    label[i] = self.Leaf_label[2][inode];

        return label;


'''
F4 .  .  .  .  .
F3 .  .  .  LG .
F2 .  HG .  .  .
F1 E  .  HM .  LM
'''

'''
F4 .   .   .   .   .   .   .   .   .   .   .
F3 .   .   .   .   .   .   .   .   .   .   .
F2 .   .  PoM  .   .   .  PrM  .   .   .   .
F1 E  PoG  .  TG  TM  PrG  .  RG  RM  CG  CM
'''

from copy import deepcopy
from collections import deque

sample_state = {
    1: {'HM', 'LM'},
    2: {'HG'},
    3: {'LG'},
    4: set()
}
initial_state = {
    1: {'PoG', 'ThG', 'ThM', 'PrG', 'RG', 'RM', 'CG', 'CM'},
    2: {'PoM', 'PrM'},
    3: set(),
    4: set()
}

class State:
    def __init__(self, state, elevator=1):
        self.state = state
        self.rev = {}
        for k in state:
            for o in state[k]:
                self.rev[o] = k
        self.elevator = elevator

    def tupify(self):
        return (tuple(sorted(self.rev.items())), self.elevator)

    def actions(self):
        a = []
        to_move = list(filter(lambda x: self.rev[x] == self.elevator, self.rev.keys()))
        for s1 in to_move:
            for dest_floor in list(filter(lambda x: abs(x - self.elevator) == 1, self.state.keys())):
                a.append(((s1,), dest_floor))
                for s2 in to_move:
                    if s1 != s2:
                        a.append(((s1, s2), dest_floor))
        return a

    def do(self, a): # a = ((..., ...), dest_floor)
        stuff, dest_floor = a
        new_state = State(state=deepcopy(self.state), elevator=dest_floor)
        for s in stuff:
            new_state.state[new_state.rev[s]].remove(s)
            new_state.state[dest_floor].add(s)
            new_state.rev[s] = dest_floor
        return new_state

    def is_valid(self):
        for check in self.state.values():
            microchips = list(filter(lambda x: x[-1] == 'M', check))
            gens = list(filter(lambda x: x[-1] == 'G', check))
            for m in microchips:
                for g in gens:
                    if m[:-1] != g[:-1] and m[:-1] + 'G' not in gens:
                        return False
        return True

    def is_terminal(self):
        return sum(map(lambda x: len(self.state[x]), [1, 2, 3])) == 0

def BFS(st, debug=False):
    q = deque([(State(st), 0)])
    visited = {State(st).tupify()}
    while q:
        s, d = q.popleft()
        if debug and (not q or q[0][1] == d + 1):
            print(f'Depth {d} handled')
        for a in s.actions():
            nxt = s.do(a)
            if nxt.is_terminal():
                return d + 1
            tup = nxt.tupify()
            if nxt.is_valid() and tup not in visited:
                visited.add(tup)
                q.append((nxt, d + 1))

# Will take a while
print('Part 1:', BFS(deepcopy(initial_state)))
initial_state[1] |= {'EG', 'EM', 'DG', 'DM'}
print('Part 2:', BFS(deepcopy(initial_state)))


from django.shortcuts import render

from .models import Project

def projects_index(request):
    projects = Project.objects.all()
    for project in projects:
        print(project.image.url)

    context = {
        'projects': projects
    }

    return render(request, 'app/about.html', context)

def project_detail(request, pk):
    project = Project.objects.get(pk=pk)

    context = {
        'project': project
    }

    return render(request, 'project_detail.html', context)


from Report import Report

class DonorReportFactory(object):

    def __init__(self, context, **kw):
        self.context = context
        self.params = kw

    def getReport(self):
        # create and fill the report
        name = "Donor Report"
        report = Report(name)
        report.setReportHeaders(( name,),)
        report.setTableHeaders(((
            'Donor name',
            'Donor type',
            'Project title',
            'Focal Area',
            'Geographic scope',
            'Region',
            'Country(ies)',
            'Pledged (planned) amount',
            'Total project grant',
            'Project status',
            ),))
        # XXX Implement this
        # report.setTableRows()
        # report.setTableTotals([])
        # report.setReportFooters()
        return report


from datetime import datetime
from pathlib import Path

from scrapy import signals

from services.credentials import prepare_json_credentials, read_json_credentials
from services.logger import log_writer

class OreillySpiderMiddleware:
    @classmethod
    def from_crawler(cls, crawler):
        s = cls()
        crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
        return s

    def process_spider_input(self, response, spider):
        return None

    def process_spider_output(self, response, result, spider):
        for i in result:
            yield i

    def process_spider_exception(self, response, exception, spider):
        location = "logs/meta"
        Path(location).mkdir(parents=True, exist_ok=True)
        file_path = f"{location}/{spider.name}.log"
        string = (
            f"ERROR: {str(exception)} occurred for "
            f"{response.request.url} while running {spider.name} spider "
            f"at {datetime.utcnow().isoformat()}\n"
        )
        log_writer(file_path, string)

    def process_start_requests(self, start_requests, spider):
        location = "logs/meta"
        Path(location).mkdir(parents=True, exist_ok=True)

        for r in start_requests:
            file_path = f"{location}/{spider.name}.log"
            string = (
                f"INFO: {r.method} request fired for {spider.name} with {r.url} at {datetime.utcnow().isoformat()}\n"
            )
            log_writer(file_path, string)
            yield r

    def spider_opened(self, spider):
        spider.logger.info("Spider opened: %s" % spider.name)

class OreillyDownloaderMiddleware:
    @classmethod
    def from_crawler(cls, crawler):
        s = cls()
        crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
        return s

    def process_request(self, request, spider):
        if request.headers:
            return None

        location = "keys/credentials"
        Path(location).mkdir(parents=True, exist_ok=True)

        if not Path(f"{location}/{spider.name}.sh").exists():
            prepare_json_credentials(spider.name)

        credentials = read_json_credentials(spider.name)

        for key, value in credentials["headers"].items():
            request.headers[key] = value

        for key, value in credentials["cookies"].items():
            request.cookies[key] = value

        request.headers["content-type"] = "application/json"

        if spider.name != "auth":
            credentials = read_json_credentials(spider.name)
            for key, value in credentials["headers"].items():
                if key not in request.headers:
                    request.headers[key] = value
            for key, value in credentials["cookies"].items():
                if key not in request.cookies:
                    request.cookies[key] = value

        return None

    def process_response(self, request, response, spider):
        location = "logs/fails"
        Path(location).mkdir(parents=True, exist_ok=True)

        if (
            response.status == 200
            and response.headers.get("content-type") == "application/json"
            and "success" in response.json()
            and not response.json()["success"]
        ):
            file_path = f"{location}/{spider.name}.log"
            string = f"request to {request.url} failed with {response.json()} at {datetime.utcnow().isoformat()}\n"
            log_writer(file_path, string)

        return response

    def process_exception(self, request, exception, spider):
        location = "logs/fails"
        Path(location).mkdir(parents=True, exist_ok=True)

        file_path = f"{location}/{spider.name}.log"
        string = f"ERROR: request to {request.url} failed with {str(exception)} at {datetime.utcnow().isoformat()}\n"
        log_writer(file_path, string)

    def spider_opened(self, spider):
        spider.logger.info("Spider opened: %s" % spider.name)


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

# In[2]:

import math
import time
from IPython.display import clear_output
import matplotlib.pyplot as plt
import pandas as pd

# In[3]:

#Membuat fungsi dari garis (dengan n = 2)
def fungsi_fx(x):
    fx = 2*(x)+12
    return fx

#Golden Search Section
r = (math.sqrt(5)-1) / 2
xl = 4
xu = 10

def golden_search_min(xl,xu,e):
    it = 0
    while e >= 0.05:
        d = r*(xu-xl)
        x1 = xl+d
        x2 = xu-d
        fx1 = fungsi_fx(x1)
        fx2 = fungsi_fx(x2)
        clear_output(wait=True)

        if fx2>fx1:
            xl = x2
            xu = xu
            x2 = x1
            x1 = xl + (r*(xu-xl))
            xopt = x1

        else:
            xl = xl
            xu = x1
            x1 = x2
            x2 = xu - (r*(xu-xl))
            xopt = x2
        it+=1
        print('Iterasi:', it)
        print('xl:', xl)
        print('xu:', xu)
        print('fx optimum:', fungsi_fx(xopt))

        e = ((1-r)*(abs((xu-xl) / xopt))) * 100
        print('Error:', e, '%')

        #Mencari jarak antara dua titik koordinat (dengan n = 2)
        titik_x1 = 4
        titik_y1 = 22
        d_jarak = math.sqrt((titik_x1-xopt)**2 + (titik_y1-fungsi_fx(xopt))**2)
        print('Jarak antara dua titik koordinat:', d_jarak, 'satuan unit')

        time.sleep(3)

golden_search_min(4,10,1)

# In[7]:

import matplotlib.pyplot as plt
import pandas as pd

#Memasukkan data kedalam python
df = pd.read_csv('D:\Tugas Metode Numerik/truck_7.txt', sep = ';')

t = df['time(s)'].tolist()
v = df['velocity(m/s)'].tolist()

#Menghitung jarak yang ditempuh
def trapezoid(x,y,h):
    L = (h/2)*((y[0]+2*(sum(y[1:-1]))+y[-1]))
    return L

S = trapezoid(t,v,2)
print('Jarak yang ditempuh:', S, 'm')

#Mem-plotkan hasil
plt.figure(figsize=(12,8))
plt.plot(t,v,'c')
plt.plot(t,v,'r.')
plt.xlabel('time(s)')
plt.ylabel('speed(m/s)')
plt.show()

# In[ ]:


list = []

while True:
    try:
        todo = input("Enter your todo's: ")
    except ValueError:
        print("Sorry, I didn't understand that.")
        continue
    if 'remove' in todo:
       todo = todo.replace('remove', '')
       todo = todo.strip()
       list.remove(todo)
       print('Todo:', list)
    elif todo == 'end':
            break
    else:
        list.append(todo)
        print('Todo:', list)


from sys import stdin

N = int(input())
dp = []
for _ in range(N):
    dp.append(list(map(int, stdin.readline().rstrip().split())))

for i in range(1, N):
    for j in range(len(dp[i])):
        if j == 0:
            dp[i][j] = dp[i-1][j]+ dp[i][j]
            continue
        else:
            left = dp[i-1][j-1] + dp[i][j]

        if j == len(dp[i])-1:
            dp[i][j] = dp[i-1][j-1] + dp[i][j]
            continue
        else:
            right = dp[i-1][j] + dp[i][j]

        dp[i][j] = max(right, left)

print(max(dp[-1]))


from product import Product

class Grower:
    def read_file(self):
        fp = open('gyakorlo_03/forras/products.txt', 'r', encoding='utf-8')
        lines = fp.readlines()
        fp.close()

        self.products = []

        for line in lines[1::]:
            (id, name, price, quantity) = line.split(';')
            product = Product(id, name, price, quantity)
            self.products.append(product)

    # legdrágább zöldség
    def larger(self):
        max_product = self.products[0]
        for product in self.products:
            if product.price > max_product.price:
                max_product = product
        print('Legdrágább:',
                max_product.name,
                max_product.price)

    # Az összes paprika ára
    def calcPepperPrice(self):
        count_product = self.products[0]
        for product in self.products:
            if product.name == 'paprika':
                count_product = product
        print('A paprika készleten ár alapján:',
        int(count_product.price) * int(count_product.weight))

    # Mennyi a vöröshagyma és a karalábé tömege együtt?
    def sumOnionKohlrabiWeight(self):
        for product in self.products:
            if product.name == 'karalábé':
                mass1_product = product
            if product.name == 'vöröshagyma':
                mass2_product = product
        print('A karalábé és a vöröshagyma össze készlete:',
        mass1_product.weight + mass2_product.weight)

    # Mi a neve legnagyobb tömegű zöldségnek?
    def showLargerWeight(self):
        max_weight = self.products[0]
        for product in self.products:
            if product.weight > max_weight.weight:
                max_weight = product
        print('A legnagyobb tömegő zöldség:',
        max_weight.name)

    #JÓ DE LASSÚ
    # Van karalábé?
    def isHaveKohlrabi(self):
        for product in self.products:
            if product.name == 'karalábé':
                product_true = True
        if product_true == True:
            print('Van karalábé')
        else:
            print('Nincs karalábé')

    #JOBB MEGOLDÁS
    #def isHaveKohlrabi(self):
    #   ker = 'karalábé'
    #    n = len(self.products)
    #    i = 0
    #    product = Product()
    #    product = self.products[0]
    #    while i<n and self.products[i].name != ker:
    #        i += 1
    #    if i<n:
    #        print('Van')
    #    else:
    #        print('Nincs')

    # Hány zöldség drágább mint 700 Ft.
    def moreThenSevenhundred(self):
        darab = 0
        for product in self.products:
            if product.price > 700:
                darab += 1
        print('Ennyi zöldség drágább mint 700 Ft:', darab)

grower = Grower()
grower.read_file()
grower.larger()
grower.calcPepperPrice()
grower.sumOnionKohlrabiWeight()
grower.showLargerWeight()
grower.isHaveKohlrabi()
grower.moreThenSevenhundred()


# -*- coding: utf-8 -*-
"""DataClean_winsupply
"""

import pandas as pd

df = pd.read_csv('winsupply.csv')
print(df.head())

def split_color(row):
  lst = row.split(',')
  # print(len(lst),lst)
  if(len(lst)>1):
    color = lst[-1]
  else:
    color = None
  print(color)
  return color
df['Color'] = df['Product name'].apply(split_color)

df.head()

def split_product(row):
  lst = row.split(',')
  # print(len(lst),lst)
  if(len(lst)>1):
    product = ','.join(lst[:-1])
  else:
    product = lst[-1]
  print(product)
  return product
df['Product name'] = df['Product name'].apply(split_product)

df.head()
df = df.drop('Weight',axis =1)

df.to_csv('dataclean_winsupply.csv')


#!/usr/bin/python3
"""
Projection algorithms for isometric perspective
"""
from models.base import BaseModel
import math
import numpy as np

class Projection():
    projection_type = None
    region_width = None
    region_height = None
    viewport = None
    camera = None

    def __init__(self, projection_type='isometric'):
        self.projection_type = projection_type

    def project_all(self, vertices):
        """
        Project all vertex from object
        """
        projected = []
        for vertex in vertices:
            projected.append(self.project(vertex))
        return projected

    def project(self, vertex, width=None, height=None):
        if self.projection_type == 'isometric':
            return self.isometric(vertex, width, height)
        elif self.projection_type == 'perspective':
            return self.perspective(vertex, width, height)
        elif self.projection_type == 'ortho':
            return self.ortho(vertex, width, height)

    def isometric(self, vertex, width, height):
        """
        Projects on vertex depends on the
        defined inclination
        """
        inc = 0.7
        x, y, z = float(vertex[0]), float(vertex[1]), float(vertex[2])
        Px = (inc * x) - (inc * y) + (width if width else self.region_width / 2)
        Py = ((1 - inc) * x) + ((1 - inc) * y) - z + (height if height else self.region_height / 2)
        return np.array([int(Px), int(Py), 1])

    def perspective(self, vertex, width, height):
        if not self.camera.Z0:
            self.camera.Z0 = (width if width else self.region_width / 2.0) / math.tan((self.camera.fov / 2.0) * math.pi / 180.0)
        Px = vertex[0] * self.camera.Z0 / (self.camera.Z0 + vertex[2])
        Py = vertex[1] * self.camera.Z0 / (self.camera.Z0 + vertex[2])

        return np.array([int(Px), int(Py), vertex[2]])

    def ortho(self, vertex, width, height):
        pass

class Engine3D():
    projection = None
    screen_dimensions = None
    cameras = []
    objects = []
    canvas = None
    motion_id = None
    click_id = None

    def __init__(self, projection_type='isometric', mode='Play'):
        self.projection = Projection(projection_type=projection_type)

    def calculate_map_width(self, terrain):
        """
        Get the width for the entire isometric map to fit the screen
        """
        fs = terrain.faces
        vr = terrain.vertices
        pos = fs[0][3] - 1
        w1 = self.screen_dimensions["width"]
        h1 = self.screen_dimensions["height"]
        x1 = self.projection.project(vr[len(vr) - 1 - pos], width=w1, height=h1).x
        x2 = self.projection.project(vr[pos], width=w1, height=h1).x
        y1 = self.projection.project(vr[0], width=w1, height=h1).y
        y2 = self.projection.project(vr[len(vr) - 1], width=w1, height=h1).y
        w = x1 - x2
        h = y1 - y2
        return int((w / self.screen_dimensions["width"])) + 2, int((h / self.screen_dimensions["height"])) + 2

    def raytrace(self, canvas_dimensions):
        width = canvas_dimensions.get('width')
        height = canvas_dimensions.get('height')

        ratio =  float(width) / height
        screen = (-1, 1/ratio, 1, -1/ratio)
        image = np.zeros((int(height), int(width), 4))
        image = image + np.array([0, 0, 0, 255])

        for i, y in enumerate(np.linspace(screen[1], screen[3], int(height))):
            for j, x in enumerate(np.linspace(screen[0], screen[2], int(width))):
                light = world['lights'][0]
                pixel = np.array([x, y, 0])
                origin = world['camera']
                direction = normalize(pixel - origin)

                nearest_object, min_distance = nearest_intersected_object(world['objects'], origin, direction)
                if nearest_object is None:
                    continue

                intersection = origin + min_distance  * direction

                normal_to_surface = normalize(intersection - nearest_object['center'])
                shifted_point = intersection + 1e-5 * normal_to_surface

                intersection_to_light = normalize(light['position'] - shifted_point)

                _, min_distance = nearest_intersected_object(world['objects'], shifted_point, intersection_to_light)
                intersection_to_light_distance = np.linalg.norm(light['position'] - intersection)
                is_shadowed = min_distance < intersection_to_light_distance

                if is_shadowed:
                    continue

                illumination = np.zeros((4))

                illumination += nearest_object['ambient'] * light['ambient']

                illumination += nearest_object['diffuse'] * light['diffuse'] * np.dot(intersection_to_light, normal_to_surface)

                intersection_to_camera = normalize(world['camera'] - intersection)
                H = normalize(intersection_to_light + intersection_to_camera)
                illumination += nearest_object['specular'] * light['specular'] * np.dot(normal_to_surface, H) ** (nearest_object['shininess'] / 4)

                illumination[3] = 1
                image[i, j] = np.clip(illumination, 0, 1) * 255

        pixels = np.concatenate(image).ravel().tolist()
        return pixels

    def wireframe(self, projection_type, canvas_dimensions):
        """
        Draw the isometric grid in red
        using the projected vertex
        """
        # Configure viewportself.screen_dimensions = {
        self.screen_dimensions = {
            "width": canvas_dimensions['width'],
            "height": canvas_dimensions['height']
        }

        self.projection.viewport = self.screen_dimensions
        self.projection.projection_type = projection_type
        self.projection.camera = self.cameras[0]
        self.projection.region_width = self.screen_dimensions.get('width')
        self.projection.region_height = self.screen_dimensions.get('height')

        # Draw polygons for each object
        projected_objects = []
        for obj in self.objects:
            print('Rendering: ', obj)

            world_transformation = obj.translate(
                    obj.rotate(obj.scale(obj.vertices))
                )
            camera_transformation = obj.rotate(
                obj.translate(world_transformation, np.array(
                    [
                        -self.projection.camera.translation[0],
                        -self.projection.camera.translation[1],
                        -self.projection.camera.translation[2]
                    ]
                )), np.array(
                        [
                            -self.projection.camera.rotation[0],
                            -self.projection.camera.rotation[1],
                            -self.projection.camera.rotation[2]
                        ]

                    )
            )
            projected_view = self.projection.project_all(camera_transformation)
            normalized_view = obj.normalize(
                projected_view, self.projection.viewport
            )
            projected_faces = []
            for face in obj.faces:
                poly = []
                for vertex_index in face:
                    poly.append(
                        [
                            int(normalized_view[vertex_index][0]),
                            int(normalized_view[vertex_index][1]),
                            int(camera_transformation[vertex_index][2])
                        ]
                    )
                projected_faces.append(poly)
            center = list(obj.calculate_center(normalized_view))
            vertices = [ [int(p[0]), int(p[1]), int(p[2])] for p in normalized_view]
            # print('calculated_center: ', center)
            # print(''vertices)
            projected_objects.append({
                'vertices': vertices,
                'faces': obj.clip(self.projection.camera.translation, projected_faces),
                'center': [ int(coord) for coord in obj.calculate_center(normalized_view) ],
            })
        print(projected_objects[0]['faces'][:20])
        return projected_objects

    def add_camera(self, camera):
        self.cameras.append(camera)

    def add_object(self, obj):
        self.objects.append(obj)

    def __str__(self):
        return """
        Engine 3D
        v 0.0.1
        viewport: {} x {}
        Cameras:
        {}
        Objects:
        {}
        """.format(
            self.screen_dimensions.get('width'),
            self.screen_dimensions.get('heiht'),
            [str(cam) for cam in self.cameras],
            [str(obj) for obj in self.objects]
        )


import socket
import math

#dati dell'host: indirizzo del server e porta in ascolto
SERVER_HOST = "INSERISCI INDIRIZZO"
LPORT = 0000 #INSERISCI PORTA

#creazione del socket TCP di rete composto dall'indirizzo IP del server e dalla porta
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

#richiesta di connessione al server
sock.connect((SERVER_HOST, LPORT))

#messaggio inviato dal server una volta effettuata la connessione
msg = sock.recv(1024).decode("utf-8")
print(msg)

#estrapolazione dei numeri dal testo e conversione in integer
num_1 = int(msg.split()[27])
num_2 = int(msg.split()[31])

#calcolo del risultato e arrotondamento per due cifre decimali
result = round((math.sqrt(num_1) * num_2), 2)

#invio risposta al server (aggiungere "\n" alla stringa per evitare errori di buffer)
sock.send((str(result) + "\n").encode('utf-8'))

#conferma di avvenuta risposta
confirmation = sock.recv(1024).decode('utf-8')
print(confirmation)

#chiusura connessione
sock.close()


from typing import List

class Solution:
    def findUnsortedSubarray(self, nums: List[int]) -> int:
        mono_queue = list()
        nr_nums = len(nums)
        for i_num in range(nr_nums):
            num = nums[i_num]
            while mono_queue and num < mono_queue[-1][1]:
                index, value = mono_queue.pop()
            mono_queue.append((i_num, num))
        subarr_start = 0
        while subarr_start < len(mono_queue) and mono_queue[subarr_start][0] == subarr_start:
            subarr_start += 1
        if subarr_start == len(mono_queue):
            return 0

        mono_queue = list()
        for i_num in range(nr_nums - 1, -1, -1):
            num = nums[i_num]
            while mono_queue and num > mono_queue[-1][1]:
                index, value = mono_queue.pop()
            mono_queue.append((i_num, num))
        subarr_end = nr_nums - 1
        while subarr_end > 0 and mono_queue[nr_nums - 1 - subarr_end][0] == subarr_end:
            subarr_end -= 1
        return subarr_end - subarr_start + 1

sol = Solution()
cases = [
    {
        "input": [2,6,4,8,10,9,15],
        "expect": 5
    },
    {
        "input": [1, 2, 3, 4],
        "expect": 0
    },
    {
        "input": [1],
        "expect": 0
    },
]

for case in cases:
    result = sol.findUnsortedSubarray(case["input"])
    print(case["input"], result)
    assert result == case['expect']


# faça um programa que leia o nome completo de uma pessoa e mostre o primeiro e ultimo
# nomes separadamente

nome = str(input('digite seu nome')).strip()
nome = nome.title()
nome = nome.split()

qt = len(nome)

print(f'seu nome é {nome[0]}')
print(f'o seu sobrenome é {nome[qt - 1]}')


import copy
import collections

__all__ = ['Parameter', 'ParameterDict', 'OrderedParameterDict',
           'BaseParameterDict', 'SyncedParameters']

class Parameter:
    """A named parameter with a optional value.

    The value can be changed at any time and very basic
    operations can be queued and evaluated on demand.

    Parameters
    ----------
    name : str
        Name of parameter.
    value : int or float, optional, default=None
        Value of parameter. Other types may work but not supported.

    Attributes
    ----------
    name : str
        Name of parameter.
    value : int or float
        Value of parameter, without operations applied.
    operations : list
        List of operations to apply.

    Raises
    ------
    ValueError
        The parameter is evaluated without a value.
    NotImplementedError
        Operations which require parentheses are applied.

    See Also
    --------
    Parameters : Collection of synced ``Parameter`` objects.

    Notes
    -----
    Basic operations can be applied to this object, i.e., ``+``, ``-``, ``*`` and ``/``.
    The ``Parameter`` object must be the left-most entity in an expression.
    Expressions which require parentheses are not supported.
    The only types that are supported in expressions are ``Parameter``, ``float``
    and ``int``.
    Any type should work as a value for ``Parameter`` (such as an array), however,
    only ``float`` and ``int`` are supported.
    See the examples for more details in how it works.

    Examples
    --------
    >>> Parameter('x')
    'x'
    >>> Parameter('x', 12)
    '12'
    >>> Parameter('x') + 1
    'x+1'
    >>> Parameter('x', 1) + 1
    'x+1'
    >>> a = Parameter('y') * 2
    >>> a
    'y*2'
    >>> a.value = 5
    >>> a == 10
    True
    >>> a.eval()
    10
    >>> (a * 10).eval()
    100
    >>> a * 10 + 1.5
    'y*2*10+1.5'
    >>> (a + 10) * 2
    Traceback (most recent call last):
     ...
    NotImplementedError: Parameter equations with brackets are not supported.
    """
    def __init__(self, name, value=None):
        self.name = str(name)
        self.value = value
        self.operations = []

    def __add__(self, other):
        return self.apply_operation('+', other)

    def __sub__(self, other):
        return self.apply_operation('-', other)

    def __mul__(self, other):
        return self.apply_operation('*', other)

    def __truediv__(self, other):
        return self.apply_operation('/', other)

    def apply_operation(self, op, other):
        """Apply an operation to the ``Parameter``.

        Parameters
        ----------
        op : str
            Operation symbol to apply. ``+``, ``-``, ``*`` or ``/``.
        other : float or int
            Number on RHS.

        Returns
        -------
        obj
            A copy of ``self`` with the operation added to the queue.

        Notes
        -----
        You should only use this method directly if a builtin Python operation
        is not otherwise implemented in this class, i.e., do ``a + 1`` and not
        ``a.apply_operation('+', 1)``.
        """
        obj = self.copy()  # Operate on a copy
        obj.operations += [(op, other)]  # Add to queue
        obj.verify()  # Verify no parentheses
        return obj

    def __eq__(self, other):
        # Test equality against the expression's numeric value.
        # If the parameter doesn't have a value,
        # test equality against the string representation
        # of the unevaluated expression.
        try:
            exp = self.eval()
        except ValueError:
            exp = str(self)
        return exp == other

    @property
    def value_or_name(self):
        """Returns its value if set, otherwise returns its name."""
        if self.value is None:
            return self.name
        else:
            return self.value

    def eval(self):
        """Evaluate the expression using the parameter's current value."""
        if self.value is None:
            raise ValueError('Cannot evaluate without a value.')
        return eval(
            str(self),
            {'__builtins__': None},
            {self.name: self.value}
        )

    def __str__(self):
        if len(self.operations) == 0:
            return str(self.value_or_name)
        ret = str(self.name)
        for op, val in self.operations:
            if isinstance(val, Parameter):
                val = val.eval()
            ret += f'{op}{val}'
        return ret

    def __repr__(self):
        return f"'{str(self)}'"

    def verify(self):
        """Enforce order of operations."""
        disable_mul_div = False
        for op, _ in self.operations:
            if op in ('+', '-'):
                disable_mul_div = True
            elif op in ('*', '/') and disable_mul_div:
                raise NotImplementedError(
                    'Parameter equations with brackets are not supported.'
                )

    def copy(self):
        """Return a copy of the parameter."""
        return self.__copy__()

    def __copy__(self):
        obj = self.__class__(copy.copy(self.name), copy.copy(self.value))
        obj.operations = copy.deepcopy(self.operations)
        return obj

    def __deepcopy__(self, memodict={}):
        return self.__copy__()

class SyncedParameters:
    """Database for keeping :class:`Parameter` objects in sync.

    :class:`Parameter` objects with the same name can be linked with this class
    and kept in sync with each other.

    Examples
    --------
    >>> a = Parameter('x') + 1
    >>> b = Parameter('x', 2) * 3
    >>> c = Parameter('y')

    Now that :class:`Parameter` objects have been created,
    we can keep them in sync.

    >>> s = SyncedParameters()
    >>> s.track_object(a)
    >>> s.track_object(b)
    >>> s.track_object(c)

    Notice how the value of `a` has been synced to match the
    value provided in `b`.

    >>> a == 3
    True

    The value of a named parameter can be updated and the
    change is propagated to all :class:`Parameter` objects
    of that name.

    >>> s.update_parameter('x', 3)
    >>> a == 4
    True
    >>> b == 9
    True
    """
    @property
    def _tracked(self):
        """Dictionary of tracked :class:`Parameters` grouped by name.

        Notes
        -----
        Has the form: ``{Parameter.name: {'value': Parameter.value, 'objects': List[Parameter]}}``.
        All ``Parameter.value`` of the same ``Parameter.name`` are
        identical in both ``'value'`` and all in ``'objects'``.
        """
        if not hasattr(self, '_tracked_objs'):
            self._tracked_objs = {}
        return self._tracked_objs

    def track_object(self, obj):
        """Add a new :class:`Parameter` object to keep in sync.

        Parameters
        ----------
        obj : :class:`Parameter`
            The parameter object to keep in sync.

        Notes
        -----
        Any objects added must not have a value that contradicts the existing
        value of the parameter (if set). Objects added which have no value set
        will inherit the value of the other parameters of the same name.
        """
        if self.exists(obj.name):  # If parameter already registered...
            if obj.value is not None:  # ...copy incoming value to existing.
                # (No more than one value should be associated with each name.)
                if self.has_value(obj.name) and obj.value is not self.get_parameter(obj.name):
                    raise ValueError(f"'{obj.name}' value {obj.value} does not match "
                                     f"existing value {self.get_parameter(obj.name)}.")
                self.update_parameter(obj.name, obj.value)
            elif self.has_value(obj.name):  # ...copy existing value to incoming.
                obj.value = self.get_parameter(obj.name)
            self._tracked[obj.name]['objects'] += [obj]
        else:
            self._tracked[obj.name] = {'value': obj.value, 'objects': [obj]}

    def update_parameter(self, name, value):
        """Update the value of a named parameter.

        All tracked :class:`Parameter` objects of this name will be updated.

        Parameters
        ----------
        name : string
            Name of parameter to update.
        value : float or int
            New value of parameter. Other types may work.
        """
        self._tracked[name]['value'] = value
        for obj in self._tracked[name]['objects']:
            obj.value = value

    def get_parameter(self, name):
        """Get the value of the named parameter.

        Parameters
        ----------
        name : str
            Name of parameter to get value of.

        Returns
        -------
        value
            The current value of the parameter. ``None`` if not set.
        """
        return self._tracked[name]['value']

    def has_value(self, name: str) -> bool:
        """Whether the named parameter has a value set."""
        return False if self.get_parameter(name) is None else True

    def exists(self, name: str) -> bool:
        """Whether any parameters of a particular name are tracked."""
        try:
            self._tracked[name]
        except KeyError:
            return False
        else:
            return True

class BaseParameterDict(SyncedParameters):
    """
    A base class for dictionaries of :class:`Parameter` objects.

    The same parameters existing across multiple dictionary
    values can be kept in sync with each other.
    For a :class:`Parameter` object to be kept in sync it must be
    located in the dictionary such that ``{key: Parameter}`` or
    ``{key: List[Parameter]}``.
    """
    def __setitem__(self, key, value):
        if isinstance(value, Parameter):
            self.track_object(value)
        elif isinstance(value, list):
            for item in value:
                if isinstance(item, Parameter):
                    self.track_object(item)
        super().__setitem__(key, value)

    def eval(self):
        """Return a copy of the dictionary with all :class:`Parameter` objects evaluated."""
        edict = self.__class__.__bases__[-1]()
        for key, value in self.items():
            if isinstance(value, Parameter):
                value = value.eval()
            elif isinstance(value, list):
                lst = []
                for i, v in enumerate(value):
                    if isinstance(v, Parameter):
                        lst += [v.eval()]
                    else:
                        lst += [v]
                value = lst
            edict[key] = value
        return edict

class ParameterDict(BaseParameterDict, collections.UserDict):
    """
    An unordered dictionary of :class:`Parameter` objects.

    The same parameters existing across multiple dictionary
    values can be kept in sync with each other.
    For a :class:`Parameter` object to be kept in sync it must be
    located in the dictionary such that ``{key: Parameter}`` or
    ``{key: List[Parameter]}``.

    Examples
    --------
    >>> d = ParameterDict({
    ...     'a': Parameter('x') + 1,
    ...     'b': [2, Parameter('x'), 5],
    ...     'c': {1, 2, 3},
    ... })
    >>> d
    {'a': 'x+1', 'b': [2, 'x', 5], 'c': {1, 2, 3}}
    >>> d.update_parameter('x', 1)
    >>> d.eval()
    {'a': 2, 'b': [2, 1, 5], 'c': {1, 2, 3}}
    """

class OrderedParameterDict(BaseParameterDict, collections.OrderedDict):
    """
    An ordered dictionary of :class:`Parameter` objects.

    The same parameters existing across multiple dictionary
    values can be kept in sync with each other.
    For a :class:`Parameter` object to be kept in sync it must be
    located in the dictionary such that ``{key: Parameter}`` or
    ``{key: List[Parameter]}``.

    Examples
    --------
    >>> d = OrderedParameterDict([
    ...     ('a', Parameter('x') + 1),
    ...     ('b', [2, Parameter('x'), 5]),
    ...     ('c', {1, 2, 3}),
    ... ])
    >>> d
    OrderedParameterDict([('a', 'x+1'), ('b', [2, 'x', 5]), ('c', {1, 2, 3})])
    >>> d.update_parameter('x', 1)
    >>> d.eval()
    OrderedDict([('a', 2), ('b', [2, 1, 5]), ('c', {1, 2, 3})])
    """


import numpy as np
import pandas as pd
from statistics import mode
import distances as distances

class KNeighbors:
    def __init__(self, n_neighbors=3, trainig_data=None, distance_type='euclidean'):
        self.n_neighbors = n_neighbors
        condition = distance_type == 'manhattan'
        self.calc_distance = distances.manhattan_distance if(condition) else distances.euclidean_distance
        self.training_dataframe = trainig_data

    def classify(self,new_object):
        nearest_neighbors = [[float('inf'),None] for _ in range(self.n_neighbors)] # create space to nearest_neighbors
        for _class, _object in self.training_dataframe.iterrows(): # _class = index and _object = data series
            neighbor_distance = self.calc_distance(new_object,_object.values) # get neighbor distance
            nearest_neighbors.sort(key=lambda dist: dist[0], reverse=True) # sort by smallest to largest
            if neighbor_distance < nearest_neighbors[0][0]: # if actual distance < smallest
                nearest_neighbors[0][0] = neighbor_distance # smallest = atual
                nearest_neighbors[0][1] = _class # smallest name = actual class name
        nearest_neighbors = [_class[1] for _class in nearest_neighbors] # set nearest_neighbors
        return mode(nearest_neighbors) # return nearest_neighbors into mode