from joblib import Parallel, delayed

from mushroom.utils import spaces
from mushroom.environments import MDPInfo
from mushroom.algorithms.policy_search import *
from mushroom.distributions import *
from mushroom.approximators.parametric import LinearApproximator
from mushroom.approximators.regressor import Regressor
from mushroom.utils.dataset import compute_J
from mushroom.policy import DeterministicPolicy

from mushroom.utils.parameters import Parameter, AdaptiveParameter

from mushroom_hierarchical.core.hierarchical_core import HierarchicalCore
from mushroom_hierarchical.environments.segway_linear_motion import SegwayLinearMotion
from mushroom_hierarchical.blocks.computational_graph import ComputationalGraph
from mushroom_hierarchical.blocks.control_block import ControlBlock
from mushroom_hierarchical.blocks.basic_operation_block import *
from mushroom_hierarchical.blocks.model_placeholder import PlaceHolder
from mushroom_hierarchical.utils.callbacks.collect_distribution_parameter import\
    CollectDistributionParameter
from mushroom_hierarchical.policy.deterministic_control_policy import DeterministicControlPolicy

from experiments.segway.lqr_cost_segway import lqr_cost_segway
from experiments.segway.angle_to_angle_diff_complete_state import *
from experiments.segway.pick_first_state import pick_first_state

def fall_reward(inputs):
    state = inputs[0]
    if abs(state[1]) > np.pi / 2:
        res = -10000.0
    else:
        res = 0.0
    return np.array([res])

def lqr_cost_segway(ins):
    x = np.concatenate(ins)
    Q = np.diag([3.0, 0.1, 0.1])
    J = x.dot(Q).dot(x)
    reward = -J

    return np.array([reward])

def segway_experiment(alg_high, alg_low, params_high, params_low):

    np.random.seed()

    # Model Block
    mdp = SegwayLinearMotion(goal_distance=1.0)

    #State Placeholder
    state_ph = PlaceHolder(name='state_ph')

    #Reward Placeholder
    reward_ph = PlaceHolder(name='reward_ph')

    #Last_In Placeholder
    lastaction_ph = PlaceHolder(name='lastaction_ph')

    # Function Block 1
    function_block1 = fBlock(name='f1 (pick distance to goal state var)',
                             phi=pick_first_state)

    # Function Block 2
    function_block2 = fBlock(name='f2 (build state)',
                             phi=angle_to_angle_diff_complete_state)

    # Function Block 3
    function_block3 = fBlock(name='f3 (reward low level)',
                             phi=lqr_cost_segway)

    # Function Block 4
    function_block4 = addBlock(name='f4 (add block)')

    # Function Block 5
    function_block5 = fBlock(name='f5 (fall punish low level)', phi=fall_reward)

    # Features
    approximator1 = Regressor(LinearApproximator,
                             input_shape=(1,),
                             output_shape=(1,))

    # Policy H
    n_weights = approximator1.weights_size
    mu1 = np.zeros(n_weights)
    sigma1 = 2.0e-2*np.ones(n_weights)
    pi1 = DeterministicPolicy(approximator1)
    dist1 = GaussianDiagonalDistribution(mu1, sigma1)

    # Agent H
    lim = np.pi/2
    mdp_info_agent1 = MDPInfo(observation_space=mdp.info.observation_space,
                              action_space=spaces.Box(-lim, lim, (1,)),
                              gamma=mdp.info.gamma,
                              horizon=mdp.info.horizon)
    agent_high = alg_high(dist1, pi1, mdp_info_agent1, **params_high)

    # Policy L
    approximator2 = Regressor(LinearApproximator,
                              input_shape=(3,),
                              output_shape=(1,))
    n_weights2 = approximator2.weights_size
    mu2 = np.zeros(n_weights2)
    sigma2 = 2.0*np.ones(n_weights2)
    pi2 = DeterministicControlPolicy(approximator2)
    dist2 = GaussianDiagonalDistribution(mu2, sigma2)

    # Agent Low
    mdp_info_agent2 = MDPInfo(observation_space=spaces.Box(
        low=mdp.info.observation_space.low[1:], #FIXME FALSE
        high=mdp.info.observation_space.high[1:], #FIXME FALSE
        shape=(3,)),
        action_space=mdp.info.action_space,
        gamma=mdp.info.gamma, horizon=mdp.info.horizon)

    agent_low = alg_low(dist2, pi2, mdp_info_agent2, **params_low)

    # Control Block 1
    parameter_callback1 = CollectDistributionParameter(dist1)
    control_block1 = ControlBlock(name='Control Block High', agent=agent_high,
                                  n_eps_per_fit=n_ep_per_fit*2,
                                  callbacks=[parameter_callback1])

    # Control Block 2
    parameter_callback2 = CollectDistributionParameter(dist2)
    control_block2 = ControlBlock(name='Control Block Low', agent=agent_low,
                                  n_eps_per_fit=n_ep_per_fit,
                                  callbacks=[parameter_callback2])
    control_block1.set_mask()

    # Algorithm
    blocks = [state_ph, reward_ph, lastaction_ph, control_block1,
              control_block2, function_block1, function_block2,
              function_block3, function_block4, function_block5]

    state_ph.add_input(control_block2)
    reward_ph.add_input(control_block2)
    lastaction_ph.add_input(control_block2)
    control_block1.add_input(function_block1)
    control_block1.add_reward(reward_ph)
    control_block2.add_input(function_block2)
    control_block2.add_reward(function_block4)
    function_block1.add_input(state_ph)
    function_block2.add_input(control_block1)

    function_block2.add_input(state_ph)
    function_block3.add_input(function_block2)
    function_block5.add_input(state_ph)
    function_block4.add_input(function_block3)
    function_block4.add_input(function_block5)
    computational_graph = ComputationalGraph(blocks=blocks, model=mdp)
    core = HierarchicalCore(computational_graph)

    # Train
    dataset_eval_run = core.evaluate(n_episodes=eval_run, render=False)
    J = compute_J(dataset_eval_run, gamma=mdp.info.gamma)
    print('J at start : ' + str(np.mean(J)))

    mask_done = False
    for n in range(n_epochs):
        print('ITERATION', n)

        if n == 2:
            control_block1.unset_mask()
        core.learn(n_episodes=n_iterations*n_ep_per_fit, skip=True)
        dataset_eval_run = core.evaluate(n_episodes=eval_run, render=False)
        J = compute_J(dataset_eval_run, gamma=mdp.info.gamma)
        print('J at iteration ' + str(n) + ': ' + str(np.mean(J)))
        print('dist H:', dist1.get_parameters())
        print('dist L mu:', dist2.get_parameters()[:3])
        print('dist L sigma:', dist2.get_parameters()[3:])

if __name__ == '__main__':
    learning_rate_high = Parameter(value=1e-5)
    learning_rate_low = AdaptiveParameter(value=1e-1)
    eps_high = 0.05
    eps_low = 0.05
    beta_high = 0.01
    beta_low = 2e-3

    algs_params = [
            #(REPS, REPS, {'eps': eps_high}, {'eps': eps_low}),
            (RWR, RWR, {'beta': beta_high}, {'beta': beta_low}),
            #(PGPE, PGPE, {'learning_rate': learning_rate_high},
             #{'learning_rate': learning_rate_low}),
        #(PGPE, RWR, {'learning_rate' : learning_rate_high}, {'beta' :
        # beta_low})
        ]

    n_jobs = 1
    how_many = 1
    n_epochs = 10
    n_iterations = 4
    n_ep_per_fit = 25
    eval_run = 20

    experiment_params = {'how_many': how_many,
                         'n_epochs': n_epochs,
                         'n_iterations': n_iterations,
                         'n_ep_per_fit': n_ep_per_fit,
                         'eval_run': eval_run}
    for alg_high, alg_low, params_high, params_low in algs_params:
        Js = Parallel(n_jobs=n_jobs)(delayed(segway_experiment)
                                 (alg_high, alg_low, params_high, params_low)
                                 for i in range(how_many))


from keras.datasets import boston_housing
from keras import models,layers
import numpy as np
from math import *
(train_data,train_prices),(test_data,test_prices) = boston_housing.load_data()
print(train_data.shape)
print(test_data.shape)
print(train_prices.shape)
print(test_prices.shape)
mean=train_data.mean(axis=0)
train_data-=mean
std=train_data.std(axis=0)
train_data/=std
test_data-=mean
test_data/=std

def housing_model():
    #Using a function due to multiple instantiation
    model=models.Sequential()
    model.add(layers.Dense(64,activation='relu',input_shape=(train_data.shape[1],)))
    model.add(layers.Dense(64,activation='relu'))
    model.add(layers.Dense(1))
    model.compile(optimizer='rmsprop',loss='mse',metrics=['mae'])
    return model

#K-fold validation
k=4
num_val_samples=len(train_data)//k
num_epochs=100
all_scores=[]
for i in range(k):
    print('Processing Fold #',i)
    #Prepare the validation data through partitions
    val_data = train_data[i*num_val_samples:(i+1)*num_val_samples]
    val_prices = train_prices[i*num_val_samples:(i+1)*num_val_samples]

    #Training data
    partial_train_data = np.concatenate(
        [train_data[:i*num_val_samples],
         train_data[(i+1)*num_val_samples:]],
        axis=0)
    partial_train_prices = np.concatenate(
        [train_prices[:i*num_val_samples],
         train_prices[(i+1)*num_val_samples:]],
        axis=0)

    #Build the Keras model
    model = housing_model()
    #train the model
    model.fit(partial_train_data,partial_train_prices,
              epochs=num_epochs,batch_size=1,verbose=0)
    val_mse,val_mae=  model.evaluate(val_data,val_prices,verbose=0)
    all_scores.append(val_mae)

print(all_scores)


import copy

class Grid:
    def __init__(self, grid_size, spos) -> None:
        self.size = grid_size
        self.blank_grid = [[0 for i in range(grid_size)] for j in range(grid_size)]
        self.grid = self.init_grid(copy.deepcopy(self.blank_grid), grid_size, spos)
        self.nummax = 0
        self.xmax = []
        self.ymax = []

    def init_grid(self, grid, n, spos):
        iloc = n // 2
        for pos in spos:
            grid[iloc - pos[0]][iloc - pos[1]] = 1
        # gprint("Starting Position", grid)
        return grid

def gprint(title, grid):
    g = len(grid[0]) * 3 - 1
    print(f'\n{" " + title + " ":+^{g}}')
    for i in grid:
        print(" ".join([f"{j:02d}" for j in i]).replace("00", ".."))

def rotate_grid(grid):
    rotated = copy.deepcopy(grid)
    size = len(grid[0]) - 1
    for y, line in enumerate(grid):
        for x, item in enumerate(line):
            rotated[size - x][y] = grid[y][x]
    return rotated

def reduce_grid(grid):
    igrid = [[grid[j][i] for j, _ in enumerate(grid)] for i, _ in enumerate(grid)]
    c = []
    for g in (grid, igrid):
        for line, line_data in enumerate(g):
            if [i for i in line_data if i != 0]:
                lower_limit = int(line)
                break
        for line, line_data in enumerate(g[::-1]):
            if [i for i in line_data if i != 0]:
                upper_limit = len(grid) - int(line)
                break
        c.append((lower_limit, upper_limit))

    new_grid = [
        [grid[c[0][0] + i][c[1][0] + j] for j, _ in enumerate(grid[c[1][0] : c[1][1]])]
        for i, _ in enumerate(grid[c[0][0] : c[0][1]])
    ]
    return new_grid, len(new_grid), len(new_grid[0])

def valid_squares(grid, next):
    G.ogrid = copy.deepcopy(G.blank_grid)
    result = []
    for x in range(1, G.size - 1):
        for y in range(1, G.size - 1):
            if grid[y][x] == 0:
                s = 0
                for i in range(x - 1, x + 2):
                    for j in range(y - 1, y + 2):
                        if (i, j) != (x, y):
                            s += grid[j][i]
                if s == next:
                    result.append((x, y))
                G.ogrid[y][x] = s
    return result

def solve(grid, n):
    for opt in valid_squares(grid, n):
        grid[opt[1]][opt[0]] = n
        solve(grid, n + 1)
        if n > G.nummax:
            G.nummax = int(n)
            G.final_grid = copy.deepcopy(grid)
        _, xmax, ymax = reduce_grid(grid)
        G.xmax.append(xmax)
        G.ymax.append(ymax)
        grid[opt[1]][opt[0]] = 0

def generate_configs(n, xmax, ymax):
    if n == 2:
        return [[(-1, -1), (1, 1)], [(-1, 0), (1, 0)], [(0, 1), (0, 0)]]
    if n == 3:
        return [[(-1, -1), (1, 1), (1, -1)], [(-1, -1), (1, 0), (1, -1)]]
    if n == 4:
        return [
            [(-1, -1), (1, 1), (1, -1), (1, 0)],
            [(-1, -1), (1, 0), (1, -1), (0, 1)],
        ]

def main(n, x=0, y=0):
    global G
    xmax, ymax, snummax = [], [], []
    configs = generate_configs(n, x, y)
    for spos in configs:
        G = Grid(11, spos)
        solve(G.grid, 2)
        xmax.append(max(G.xmax))
        ymax.append(max(G.ymax))
        snummax.append(G.nummax)
        # print(f"Max N: {G.nummax} -- Max Grid Size: {max(G.xmax)},{max(G.ymax)}")
        # gprint("Best Result", reduce_grid(G.final_grid)[0])
    accum_results.append(
        {"Q": n, "MaxN": max(snummax), "GridXmax": max(xmax), "GridYmax": max(ymax)}
    )
    return max(xmax), max(ymax)

accum_results = []

x, y = main(2, 0, 0)
x, y = main(3, x, y)
x, y = main(4, x, y)
print(accum_results)

t = copy.deepcopy(G.grid)
gprint("Rotate", t)
for i in range(4):
    t = rotate_grid(t)
    s = reduce_grid(t)[0]
    gprint("Rotate", t)
    gprint("Rotate reduced", s)


#!/usr/bin/python3
"""
Write a Python script that takes in a letter and sends
a POST request to http://0.0.0.0:5000/search_user
with the letter as a parameter.
"""

if __name__ == "__main__":
    import requests
    from sys import argv

    if len(argv) > 1:
        q = str(argv[1])
    else:
        q = ""

    data = {'q': q}
    res = requests.post("http://0.0.0.0:5000/search_user", data)

    try:
        json = res.json()
        if(len(json)) == 0:
            print("No result")
        if(len(json) > 0):
            print("[{}] {}".format(json["id"], json["name"]))
    except ValueError as error:
        print("Not a valid JSON")


import logging

from mvt.common.indicators import Indicators
from mvt.common.module import run_module
from mvt.ios.modules.mixed.safari_browserstate import SafariBrowserState

from ..utils import get_ios_backup_folder

class TestSafariBrowserStateModule:
    def test_parsing(self):
        m = SafariBrowserState(target_path=get_ios_backup_folder())
        m.is_backup = True
        run_module(m)
        assert len(m.results) == 1
        assert len(m.timeline) == 1
        assert len(m.detected) == 0

    def test_detection(self, indicator_file):
        m = SafariBrowserState(target_path=get_ios_backup_folder())
        m.is_backup = True
        ind = Indicators(log=logging.getLogger())
        ind.parse_stix2(indicator_file)
        # Adds a file that exists in the manifest.
        ind.ioc_collections[0]["domains"].append("en.wikipedia.org")
        m.indicators = ind
        run_module(m)
        assert len(m.detected) == 1
        assert len(m.results) == 1
        assert m.results[0]["tab_url"] == "https://en.wikipedia.org/wiki/NSO_Group"


# https://leetcode.com/problems/number-of-islands/
"""
Given an m x n 2D binary grid grid which represents a map of '1's (land) and '0's (water), return the number of islands
An island is surrounded by water and is formed by connecting adjacent lands horizontally or vertically
You may assume all four edges of the grid are all surrounded by water

Input: grid = [
  ["1","1","0","0","0"],
  ["1","1","0","0","0"],
  ["0","0","1","0","0"],
  ["0","0","0","1","1"]
]
Output: 3
"""
from collections import deque

# strategy: for each ceil containing '1', expand the exploration of the neighbor nodes as a graph traversal (BFS in this case)
# although this problem is about a grid, it can be solved as A GRAPH TRAVERSAL
def num_islands(grid):
    if not grid:
        return 0

    rows, cols = len(grid), len(grid[0]) # have the width and heigth dimensions as variables from the beginning
    visited  = set()                     # as any other implementation of BFS, have a set() to not visit nodes twice
    islands = 0

    def bfs(r, c):                       # Breadth First Search implementation
        queue = deque()                  # natural use of a 'queue'
        queue.append((r, c))
        visited.add((r, c))              # set() to not visit nodes more than once

        while queue:
            pos_r, pos_c = queue.popleft()                  # dequeue the most-left value from the queue
            directions = [[0, 1], [0, -1], [1, 0], [-1, 0]] # these are the 4 dirs of ceils to check: right, left, above, under

            for dir_r, dir_c in directions:             # apply one new direction at a time, to check the 4 ceils that
                check_r = pos_r + dir_r                 # can potentially contain a neighbour piece of land
                check_c = pos_c + dir_c                 # left, right, above, under

                if (check_r in range(rows) and          # the ceil to check, must be still within the boundaries of the grid
                    check_c in range(cols) and          # the ceil to check, must be still within the boundaries of the grid
                    grid[check_r][check_c] == '1' and   # check if it is, indeed, a piece of land
                    (check_r, check_c) not in visited): # IMPORTANT! make sure that the ceil hasn't been visited before

                    queue.append((check_r, check_c))    # if so, add it to the queue so the (node) can be explored as well
                    visited.add((check_r, check_c))     # make sure to mark it as visited, so it's not counted twice as part of some island

    for r in range(rows):               # classic nested for-loop to explore the grid (matrix, 2D array)
        for c in range(cols):
            if (grid[r][c] == '1' and   # if the ceil represents a piece of land, and this node hasn't been explored...
                (r, c) not in visited):
                bfs(r, c)               # keep expanding this piece of land, by performing BFS on this ceil (node)
                islands += 1            # in the end, this piece of land will represent a new island

    return islands

# O(n*m) time  // n and m being the dimension sizes of the grid
# O(n*m) space // the queue and set() are aux memory

# explanation: https://www.youtube.com/watch?v=pV2kpPD66nE&ab_channel=NeetCode


from geocoder import get_coordinates, get_ll_span
from mapapi import show_map

import random
import sys

def main():
    towns = [
        "Ярославль",
        "Нижний Новгород",
        "Казань",
        "Великий Новгород",
        "Архангельск",
        "Саратов",
        "Петрозаводск",
        "Астрахань"
    ]
    random.shuffle(towns)

    for town in towns:
        ll, spn = get_ll_span(town)
        map_type = "sat"
        if random.random() > 0.5:
            spn = "0.001,0.001"
            map_type = "map"
        ll_spn = "ll={ll}&spn={spn}".format(**locals())
        show_map(ll_spn, map_type)

if __name__ == "__main__":
    main()


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

"""
This script is for generating skeleton based on the predicted joint&bone probability maps.
It is formulated as a minimal spinning tree problem.
A soft non-maximum suppression approach is used for better results.
Refer to our paper for more details.
"""

import os
import glob
import cv2
import sys
import numpy as np
from scipy.ndimage import correlate as correlate
from scipy.ndimage import binary_erosion as binary_erosion

from util.tree_utils import TreeNode
from util.rigging_parser.obj_parser import Mesh_obj
from util.rigging_parser.skel_parser import Skel
from util.open3d_utils import show_obj_skel

def nms_soft(heatmap, th_conf=0.1, size=15, sigma=6):
    '''
    soft nms algorithm proposed in "Soft-nms - improving object detection with one line of code"
    :param heatmap: predicted joint heatmaps
    :param th_conf: lowest threshold to stop
    :param size: gaussian kernel size
    :param sigma: gaussian kernel sigma
    :return: heatmap after soft-nms
    '''
    heatmap[heatmap < th_conf] = 0

    # we find all local maximums by looking into the gradient of each voxel.
    # Local maximums should have higher value to all its 26 neighbors.
    # so we apply 26 filters (each of them is 3*3*3) to compute the discrete gradient w.r.t. all directions/neighbors.
    kernels = np.zeros((26, 3, 3, 3))
    kernels[:, 1, 1, 1] = 1.0
    gradient_map = np.zeros((26, heatmap.shape[0], heatmap.shape[1], heatmap.shape[2]), np.float32)
    # create 26 kernels to compute gradient
    for c in range(26):
        if c < 13:
            i = c // 9
            j = (c - i * 9) // 3
            k = (c - i * 9) % 3
            kernels[c, i, j, k] = -1.0
        else:
            i = (c + 1) // 9
            j = (c + 1 - i * 9) // 3
            k = (c + 1 - i * 9) % 3
            kernels[c, i, j, k] = -1.0
        gradient_map[c, ...] = correlate(heatmap, kernels[c, ...], mode='constant', cval=0.0)
    gradient_map[gradient_map >= 0] = 0
    gradient_map[gradient_map < 0] = -1
    # Sum up all 26 filters. Local maximums will have 26 zeros. Otherwise summation will be negetive.
    gradient_map = np.sum(gradient_map, axis=0)

    maximum_map = np.logical_and((gradient_map == 0), (heatmap > 0))  # local maximum map
    heatmap = np.multiply(heatmap, maximum_map.astype(np.float32))  # pick all local maximum by masking

    # create gaussian kernel for soft-nms
    radius = size//2
    x = np.arange(0, size, 1, float)
    y = x[:, np.newaxis]
    z = y[..., np.newaxis]
    x0 = y0 = z0 = size // 2
    # The gaussian is not normalized, we want the center value to equal 1
    # This is actually 1 - gaussian
    g = 1 - np.exp(- ((x - x0) ** 2 + (y - y0) ** 2 + (z - z0) ** 2) / (2 * sigma ** 2))
    result = np.zeros((heatmap.shape), dtype=np.float32)
    # for symmetric reason, we only do nms to the left half.
    # Note we add 1 more column, because we don't want to ruin the middle part of heat map
    heatmap_half = heatmap[0:int(heatmap.shape[0] / 2) + 1, ...]

    while np.any(heatmap_half >= th_conf):
        idx_list = np.argwhere(heatmap_half == np.amax(heatmap_half))
        if len(idx_list) > 1:
            # first merge adjacent voxels
            idx_list = idx_list.tolist()
            # For adjacent voxels, we only preserve the one closest to the middle plane.
            # So here we sort according to their distance to the middle plane.
            idx_list.sort(key=lambda x: abs(x[0] - 43.5))
            idx_list_merge = []  # list to store joints after adjacent merging.
            for idx in idx_list:
                preserve_flag = True
                # check if any adjacent voxel has already preserved.
                for prefix in idx_list_merge:
                    if abs(prefix[0] - idx[0]) <= 1 and abs(prefix[1] - idx[1]) <= 1 and abs(prefix[2] - idx[2]) <= 1:
                        preserve_flag = False
                        break
                if preserve_flag: # if no neighbor has been preserved, we add this voxel to the preserved list.
                    idx_list_merge.append(idx)
            idx_list = np.array(idx_list_merge)
        # decay map is used to store how much we should reduce the heatmap probability
        decay_map = np.ones((heatmap_half.shape), dtype=np.float32)
        for idx in idx_list:
            # set the local maximum position to the original probability in the result heatmap
            result[idx[0], idx[1], idx[2]] = heatmap_half[idx[0], idx[1], idx[2]]
            heatmap_half[idx[0], idx[1], idx[2]] = 0
            ul = [int(idx[0] - radius), int(idx[1] - radius), int(idx[2] - radius)]
            br = [int(idx[0] + radius + 1), int(idx[1] + radius + 1), int(idx[2] + radius + 1)]
            g_x = max(0, -ul[0]), min(br[0], heatmap_half.shape[0]) - ul[0]
            g_y = max(0, -ul[1]), min(br[1], heatmap_half.shape[1]) - ul[1]
            g_z = max(0, -ul[2]), min(br[2], heatmap_half.shape[2]) - ul[2]
            # Image range
            img_x = max(0, ul[0]), min(br[0], heatmap_half.shape[0])
            img_y = max(0, ul[1]), min(br[1], heatmap_half.shape[1])
            img_z = max(0, ul[2]), min(br[2], heatmap_half.shape[2])
            decay_map[img_x[0]:img_x[1], img_y[0]:img_y[1], img_z[0]:img_z[1]] = \
                np.multiply(decay_map[img_x[0]:img_x[1], img_y[0]:img_y[1], img_z[0]:img_z[1]],
                            g[g_x[0]:g_x[1], g_y[0]:g_y[1], g_z[0]:g_z[1]])
        heatmap_half = np.multiply(heatmap_half, decay_map)
        heatmap_half[heatmap_half < th_conf] = 0

    return result

def load_ts(ts_filename):
    trans = np.zeros(3)
    center_trans = np.zeros(3, dtype=np.int8)
    with open(ts_filename, 'r') as fts:
        line1 = fts.readline().strip().split()
        center_trans[0], center_trans[1], center_trans[2] = int(line1[0]), int(line1[1]), int(line1[2])
        line2 = fts.readline().strip().split()
        trans[0], trans[1], trans[2] = float(line2[0]), float(line2[1]), float(line2[2])
        line3 = fts.readline().strip()
        scl = float(line3)
    return trans, scl, center_trans

def loadSkel_recur(p_node, parent_id, joint_pos, parent):
    for i in range(len(parent)):
        if parent[i] == parent_id:
            ch_node = TreeNode('joint_{}'.format(i), tuple(joint_pos[i]))
            p_node.children.append(ch_node)
            ch_node.parent = p_node
            loadSkel_recur(ch_node, i, joint_pos, parent)

def getInitId(joint_pos):
    '''
    Root joint is chosen as the lowest joint near the middle symmetric plane
    :param joint_pos: all joint positions
    :return: root joint ID
    '''
    sorted_id = np.argsort(joint_pos[:,1])
    for i in range(len(sorted_id)):
        id = sorted_id[i]
        if joint_pos[id, 0] < 0.2:
            continue
        if abs(joint_pos[id, 0]) < 2e-2:
            return id
    return np.argsort(abs(joint_pos[:,0]))[0]

def unique_rows(a):
    a = np.ascontiguousarray(a)
    unique_a = np.unique(a.view([('', a.dtype)]*a.shape[1]))
    return unique_a.view(a.dtype).reshape((unique_a.shape[0], a.shape[1]))

def minKey(key, mstSet, nV):
    # Initilaize min value
    min = sys.maxsize

    for v in range(nV):
        if key[v] < min and mstSet[v] == False:
            min = key[v]
            min_index = v

    return min_index

def flip(pred_joint, pred_bone, trans, center_trans, scl, input_dim=88, r=3):
    '''
    Enforcing predicted heatmap symmetric by reflecting left half to the right
    The symmetric voxel positions are computed by converting to euclidean space coordinates, finding symmetric positions and converting back.
    This is because voxel space has low resolution and accuracy. Euclidean space is more accurate.
    :param pred_joint: predicted joint heatmap
    :param pred_bone: predicted bone heatmap
    :param trans: translation vector from voxel space to euclidean space
    :param center_trans: translation vector from centered volume to original volume
    :param scl: scale from voxel space to euclidean space
    :param input_dim: voxel grid dimension
    :param r: voxel grid padding
    :return: symmetric heatmaps
    '''
    ori_dim = input_dim - 2 * r  # original voxel grid dimension without padding

    grid_x, grid_y, grid_z = np.meshgrid(np.arange(input_dim), np.arange(input_dim), np.arange(input_dim))
    grid_coord = np.concatenate((grid_y.flatten()[:, None], grid_x.flatten()[:, None], grid_z.flatten()[:, None]), axis=1)
    grid_coord = grid_coord - r + center_trans
    # grid_coord (88^3 * 3) stores correpsonding euclidean coordinates for all voxels in voxel grid
    grid_coord = grid_coord / np.array([ori_dim, ori_dim, ori_dim]) * scl + trans

    # for joint heatmap
    if pred_joint is not None:
        # reflect the left-half heatmap values to the right-half
        # flatten predicted joint map to (88^3, ), with the same order as grid_coord
        val_pred_joint = pred_joint.flatten()
        val_left = np.copy(val_pred_joint[np.logical_and(grid_coord[:, 0] < -2e-2, val_pred_joint > 0)])
        grid_coord_left = np.copy(grid_coord[np.logical_and(grid_coord[:, 0] < -2e-2, val_pred_joint > 0), :])
        grid_coord_right_ = grid_coord_left.copy()
        grid_coord_right_[:, 0] = -grid_coord_right_[:, 0]

        # transform euclidean coodinates back into voxel space.
        vc = np.round((grid_coord_right_ - trans) / scl * ori_dim - center_trans + r)  #
        vc = vc.astype(int)
        vc = np.clip(vc, 0, input_dim-1)
        pred_joint[vc[:,0], vc[:,1], vc[:,2]] = (pred_joint[vc[:,0], vc[:,1], vc[:,2]] + val_left) # add left to right

        # do the same for the right half, reflecting them into the left half.
        val_pred_joint = pred_joint.flatten()
        val_right = np.copy(val_pred_joint[np.logical_and(grid_coord[:, 0] > 2e-2, val_pred_joint > 0)])
        grid_coord_right = np.copy(grid_coord[np.logical_and(grid_coord[:, 0] > 2e-2, val_pred_joint > 0), :])
        grid_coord_left_ = grid_coord_right.copy()
        grid_coord_left_[:, 0] = -grid_coord_left_[:, 0]

        vc = np.round((grid_coord_left_ - trans) / scl * ori_dim - center_trans + r)
        vc = vc.astype(int)
        vc = np.clip(vc, 0, input_dim - 1)
        pred_joint[vc[:, 0], vc[:, 1], vc[:, 2]] = val_right  # don't add values here. Just copy. Otherwise it will unsymmetric again!

    # for bone heatmap
    if pred_bone is not None:
        val_pred_bone = pred_bone.flatten()
        # reflect left-half voxels to right half
        val_left = np.copy(val_pred_bone[np.logical_and(grid_coord[:, 0] < -2e-2, val_pred_bone > 0)])
        grid_coord_left = np.copy(grid_coord[np.logical_and(grid_coord[:, 0] < -2e-2, val_pred_bone > 0), :])
        grid_coord_right_ = grid_coord_left.copy()
        grid_coord_right_[:, 0] = -grid_coord_right_[:, 0]

        vc = np.round((grid_coord_right_ - trans) / scl * ori_dim - center_trans + r)
        vc = vc.astype(int)
        vc = np.clip(vc, 0, input_dim - 1)
        pred_bone[vc[:, 0], vc[:, 1], vc[:, 2]] = (pred_bone[vc[:, 0], vc[:, 1], vc[:, 2]] + val_left) / 2

        val_pred_bone = pred_bone.flatten()
        # reflect right-half voxels to left half
        val_right = np.copy(val_pred_bone[np.logical_and(grid_coord[:, 0] > 2e-2, val_pred_bone > 0)])
        grid_coord_right = np.copy(grid_coord[np.logical_and(grid_coord[:, 0] > 2e-2, val_pred_bone > 0), :])
        grid_coord_left_ = grid_coord_right.copy()
        grid_coord_left_[:, 0] = -grid_coord_left_[:, 0]

        vc = np.round((grid_coord_left_ - trans) / scl * ori_dim - center_trans + r)
        vc = vc.astype(int)
        vc = np.clip(vc, 0, input_dim - 1)
        pred_bone[vc[:, 0], vc[:, 1], vc[:, 2]] = val_right

    return pred_joint, pred_bone

def primMST(graph, init_id):
    nV = graph.shape[0]
    # Key values used to pick minimum weight edge in cut
    key = [sys.maxsize] * nV
    parent = [None] * nV  # Array to store constructed MST
    mstSet = [False] * nV
    # Make key init_id so that this vertex is picked as first vertex
    key[init_id] = 0
    parent[init_id] = -1  # First node is always the root of

    for cout in range(nV):
        # Pick the minimum distance vertex from
        # the set of vertices not yet processed.
        # u is always equal to src in first iteration
        u = minKey(key, mstSet, nV)

        # Put the minimum distance vertex in
        # the shortest path tree
        mstSet[u] = True

        # Update dist value of the adjacent vertices
        # of the picked vertex only if the current
        # distance is greater than new distance and
        # the vertex in not in the shotest path tree
        for v in range(nV):
            # graph[u][v] is non zero only for adjacent vertices of m
            # mstSet[v] is false for vertices not yet included in MST
            # Update the key only if graph[u][v] is smaller than key[v]
            if graph[u,v] > 0 and mstSet[v] == False and key[v] > graph[u,v]:
                key[v] = graph[u,v]
                parent[v] = u

    return parent, key

def primMST_symmetry(graph, init_id, joints):
    '''
    My revised prim algorithm to find a MST as symmetric as possible.
    The function is sort of messy but...
    The basic idea is if a bone on the left is picked, its counterpart on the right should also be picked
    :param graph: cost matrix (N*N)
    :param init_id: joint ID to be first picked
    :param joints: joint position (N*3)
    :return:
    '''
    joint_mapping = {}
    # this is trick. Since we already reflect the joints, "joints" have the order as left_joints->middle_joints->right_joints
    # so we can find correspondence by simply following the original order after splitting three parts.
    left_joint_ids = np.argwhere(joints[:, 0] < -2e-2).squeeze(1).tolist()
    middle_joint_ids = np.argwhere(np.abs(joints[:, 0]) <= 2e-2).squeeze(1).tolist()
    right_joint_ids = np.argwhere(joints[:, 0] > 2e-2).squeeze(1).tolist()
    for i in range(len(left_joint_ids)):
        joint_mapping[left_joint_ids[i]] = right_joint_ids[i]
    for i in range(len(right_joint_ids)):
        joint_mapping[right_joint_ids[i]] = left_joint_ids[i]

    if init_id not in middle_joint_ids:
        #find nearest joint in the middle to be root
        if len(middle_joint_ids) > 0:
            nearest_id = np.argmin(np.linalg.norm(joints[middle_joint_ids, :] - joints[init_id, :][np.newaxis, :], axis=1))
            init_id = middle_joint_ids[nearest_id]

    nV = graph.shape[0]
    # Key values used to pick minimum weight edge in cut
    key = [sys.maxsize] * nV
    parent = [None] * nV  # Array to store constructed MST
    mstSet = [False] * nV
    # Make key init_id so that this vertex is picked as first vertex
    key[init_id] = 0
    parent[init_id] = -1  # First node is always the root of

    while not all(mstSet):
        # Pick the minimum distance vertex from
        # the set of vertices not yet processed.
        # u is always equal to src in first iteration
        u = minKey(key, mstSet, nV)
        # left cases
        if u in left_joint_ids and parent[u] in middle_joint_ids:
            u2 = joint_mapping[u]
            if mstSet[u2] is False:
                mstSet[u2] = True
                parent[u2] = parent[u]
                key[u2] = graph[u2, parent[u2]]
        elif u in left_joint_ids and parent[u] in left_joint_ids:
            u2 = joint_mapping[u]
            if mstSet[u2] is False:
                mstSet[u2] = True
                parent[u2] = joint_mapping[parent[u]]
                key[u2] = graph[u2, parent[u2]]
                if mstSet[parent[u2]] is False:
                    mstSet[parent[u2]] = True
                    key[parent[u2]] = graph[parent[u2], parent[parent[u2]]]

        elif u in middle_joint_ids and parent[u] in left_joint_ids:
            # form loop in the tree, but we can do nothing
            u2 = None
        # right cases
        elif u in right_joint_ids and parent[u] in middle_joint_ids:
            u2 = joint_mapping[u]
            if mstSet[u2] is False:
                mstSet[u2] = True
                parent[u2] = parent[u]
                key[u2] = graph[u2, parent[u2]]
        elif u in right_joint_ids and parent[u] in right_joint_ids:
            u2 = joint_mapping[u]
            if mstSet[u2] is False:
                mstSet[u2] = True
                parent[u2] = joint_mapping[parent[u]]
                key[u2] = graph[u2, parent[u2]]
                if mstSet[parent[u2]] is False:
                    mstSet[parent[u2]] = True
                    key[parent[u2]] = graph[parent[u2], parent[parent[u2]]]
        elif u in middle_joint_ids and parent[u] in right_joint_ids:
            # form loop in the tree, but we can do nothing
            u2 = None
        # middle case
        else:
            u2 = None

        mstSet[u] = True

        # Update dist value of the adjacent vertices
        # of the picked vertex only if the current
        # distance is greater than new distance and
        # the vertex in not in the shotest path tree
        for v in range(nV):
            # graph[u][v] is non zero only for adjacent vertices of m
            # mstSet[v] is false for vertices not yet included in MST
            # Update the key only if graph[u][v] is smaller than key[v]
            if graph[u,v] > 0 and mstSet[v] == False and key[v] > graph[u,v]:
                key[v] = graph[u,v]
                parent[v] = u
            if u2 is not None and graph[u2,v] > 0 and mstSet[v] == False and key[v] > graph[u2,v]:
                key[v] = graph[u2, v]
                parent[v] = u2

    return parent, key

def getMSTcost(joint_pos, joint_pos_cartesian, joint_pred, bone_pred, volume):
    n_joint = len(joint_pos)
    cost_matrix = np.zeros((n_joint, n_joint), dtype=np.float32)
    # fill upper triangular matrix
    for r in range(n_joint):
        for c in range(r + 1, n_joint):
            pos_start = joint_pos[r]
            pos_end = joint_pos[c]
            num_step = np.round((np.linalg.norm(pos_end - pos_start)) / 1.0).astype(int)
            pos_sample = pos_start[np.newaxis, :] + (pos_end - pos_start)[np.newaxis, :] * np.linspace(0, 1, num_step)[:, np.newaxis]
            pos_sample = np.round(pos_sample).astype(int)
            pos_sample = [tuple(row) for row in pos_sample]
            pos_sample = unique_rows(pos_sample)

            cost_matrix[r, c] = -np.log(bone_pred[pos_sample[:, 0], pos_sample[:, 1], pos_sample[:, 2]] + 1e-8).sum()
            cost_matrix[r, c] += 500 * np.sum(volume[pos_sample[:, 0], pos_sample[:, 1], pos_sample[:, 2]] == 0)

    cost_matrix = cost_matrix + cost_matrix.transpose()
    init_id = getInitId(joint_pos_cartesian)
    parent, key = primMST_symmetry(cost_matrix, init_id, joint_pos_cartesian)
    return joint_pos_cartesian, cost_matrix, parent, key

def mst_generate(res_folder, best_thred, sigma, size, visualize=True, out_folder='results/mst_volNet/',
                 mesh_folder='model_resource_data/obj_fixed/'):
    '''
    Generate skeleton as a MST problem.
    :param res_folder: folde that contains predicted joints, bones, voxelized input and
                       transformation information between vox-space coordinates and original (cartesian) coordinates.
    :param best_thred: minimal threshold to NMS
    :param sigma: sigma for soft NMS
    :param size: gaussian kernel size for soft NMS
    :param visualize: visualize result or not with Open3D library
    :param out_folder: folder to output final results
    '''
    joint_pred_list = glob.glob(res_folder + 'joint_pred_*.npy')
    for i in range(len(joint_pred_list)):
        joint_pred_file = joint_pred_list[i]
        model_id = joint_pred_file.split('_')[-1][:-4]
        print(model_id)
        joint_pred = np.load(joint_pred_file)
        bone_pred = np.load(joint_pred_file.replace('joint_', 'bone_'))
        input = np.load(joint_pred_file.replace('joint_pred_', 'input_')).astype(np.float32)
        erode_input = binary_erosion(input, structure=None, iterations=1).astype(np.float32)
        mesh = Mesh_obj(os.path.join(mesh_folder, '{}.obj'.format(model_id)))

        joint_pred = np.clip(joint_pred, 0.0, 1.0)
        bone_pred = np.clip(bone_pred, 0.0, 1.0)
        joint_pred = joint_pred * erode_input
        bone_pred = bone_pred * erode_input
        ts_filename = joint_pred_file.replace('joint_pred_', 'ts_').replace('.npy', '.txt')
        trans, scl, center_trans = load_ts(ts_filename)

        joint_pred, bone_pred = flip(joint_pred, bone_pred, trans, center_trans, scl)
        joint_pred = nms_soft(joint_pred, best_thred, size=size, sigma=sigma)
        joint_pred, _ = flip(joint_pred, None, trans, center_trans, scl)

        joint_pos = np.argwhere(joint_pred > 1e-10)
        joint_pos_cartesian = joint_pos - 3 + center_trans
        joint_pos_cartesian = joint_pos_cartesian / 82 * scl + trans
        #contain_flag = filter_joints_between_legs(joint_pos_cartesian, mesh.v)
        #joint_pos_cartesian = joint_pos_cartesian[contain_flag]
        #joint_pos = joint_pos[contain_flag]

        # make extracted joints symmetric
        joint_pos_cartesian_left = joint_pos_cartesian[joint_pos_cartesian[:, 0] < -2e-2]
        joint_pos_cartesian_middle = joint_pos_cartesian[np.abs(joint_pos_cartesian[:, 0]) < 2e-2]
        joint_pos_cartesian_right = joint_pos_cartesian_left * np.array([[-1, 1, 1]])
        joint_pos_cartesian = np.concatenate((joint_pos_cartesian_left, joint_pos_cartesian_middle, joint_pos_cartesian_right), axis=0)
        joint_pos = np.round((joint_pos_cartesian - trans) / scl * 82 - center_trans + 3).astype(int)

        if len(joint_pos) in [0, 1, 2]:
            print('too few joints extracted. Try to reduce the threshold.')
            continue
        if len(joint_pos) > 100:
            print('too many joints extracted. Try to increase the threshold.')
            continue
        joint_pos_cartesian, cost_matrix, parent, key = getMSTcost(joint_pos, joint_pos_cartesian, joint_pred, bone_pred, input)

        skel = Skel()
        for i in range(len(parent)):
            if parent[i] == -1:
                skel.root = TreeNode('joint_{}'.format(i), tuple(joint_pos_cartesian[i]))
                break
        loadSkel_recur(skel.root, i, joint_pos_cartesian, parent)

        if joint_pos_cartesian is not None:
            if visualize:
                img = show_obj_skel(mesh, skel.root)
                cv2.imwrite(os.path.join(out_folder, 'mst_{0}.jpg').format(model_id), img[:, 300:-300, ::-1])
        if parent:
            skel.save(os.path.join(out_folder, 'mst_{0}.txt').format(model_id))

if __name__ == '__main__':
    mesh_folder = 'model_resource_data/obj/'
    folder_name = 'volNet/'
    out_folder = 'results/mst_{0}'.format(folder_name)
    if not os.path.isdir(out_folder):
        os.mkdir(out_folder)
    best_thred = 0.02
    print(folder_name, best_thred)
    mst_generate('results/{0}'.format(folder_name),
                 best_thred=best_thred, sigma=15, size=11, visualize=True, out_folder=out_folder,
                 mesh_folder=mesh_folder)


"""
This script contains the main class for the NCR model, that inherits from
MFBPR and uses helper functions from training_data_sampling.py. It includes the methods
for hyperparameter tuning and model evaluation.
"""

from keras.regularizers import l2
from keras.models import Model
from keras.layers import Embedding, Input, Dense, multiply, concatenate, \
    Flatten, Lambda
from keras.optimizers import Adagrad, SGD, RMSprop
from functools import partial

from models.bayesian_personalised_ranking import *

class NCR(BPRMF):
    def __init__(self, n_user, n_item, beta, rating_data=True, features_dict=None):
        """
        Parameters:
          n_user (int): no. of unique users in the dataset
          n_item (int): no. of unique items in the dataset
          beta (float): unobserved sampling proportion for the negative level
          rating_data (bool): True if rating data is used, False otherwise
          features_dict (dict): dictionary mapping items to features
        """
        super().__init__(n_user, n_item, beta, rating_data)
        self.n_user = n_user
        self.n_item = n_item
        self.beta = beta
        self.rating_data = rating_data
        self.features_dict = features_dict

    def get_model(self, learner, learning_rate, nbpr_dim, layers, reg_layers,
                  reg_embed_nbpr, reg_embed_mlp):
        """
        Returns NCR model, a concatenation of NBPR and MLP models.
        Parameters:
          learner (str): SGD learner: ['adagrad', 'rmsprop', 'sgd']
          learning_rate (float): SGD learning rate
          nbpr_dim (int): dimension of embedding for Neural BPR
          reg_embed_nbpr (float): L2 regularisation for Neural BPR embeddings
          reg_embed_mlp (float: L2 regulaisation for MLP embeddings
          layers (list of int): list of input dimensions for layers in DNCR
          reg_layers (float): L2 regularisation for layers in DNCR
        Returns:
          model (model): Keras NCR Model
        """
        user_input = Input(shape=(1,), dtype='int32')
        item_input_i = Input(shape=(1,), dtype='int32')
        item_input_j = Input(shape=(1,), dtype='int32')

        # NBPR Model
        nbpr_embedding_user = \
            Embedding(input_dim=self.n_user, output_dim=nbpr_dim,
                      embeddings_initializer='random_normal',
                      name='nbpr_user_embedding',
                      embeddings_regularizer=l2(reg_embed_nbpr),
                      input_length=1)
        nbpr_embedding_item = \
            Embedding(input_dim=self.n_item,
                      output_dim=nbpr_dim,
                      embeddings_initializer='random_normal',
                      name='nbpr_item_embedding',
                      embeddings_regularizer=l2(reg_embed_nbpr),
                      input_length=1)

        nbpr_user_latent = Flatten()(nbpr_embedding_user(user_input))
        nbpr_item_latent_i = Flatten()(nbpr_embedding_item(item_input_i))
        nbpr_item_latent_j = Flatten()(nbpr_embedding_item(item_input_j))

        prefer_i = multiply([nbpr_user_latent, nbpr_item_latent_i])
        prefer_j = multiply([nbpr_user_latent, nbpr_item_latent_j])
        prefer_j = Lambda(lambda x: -x)(prefer_j)
        nbpr_vector = concatenate([prefer_i, prefer_j])

        # MLP Model
        mlp_embedding_user = \
            Embedding(input_dim=self.n_user, output_dim=layers[0],
                      embeddings_initializer='random_normal',
                      name='mlp_user_embedding',
                      embeddings_regularizer=l2(reg_embed_mlp),
                      input_length=1)
        mlp_embedding_item = \
            Embedding(input_dim=self.n_item, output_dim=layers[0],
                      embeddings_initializer='random_normal',
                      name='mlp_item_embedding',
                      embeddings_regularizer=l2(reg_embed_mlp),
                      input_length=1)

        mlp_user_latent = Flatten()(mlp_embedding_user(user_input))
        mlp_item_latent_i = Flatten()(mlp_embedding_item(item_input_i))
        mlp_item_latent_j = Flatten()(mlp_embedding_item(item_input_j))
        mlp_item_latent_j = Lambda(lambda x: -x)(mlp_item_latent_j)

        mlp_vector = concatenate([mlp_user_latent, mlp_item_latent_i,
                                  mlp_item_latent_j])

        if self.features_dict is not None:
            i_j_features = Input(shape=(60,), dtype='float32')
            mlp_vector = concatenate([mlp_vector, i_j_features])

        for l in range(1, len(layers)):
            layer = Dense(layers[l], kernel_regularizer=l2(reg_layers),
                          activation='relu', kernel_initializer='he_normal',
                          name="layer%d" % l)
            mlp_vector = layer(mlp_vector)

        # Concatenate NBPR and MLP models
        concat_vector = concatenate([nbpr_vector, mlp_vector])
        prob = Dense(1, activation='sigmoid', kernel_initializer='random_normal',
                     name='prob')(concat_vector)

        if self.features_dict is not None:
            model = Model(inputs=[user_input, item_input_i, item_input_j,
                                  i_j_features], outputs=prob)

        else:
            model = Model(inputs=[user_input, item_input_i, item_input_j],
                          outputs=prob)

        if learner.lower() == "adagrad":
            model.compile(optimizer=Adagrad(lr=learning_rate),
                          loss='binary_crossentropy')

        elif learner.lower() == "rmsprop":
            model.compile(optimizer=RMSprop(lr=learning_rate),
                          loss='binary_crossentropy')

        else:
            model.compile(optimizer=SGD(lr=learning_rate),
                          loss='binary_crossentropy')

        return model

    def get_training_input(self, neg_sampling_ratio, trans_cart_ratio=None):
        """
        Obtains training examples for SGD, with 'neg_sampling_ratio' times more
        negative examples than positive examples.
        Parameters:
          neg_sampling_ratio (float): number of negative (u,j,i) samples to sample
                                      for every positive (u,i,j) sample
          trans_cart_ratio (float): ratio of 'transaction' sampling weight to
                                   'add-to-cart' sampling weight
        Returns:
          users (np.array): users in training data for NCR
          items_i (np.array): i_items in training data for NCR
          items_j (np.array): j_items in training data for NCR
          labels (np.array): 1 if (u,i,j) represents a positive sample, 0 if it
                             represents a negative sample
          i_j_features (np.array): concatenation of features for item i and j
        """
        n_examples = self.pos_df.shape[0]
        users, items_i, items_j, labels, i_j_features = [], [], [], [], []

        if not self.rating_data:
            self.level_weights_dict = {'transaction': trans_cart_ratio, 'view': 1}
        else:
            self.level_weights_dict = None

        self.pos_dist = get_pos_dist(self.pos_df, self.level_weights_dict)

        for instance in range(n_examples):
            L = sample_pos(self.pos_dist)
            u, i = get_pos_user_item(L, self.pos_dict)
            N = sample_neg(self.user_reps[u])
            j = sample_neg_item(self.user_reps[u], N, self.n_item)

            users.append(u)
            items_i.append(i)
            items_j.append(j)
            labels.append(1)
            if self.features_dict is not None:
                i_j_features.append(np.append(self.features_dict[i],
                                              self.features_dict[j]))

            for count in range(int(neg_sampling_ratio)):
                N = sample_neg(self.user_reps[u])
                j = sample_neg_item(self.user_reps[u], N, self.n_item)
                users.append(u)
                items_i.append(j)
                items_j.append(i)
                labels.append(0)
                if self.features_dict is not None:
                    i_j_features.append(np.append(self.features_dict[j],
                                                  self.features_dict[i]))

        if self.features_dict is not None:
            return np.array(users), np.array(items_i), np.array(items_j), \
                   np.array(labels), np.array(i_j_features)

        else:
            return np.array(users), np.array(items_i), np.array(items_j), \
                   np.array(labels)

    def fit_ncr(self, learner, learning_rate, nbpr_dim, layers, reg_layers,
                reg_embed_nbpr, reg_embed_mlp, n_epochs,
                neg_sampling_ratio, batch_size, trans_cart_ratio=None):
        """
        Fits the NCR model to the training data.
        Parameters:
          learner (str): SGD learner to use: ['adagrad', 'rmsprop', 'adam', 'sgd']
          learning_rate (float): SGD learning rate
          nbpr_dim (int): dimension of embedding for Neural BPR
          reg_embed_nbpr (float): L2 regularisation for Neural BPR embeddings
          reg_embed_mlp (float: L2 regulaisation for MLP embeddings
          layers (list of int): list of input dimensions for layers in DNCR
          reg_layers (float): L2 regularisation for layers in DNCR
          n_epochs (int): number of training epochs
          neg_sampling_ratio (float): number of negative (u,j,i) samples to sample
                                      for every positive (u,i,j) sample
          batch_size (int): number of training examples per iteration
          trans_cart_ratio (float): ratio of 'transaction' sampling weight to
                                    'add-to-cart' sampling weight
        """

        for epoch in range(n_epochs):
            if self.features_dict is not None:
                users, items_i, items_j, labels, i_j_features = \
                    self.get_training_input(neg_sampling_ratio=neg_sampling_ratio,
                                            trans_cart_ratio=trans_cart_ratio)

                self.model = self.get_model(learner, learning_rate, nbpr_dim, layers,
                                            reg_layers, reg_embed_nbpr, reg_embed_mlp)

                self.model.fit(x=[users, items_i, items_j, i_j_features], y=labels,
                               batch_size=batch_size, epochs=n_epochs, shuffle=True)
            else:
                users, items_i, items_j, labels = \
                    self.get_training_input(neg_sampling_ratio=neg_sampling_ratio,
                                            trans_cart_ratio=trans_cart_ratio)

                self.model = self.get_model(learner, learning_rate, nbpr_dim, layers,
                                            reg_layers, reg_embed_nbpr, reg_embed_mlp)

                self.model.fit(x=[users, items_i, items_j], y=labels,
                               batch_size=batch_size, epochs=n_epochs, shuffle=True)

    def evaluate_ncr(self, test_ratings, k=10, n_random=100):
        """
        Computes mean average recall, mean reciprocal rank and AUC score.
        Parameters:
          test_ratings (pd.DataFrame): test dataset in the format (user, item, rating)
          k (int): no. of most relevant items
          n_random (int): no. of unobserved items to sample to use for evaluation
        Returns:
          average_AUC (float): mean average AUC
          recall (float): mean average recall @ k
          mrr (float): mean reciprocal rank @ k
        """
        test_ratings_array = test_ratings[['userID', 'itemID']].values
        hits, rr_agg, auc_agg = 0, 0, 0
        m = test_ratings_array.shape[0]
        auc_list = np.zeros(m)

        for instance in range(m):
            u = test_ratings_array[instance, 0]
            i = test_ratings_array[instance, 1]
            user_items = self.user_reps[u]['all_items']

            uo_items = np.setdiff1d(np.arange(self.n_item), user_items)

            random_uo_items = np.random.choice(uo_items, replace=False,
                                               size=n_random)

            if self.features_dict is not None:
                position = get_position(u=u, i=i, uo_items=random_uo_items,
                                        model=self.model,
                                        features_dict=self.features_dict)
            else:
                position = get_position(u=u, i=i, uo_items=random_uo_items,
                                        model=self.model)

            if position <= k:
                hits += 1
                rr_agg += 1 / position

            auc_list[instance] = ((len(random_uo_items) - position)
                                  / len(random_uo_items))
            auc_agg += auc_list[instance]

        recall = hits / m
        mrr = rr_agg / m
        average_auc = auc_agg / m

        return average_auc, recall, mrr

    def objective_ncr(self, grid, n_epochs, neg_sampling_ratio):
        """
        Returns (1 - mean average AUC) for NCR model trained on grid parameters,
        on the validation set, as an objective function to minimise for Bayesian
        Optimisation.
        Parameters:
          grid (dict): dictionary with values of 'learner', 'learning rate',
                      'nbpr_dim', 'layers', 'reg_layers', 'reg_embed_nbpr', 'reg_embed_mlp',
                      'batch_size', 'trans_cart_ratio'
          n_epochs (int): number of training epochs
          neg_sampling_ratio (float): number of negative (u,j,i) samples to
                                      sample for every positive (u,i,j) sample
          k (int): no. of most relevant items
        Returns:
          1 - average_AUC (float): 1 - mean average AUC
        """
        if not self.rating_data:
            trans_cart_ratio = grid['trans_cart_ratio']
        else:
            trans_cart_ratio = None

        self.fit_ncr(grid['learner'], grid['learning_rate'], grid['nbpr_dim'],
                     grid['layers'], grid['reg_layers'], grid['reg_embed_nbpr'],
                     grid['reg_embed_mlp'], n_epochs, neg_sampling_ratio,
                     grid['batch_size'], trans_cart_ratio)

        auc, _, _ = self.evaluate_ncr(self.test_ratings, self.user_reps)

        return 1 - auc

    def optimise_params_ncr(self, grid, n_epochs, neg_sampling_ratio,
                            bo_iterations=100):
        """
        Fits Bayesian Optimisation with Tree Parzen estimators to optimise
        parameters taking values in 'grid', minimising (1 - mean average AUC) on
        the validation set, for NCR model trained on grid parameters.
        Parameters:
          grid (dict): dictionary with values of 'learner', 'learning rate',
                      'nbpr_dim', 'layers', 'reg_layers', 'reg_embed_nbpr',
                      'reg_embed_mlp', 'batch_size', 'trans_cart_ratio'
          n_epochs (int): number of training epochs
          neg_sampling_ratio (float): number of negative (u,j,i) samples to sample
                                      for every positive (u,i,j) sample
          bo_iterations (int): number of iterations of Bayesian Optimisation
        Returns:
          results (dict): optimal set of hyperparameters
        """
        fmin_objective = partial(self.objective_ncr, n_epochs=n_epochs,
                                 neg_sampling_ratio=neg_sampling_ratio, k=k)

        trials = Trials()
        results = fmin(fmin_objective, grid, algo=tpe.suggest,
                       trials=trials, max_evals=bo_iterations)

        return results

def get_position(u, i, uo_items, model, features_dict=None):
    """
    Returns position of item i, in the list of unobserved items together with i.
    Parameters:
      u (int): userID
      i (int): itemID
      uo_items (list): list of (n_random) unobserved items to user u
      model (model): Trained keras NCR model
      features_dict (dict): dictionary mapping user-item tuples to features
    Returns:
      poition (int): position of i in [uo_items, i]
    """
    u_vector = np.full(len(uo_items), u, dtype='int32')
    i_vector = np.full(len(uo_items), i, dtype='int32')

    if features_dict is not None:
        i_items_features = [np.concatenate([features_dict[i], features_dict[item]])
                            for item in uo_items]
        items_i_features = [np.concatenate([features_dict[item], features_dict[i]])
                            for item in uo_items]
        score_i_items = model.predict([u_vector, i_vector, np.array(uo_items),
                                       np.array(i_items_features)])
        score_items_i = model.predict([u_vector, np.array(uo_items), i_vector,
                                       np.array(items_i_features)])
    else:
        score_i_items = model.predict([u_vector, i_vector, np.array(uo_items)])
        score_items_i = model.predict([u_vector, np.array(uo_items), i_vector])

    # Number of items that i is preferred over
    i_prefover_items = score_i_items - score_items_i

    position = sum(i_prefover_items < 0) + 1

    return int(position)


# 回测引擎：每日数据处理函数，每天执行一次
def bigquant_run(context, data):
    #获取当日日期
    today_date = data.current_dt.strftime('%Y-%m-%d')

    #大盘风控模块，读取风控数据
    benckmark_risk=context.benckmark_risk.ix[today_date].values[0]

    #当risk为1时，市场有风险，全部平仓，不再执行其它操作
    if benckmark_risk > 0:
        position_all = context.portfolio.positions.keys()
        for i in position_all:
            context.order_target(i, 0)
        print(today_date,'大盘风控止损触发,全仓卖出')
        return

    # 按日期过滤得到今日的预测数据
    ranker_prediction = context.ranker_prediction[
        context.ranker_prediction.date == data.current_dt.strftime('%Y-%m-%d')]

    # 1. 资金分配
    # 平均持仓时间是hold_days，每日都将买入股票，每日预期使用 1/hold_days 的资金
    # 实际操作中，会存在一定的买入误差，所以在前hold_days天，等量使用资金；之后，尽量使用剩余资金（这里设置最多用等量的1.5倍）
    is_staging = context.trading_day_index < context.options['hold_days'] # 是否在建仓期间（前 hold_days 天）
    cash_avg = context.portfolio.portfolio_value / context.options['hold_days']
    cash_for_buy = min(context.portfolio.cash, (1 if is_staging else 1.5) * cash_avg)
    cash_for_sell = cash_avg - (context.portfolio.cash - cash_for_buy)
    positions = {e.symbol: p.amount * p.last_sale_price
                 for e, p in context.perf_tracker.position_tracker.positions.items()}

    #---------------------------START:止赢止损模块(含建仓期)--------------------
    # 新建当日止赢止损股票列表是为了handle_data 策略逻辑部分不再对该股票进行判断
    current_stopwin_stock=[]
    current_stoploss_stock = []
    today_date = data.current_dt.strftime('%Y-%m-%d')
    positions_stop={e.symbol:p.cost_basis
    for e,p in context.portfolio.positions.items()}
    if len(positions_stop)>0:
        for i in positions_stop.keys():
            stock_cost=positions_stop[i]
            stock_market_price=data.current(context.symbol(i),'price')
            # 赚15% 且为可交易状态就止盈
            if stock_market_price/stock_cost-1 >= 0.13  and data.can_trade(context.symbol(i)) and not context.has_unfinished_sell_order(i):
                context.order_target_percent(context.symbol(i),0)
                current_stopwin_stock.append(i)
            # 亏10%并且为可交易状态就止损
            if stock_market_price/stock_cost-1 <= -0.1 and data.can_trade(context.symbol(i)) and not context.has_unfinished_sell_order(i):
                context.order_target_percent(context.symbol(i),0)
                current_stoploss_stock.append(i)
        if len(current_stopwin_stock)>0:
            print(today_date,'止盈股票列表',current_stopwin_stock)
        if len(current_stoploss_stock)>0:
            print(today_date,'止损股票列表',current_stoploss_stock)
    #--------------------------END: 止赢止损模块-----------------------------

    #--------------------------START：持有固定天数卖出(不含建仓期)---------------
    current_stopdays_stock = []
    today = data.current_dt
    today_date = data.current_dt.strftime('%Y-%m-%d')
    # 不是建仓期（在前hold_days属于建仓期）
    if not is_staging:
        equities = {e.symbol: p for e, p in context.portfolio.positions.items() if p.amount>0}
        if len(equities)>0:
            for i in equities:
                sid = equities[i].sid  # 交易标的
                #如果上面的止盈止损已经卖出过了，就不要重复卖出以防止产生空单
                if i in current_stopwin_stock+current_stoploss_stock:
                    continue
                # 今天和上次交易的时间相隔hold_days就全部卖出 datetime.timedelta(context.options['hold_days'])也可以换成自己需要的天数，比如datetime.timedelta(5)
                if today-equities[i].last_sale_date>=datetime.timedelta(5) and data.can_trade(context.symbol(i)) and not context.has_unfinished_sell_order(equities[i]):
                    context.order_target_percent(sid, 0)
                    current_stopdays_stock.append(i)
            if len(current_stopdays_stock)>0:
                print(today_date,'固定天数卖出列表',current_stopdays_stock)
    #-------------------------------END:持有固定天数卖出--------------------------

    # 2. 生成卖出订单：hold_days天之后才开始卖出；对持仓的股票，按机器学习算法预测的排序末位淘汰
    if not is_staging and cash_for_sell > 0:
        equities = {e.symbol: e for e, p in context.perf_tracker.position_tracker.positions.items()}
        instruments = list(reversed(list(ranker_prediction.instrument[ranker_prediction.instrument.apply(
                lambda x: x in equities and not context.has_unfinished_sell_order(equities[x]))])))
        for instrument in instruments:
            #防止多个止损条件同时满足，出现多次卖出产生空单
            if instrument not in current_stopdays_stock+current_stopwin_stock+current_stoploss_stock:
                context.order_target(context.symbol(instrument), 0)
                cash_for_sell -= positions[instrument]
            else:
                cash_for_sell -= positions[instrument]
            if cash_for_sell <= 0:
                break

    # 3. 生成买入订单：按机器学习算法预测的排序，买入前面的stock_count只股票
    buy_cash_weights = context.stock_weights
    buy_instruments_tmp = list(ranker_prediction.instrument)
    #防止卖出后再次买入
    buy_instruments=[k for k in buy_instruments_tmp if k not in current_stopdays_stock+current_stopwin_stock+current_stoploss_stock]
    max_cash_per_instrument = context.portfolio.portfolio_value * context.max_cash_per_instrument

    but_stock_count = 0
    for i, instrument in enumerate(buy_instruments):
        cash = cash_for_buy * 0.2
        if cash > max_cash_per_instrument - positions.get(instrument, 0):
            # 确保股票持仓量不会超过每次股票最大的占用资金量
            cash = max_cash_per_instrument - positions.get(instrument, 0)
        if cash > 0:
            score = ranker_prediction[ranker_prediction['instrument']==instrument].iloc[0]['score']
            if score<0.8:
                break

            price = data.current(context.symbol(instrument), 'price')  # 最新价格
            stock_num = np.floor(cash/price/100)*100  # 向下取整
            context.order(context.symbol(instrument), stock_num) # 整百下单
            print(today_date,'购买股票',instrument)
            but_stock_count += 1
        if but_stock_count >= 5:
            break

# 回测引擎：初始化函数，只执行一次
def bigquant_run(context):

    # 加载预测数据
    #context.ranker_prediction = context.options['data'].read_df()
    df = context.options['data'].read_df()
    ins= list(df.instrument.unique())
    start = df.date.min()
    end = df.date.max()
    #获取代码的股票名称
    df_name=D.history_data(instruments=ins, start_date=start, end_date=end,fields=['name'])
    df_merge=pd.merge(df,df_name,on=['instrument','date'])
    #过滤掉含有‘退’字的股票
    df_filter=df_merge[~(df_merge.name.str.contains('退'))]
    df_filter=df_filter[~(df_filter.name.str.contains('ST'))]
    context.ranker_prediction = df_filter

    print(context.ranker_prediction.columns)
    # 系统已经设置了默认的交易手续费和滑点，要修改手续费可使用如下函数
    context.set_commission(PerOrder(buy_cost=0.0003, sell_cost=0.0013, min_cost=5))
    # 预测数据，通过options传入进来，使用 read_df 函数，加载到内存 (DataFrame)
    # 设置买入的股票数量，这里买入预测股票列表排名靠前的5只
    stock_count = 5
    # 每只的股票的权重，如下的权重分配会使得靠前的股票分配多一点的资金，[0.339160, 0.213986, 0.169580, ..]
    # context.stock_weights = T.norm([1 / math.log(i + 2) for i in range(0, stock_count)])
    #改为等权重配置
    context.stock_weights = [1 / stock_count for i in range(0, stock_count)]
    # 设置每只股票占用的最大资金比例
    context.max_cash_per_instrument = 0.2
    context.options['hold_days'] = 5

# 回测引擎：准备数据，只执行一次
def bigquant_run(context):
    #在数据准备函数中一次性计算每日的大盘风控条件相比于在handle中每日计算风控条件可以提高回测速度
    # 多取50天的数据便于计算均值(保证回测的第一天均值不为Nan值)，其中context.start_date和context.end_date是回测指定的起始时间和终止时间
    start_date= (pd.to_datetime(context.start_date) - datetime.timedelta(days=50)).strftime('%Y-%m-%d')
    benckmark_data=D.history_data(instruments=['000001.SZA'], start_date=start_date, end_date=context.end_date,fields=['close'])
    #计算指数5日涨幅
    benckmark_data['ret5']=benckmark_data['close']/benckmark_data['close'].shift(5)-1
    #计算大盘风控条件，如果5日涨幅小于-5%则设置风险状态risk为1，否则为0
    benckmark_data['risk'] = np.where(benckmark_data['ret5']<-0.04,1,0)
    #修改日期格式为字符串(便于在handle中使用字符串日期索引来查看每日的风险状态)
    benckmark_data['date']=benckmark_data['date'].apply(lambda x:x.strftime('%Y-%m-%d'))
    #设置日期为索引
    benckmark_data.set_index('date',inplace=True)
    #把风控序列输出给全局变量context.benckmark_risk
    context.benckmark_risk=benckmark_data[['risk']]


# coding=utf-8
from __future__ import division, absolute_import, print_function
from base64 import b64encode
from fractions import gcd
from random import randrange
from collections import namedtuple
from math import log
from binascii import hexlify, unhexlify
import sys

PY3 = sys.version_info[0] == 3
if PY3:
    binary_type = bytes
    range_func = range
else:
    binary_type = str
    range_func = xrange

def is_prime(n, k=30):
    # http://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test
    if n <= 3:
        return n == 2 or n == 3
    neg_one = n - 1

    # write n-1 as 2^s*d where d is odd
    s, d = 0, neg_one
    while not d & 1:
        s, d = s + 1, d >> 1
    assert 2 ** s * d == neg_one and d & 1

    for _ in range_func(k):
        a = randrange(2, neg_one)
        x = pow(a, d, n)
        if x in (1, neg_one):
            continue
        for _ in range_func(s - 1):
            x = x ** 2 % n
            if x == 1:
                return False
            if x == neg_one:
                break
        else:
            return False
    return True

def randprime(n=10 ** 8):
    p = 1
    while not is_prime(p):
        p = randrange(n)
    return p

def multinv(modulus, value):
    """
        Multiplicative inverse in a given modulus

        >>> multinv(191, 138)
        18
        >>> multinv(191, 38)
        186
        >>> multinv(120, 23)
        47
    """
    # http://en.wikipedia.org/wiki/Extended_Euclidean_algorithm
    x, lastx = 0, 1
    a, b = modulus, value
    while b:
        a, q, b = b, a // b, a % b
        x, lastx = lastx - q * x, x
    result = (1 - lastx * modulus) // value
    if result < 0:
        result += modulus
    assert 0 <= result < modulus and value * result % modulus == 1
    return result

KeyPair = namedtuple('KeyPair', 'public private')
Key = namedtuple('Key', 'exponent modulus')

def keygen(n, public=None):
    """ Generate public and private keys from primes up to N.

    Optionally, specify the public key exponent (65537 is popular choice).

        >>> pubkey, privkey = keygen(2**64)
        >>> msg = 123456789012345
        >>> coded = pow(msg, *pubkey)
        >>> plain = pow(coded, *privkey)
        >>> assert msg == plain

    """
    # http://en.wikipedia.org/wiki/RSA
    prime1 = randprime(n)
    prime2 = randprime(n)
    composite = prime1 * prime2
    totient = (prime1 - 1) * (prime2 - 1)
    if public is None:
        private = None
        while True:
            private = randrange(totient)
            if gcd(private, totient) == 1:
                break
        public = multinv(totient, private)
    else:
        private = multinv(totient, public)
    assert public * private % totient == gcd(public, totient) == gcd(private, totient) == 1
    assert pow(pow(1234567, public, composite), private, composite) == 1234567
    return KeyPair(Key(public, composite), Key(private, composite))

def encode(msg, pubkey, verbose=False):
    chunksize = int(log(pubkey.modulus, 256))
    outchunk = chunksize + 1
    outfmt = '%%0%dx' % (outchunk * 2,)
    bmsg = msg if isinstance(msg, binary_type) else msg.encode('utf-8')
    result = []
    for start in range_func(0, len(bmsg), chunksize):
        chunk = bmsg[start:start + chunksize]
        chunk += b'\x00' * (chunksize - len(chunk))
        plain = int(hexlify(chunk), 16)
        coded = pow(plain, *pubkey)
        bcoded = unhexlify((outfmt % coded).encode())
        if verbose:
            print('Encode:', chunksize, chunk, plain, coded, bcoded)
        result.append(bcoded)
    return b''.join(result)

def decode(bcipher, privkey, verbose=False):
    chunksize = int(log(privkey.modulus, 256))
    outchunk = chunksize + 1
    outfmt = '%%0%dx' % (chunksize * 2,)
    result = []
    for start in range_func(0, len(bcipher), outchunk):
        bcoded = bcipher[start: start + outchunk]
        coded = int(hexlify(bcoded), 16)
        plain = pow(coded, *privkey)
        chunk = unhexlify((outfmt % plain).encode())
        if verbose:
            print('Decode:', chunksize, chunk, plain, coded, bcoded)
        result.append(chunk)
    return b''.join(result).rstrip(b'\x00').decode('utf-8')

def key_to_str(key):
    """
    Convert `Key` to string representation
    >>> key_to_str(Key(50476910741469568741791652650587163073, 95419691922573224706255222482923256353))
    '25f97fd801214cdc163796f8a43289c1:47c92a08bc374e96c7af66eb141d7a21'
    """
    return ':'.join((('%%0%dx' % ((int(log(number, 256)) + 1) * 2)) % number) for number in key)

def str_to_key(key_str):
    """
    Convert string representation to `Key` (assuming valid input)
    >>> (str_to_key('25f97fd801214cdc163796f8a43289c1:47c92a08bc374e96c7af66eb141d7a21') ==
    ...  Key(exponent=50476910741469568741791652650587163073, modulus=95419691922573224706255222482923256353))
    True
    """
    return Key(*(int(number, 16) for number in key_str.split(':')))

def main():
    import doctest
    print(doctest.testmod())

    pubkey, privkey = keygen(2 ** 64)
    msg = u'the quick brown fox jumped over the lazy dog ® ⌀'
    h = encode(msg, pubkey, True)
    p = decode(h, privkey, True)
    print('-' * 20)
    print('message:', msg)
    print('encoded:', b64encode(h).decode())
    print('decoded:', p)
    print('private key:', key_to_str(privkey))
    print('public key:', key_to_str(pubkey))

if __name__ == '__main__':
    main()


# -*- coding: utf-8 -*-
"""
Created on Fri Jun 17 14:12:27 2022

@author: Daniel Diogo
"""

import json
from fractions import Fraction

def loadFeatures(filePath):
    songFeatures = []
    f = open(filePath, "r")
    fileLines = f.readlines()

    for line in fileLines:
        songFeatures.append(json.loads(line))

    return songFeatures

def loadReductedSongs(filePath):
    songReductions = {}
    f = open(filePath, "r")
    fileLines = f.readlines()

    for line in fileLines:
        songReductions = json.loads(line)

    return songReductions

def unNumberedToInt(array):
    aux = list(set(array))
    arraySize = len(array)
    for i in range(arraySize):
        array[i] = aux.index(array[i])
    return array

def similar(seq1, seq2, elements, algorithm):

    size1 = len(seq1['pitch'])
    seq1aux = []

    size2 = len(seq2['pitch'])
    seq2aux = []

    for i in range(size1):
        seq1aux.append([])

        for feature in seq1:
            if seq1[feature][i] == None:
                seq1[feature][i] = 0

        if "PITCH" in elements:
            if "MIDI" in elements:
                seq1aux[i].append(seq1['midipitch'][i])
            else:
                seq1aux[i].append(seq1['pitch'][i])

        if "DUR" in elements:
            seq1aux[i].append(seq1['duration'][i])

        if "INT" in elements:
            if seq1['chromaticinterval'][i] == None:
                aux = 0
            else:
                aux = seq1['chromaticinterval'][i]
            seq1aux[i].append(aux)

        if "IOI" in elements:
            if seq1['IOI'][i] == None:
                aux = 0
            else:
                aux = seq1['IOI'][i]
            seq1aux[i].append(aux)

        if "TS" in elements:
            seq1aux[i].append(Fraction(seq1['timesignature'][i]))

        if "DUR_FRAC" in elements:
            seq1aux[i].append(Fraction(seq1['duration_frac'][i]))

        if "DIATONIC" in elements:
            seq1aux[i].append(seq1['diatonicpitch'][i])

        if "ONSET" in elements:
            seq1aux[i].append(seq1['onsettick'][i])

        if "OFFSET" in elements:
            seq1aux[i].append(Fraction(seq1['offsets'][i]))

        if "BSTR" in elements:
            seq1aux[i].append(seq1['beatstrength'][i])

        if "BEAT" in elements:
            seq1aux[i].append(seq1['beat'][i])

        if "TONIC" in elements:
            tRes = 0
            for t in seq1['tonic'][i]:
                tRes += ord(t)

            seq1aux[i].append(tRes)

        if "MODE" in elements:
            mRes = 0
            for m in seq1['mode'][i]:
                mRes += ord(m)

            seq1aux[i].append(mRes)

    for i in range(size2):
        seq2aux.append([])
        """
        for feature in seq2:
            if not isinstance(seq2[feature][i], (int,float)):
                seq2[feature] = unNumberedToInt(seq2[feature])
        """

        if "PITCH" in elements:
            if "MIDI" in elements:
                seq2aux[i].append(seq2['midipitch'][i])
            else:
                seq2aux[i].append(seq2['pitch'][i])

        if "DUR" in elements:
            seq2aux[i].append(float(seq2['duration'][i]))

        if "INT" in elements:
            if seq2['chromaticinterval'][i] == None:
                aux = 0
            else:
                aux = seq2['chromaticinterval'][i]
            seq2aux[i].append(aux)

        if "IOI" in elements:
            if seq2['IOI'][i] == None:
                aux = 0
            else:
                aux = seq2['IOI'][i]
            seq2aux[i].append(aux)

        if "TS" in elements:
            seq2aux[i].append(Fraction(seq2['timesignature'][i]))

        if "DUR_FRAC" in elements:
            seq2aux[i].append(Fraction(seq2['duration_frac'][i]))

        if "DIATONIC" in elements:
            seq2aux[i].append(seq2['diatonicpitch'][i])

        if "ONSET" in elements:
            seq2aux[i].append(seq2['onsettick'][i])

        if "OFFSET" in elements:
            seq2aux[i].append(Fraction(seq2['offsets'][i]))

        if "BSTR" in elements:
            seq2aux[i].append(seq2['beatstrength'][i])

        if "BEAT" in elements:
            seq2aux[i].append(seq2['beat'][i])

        if "TONIC" in elements:
            tRes = 0
            for t in seq2['tonic'][i]:
                tRes += ord(t)

            seq2aux[i].append(tRes)

        if "MODE" in elements:
            mRes = 0
            for m in seq2['mode'][i]:
                mRes += ord(m)

            seq2aux[i].append(mRes)

    return algorithm(seq1aux, seq2aux)

def show_results(results):
    print("Cardinality Score: " + str(results[0]))
    print("Correlation Distance: " + str(results[1]))
    print("City Block Distance: " + str(results[2]))
    print("Euclidean Distance: " + str(results[3]))
    print("Hamming Distance: " + str(results[4]))
    print("Local Alignment Score: " + str(results[5]))
    print("Structure Induction Matching Algorithm Score': " + str(results[6]))
    #print("Implication-Realization Structure Alignment Score: " + str(results[7]))
    print()


# A função input() é usada para ler a entrada do usuário a partir do teclado.
nome = input("Qual é o seu nome? ")
print("Olá, " + nome + "! Bem-vindo ao Python!")

# Ao ler números, é importante converter a entrada de string para o tipo numérico apropriado.
# Em Python, existem três tipos numéricos principais: int, float e complex.
# Ler um número inteiro
num_inteiro = int(input("Digite um número inteiro: "))
print("Você digitou o número inteiro:", num_inteiro)

# Ler um número real (float)
num_real = float(input("Digite um número real: "))
print("Você digitou o número real:", num_real)

# Ler um número complexo
num_complexo = complex(input("Digite um número complexo (no formato a+bj): "))
print("Você digitou o número complexo:", num_complexo)

# Além disso, também é possível ler vários valores em uma única linha de entrada,
# separando-os com um espaço ou outro caractere de separação.
# Ler dois números inteiros separados por espaço
x, y = input("Digite dois números inteiros separados por espaço: ").split()
x = int(x)
y = int(y)
print("Você digitou os números inteiros:", x, "e", y)

# Ler uma lista de números separados por vírgula
numeros = input("Digite uma lista de números separados por vírgula: ").split(",")
numeros = [float(x) for x in numeros]
print("Você digitou a lista de números:", numeros)

# A função split() é usada para separar a entrada do usuário em uma lista de strings,
# que é então convertida para uma lista de números usando a compreensão de lista.


import argparse
import torch
import torch.nn.functional as F
from torch_geometric.nn import LNGATConv, LNGATConv1
from torch_geometric.datasets import WikipediaNetwork, Actor
from datasets import get_planetoid_dataset
from train_eval import random_planetoid_splits, run

parser = argparse.ArgumentParser()
parser.add_argument('--dataset', type=str, required=True)
parser.add_argument('--random_splits', type=bool, default=True)
parser.add_argument('--runs', type=int, default=10)
parser.add_argument('--epochs', type=int, default=200)
parser.add_argument('--lr', type=float, default=0.01)
parser.add_argument('--weight_decay', type=float, default=0.0005)
parser.add_argument('--early_stopping', type=int, default=10)
parser.add_argument('--hidden', type=int, default=32)
parser.add_argument('--dropout', type=float, default=0.5)
parser.add_argument('--normalize_features', type=bool, default=True)
args = parser.parse_args()

class Net(torch.nn.Module):

    def __init__(self, dataset):
        super(Net, self).__init__()
        self.mlp = Seq(dataset.num_features, args.hidden)
        self.conv1 = LNGATConv(args.hidden, dataset.num_classes)
        self.conv2 = LNGATConv1(args.hidden, dataset.num_classes)

    def reset_parameters(self):
        self.mlp.reset_parameters()
        self.conv1.reset_parameters()
        self.conv2.reset_parameters()

    def forward(self, data):

        x, edge_index = data.x, data.edge_index
        x0 = F.relu(self.mlp(x))

        x1 = F.relu(self.conv1(x0, edge_index))
        x1 = F.dropout(x1, p=args.dropout, training=self.training)
        x2 = self.conv2(x0, edge_index)

        if use_global_pooling:
            x3 = torch.max(x0, axis=-1, keepdims=True)
            lngal_out = torch.cat((x1, x2, x3), axis=-1)

        lngal_out = lngal_out + x

        return F.log_softmax(lngal_out, dim=1)

dataset = get_planetoid_dataset(args.dataset, args.normalize_features)
# dataset = Actor(root='./data/Actor')
data = dataset[0]

permute_masks = random_planetoid_splits if args.random_splits else None

run(dataset, Net(dataset), args.runs, args.epochs, args.lr, args.weight_decay,
    args.early_stopping, permute_masks)


# Insertion Sort Algorithm

from Algorithm import Algorithm
from Mediator import Mediator

COMPARE = 0
SWAP = 1
INCREMENT = 2
FINISHED = 3

class InsertionSort(Algorithm):
    def __init__(self, array, mediator:Mediator):
        self.array = list(array)
        self.mediator = mediator
        self.stage = COMPARE
        self.i = 1  # the outer loop pointer
        self.j = 1  # the inner loop pointer
        self.end = len(self.array) - 1
        self.finished = False
        self.mediator.display_result("")
        self.mediator.shadow(2, self.end)
        self.mediator.highlight([1])

    def swap(self):
        temp = self.array[self.j]
        self.array[self.j] = self.array[self.j - 1]
        self.array[self.j - 1] = temp

    def next(self):
        if self.stage == COMPARE:
            message = ""
            if self.array[self.j] < self.array[self.j - 1]:
                message = "A[{}] < A[{}]"
                self.stage = SWAP
            else:
                message = "All values left of A[i] are sorted"
                self.stage = INCREMENT
            message = message.format(str(self.j), str(self.j - 1))
            self.mediator.display_description(message)
        elif self.stage == SWAP:
            self.swap()
            message = "Swap A[{}] and A[{}]".format(str(self.j), str(self.j - 1))
            self.mediator.display_description(message)
            self.mediator.update_values(self.array)
            self.j -= 1
            self.mediator.highlight([self.j])
            if self.j == 0:
                self.stage = INCREMENT
            else:
                self.stage = COMPARE
        elif self.stage == INCREMENT:
            self.mediator.display_description("Increment i")
            if self.i == self.end:
                self.stage = FINISHED
                self.end += 1
            else:
                self.stage = COMPARE
            self.i += 1
            self.j = self.i
            self.mediator.shadow(self.i+1, self.end)
            self.mediator.highlight([self.j])
        elif self.stage == FINISHED:
            self.mediator.display_description("")
            self.mediator.display_result("Sorted!")
            self.finished = True
        else:
            raise ValueError

    def get_state(self):
        return (self.stage, self.i, self.j, self.end, self.finished, tuple(self.array))

    def set_state(self, state):
        self.stage = state[0]
        self.i = state[1]
        self.j = state[2]
        self.end = state[3]
        self.finished = state[4]
        self.array = list(state[5])

    def is_finished(self):
        return self.finished

    def is_started(self):
        if self.stage == COMPARE and self.i == 1 and self.j == 1:
            return False
        else:
            return True


print("==========================私有方法==================")
class Person:
    __age = 18

    #私有方法
    def __run(self):
        print("pao")

    #注意不能这样,名字重整机制,会覆盖
    # def _Person__run(self):
    #     print("xxx")
p = Person()
p._Person__run()

print(Person.__dict__)

print("==========================__new__==================")
class Dog2():
    def __init__(self):
        print("---init方法----")
    def __del__(self):
        print("----del方法---")
    def __str__(self):
        print("对象的描述信息")
    # 如果子类实现了new方法,则必须调用父类的new方法(如果不调用父类,会覆盖父类的new方法,而子类根本就没有创建对象的操作)
    def __new__(cls, *args, **kwargs):
        print("---new方法---")
        return object.__new__(cls)
d = Dog2()
'''
#相当于做了3件事情
1.调用__new__方法创建对象,然后找一个变量接受__new__的返回值(就是__new__里面的返回值),这个返回值表示创建出来对象的引用
2.__init(刚刚创建出来的对象的引用)
3.返回对象的引用(就是d)
'''

print("==========================单例==================")
class Stu():
    __instance= None

    def __new__(cls, *args, **kwargs):
        if cls.__instance == None:
            cls.__instance = object.__new__(cls)
            return cls.__instance
        else:
            return cls.__instance

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

a = Stu("小红")
b = Stu("小兰")
print(id(a),a.name,id(b),b.name)

print("==========================内置的特殊方法==================")
print("----------__str__---------")
#__str__: 使用字符串描述这个类产生的实例
class Person:
    def __init__(self, n, a):
        self.name = n
        self.age = a

    def __str__(self):
        return "这个人的姓名是%s, 这个人的年龄是:%s" % (self.name, self.age)

p1 = Person("fh", 18)
print(p1) #会触发 __str_函数

p2 = Person("zhangsan", 19)
print(p2)  #会触发 __str_函数

s = str(p1) #也会触发 __str_函数
print(s, type(s))

print("----------__repr__---------")
#自己的理解就是repr可以打印一些给开发者看的东西,比如想看看对象的地址什么的,可以在类内部实现repr这个方法,外部通过repr(p1)调用
class Person:
    def __init__(self, n, a):
        self.name = n
        self.age = a

    def __str__(self):
        return "这个人的姓名是%s, 这个人的年龄是:%s" % (self.name, self.age)

    # def __repr__(self):
    #     return "reprxxxxx"

p1 = Person("fh", 18)
print(p1)

p2 = Person("zhangsan", 19)
print(p2)

print(repr(p1))

#repr例子:
import datetime

t = datetime.datetime.now()
print(t)
print(repr(t))
tmp = repr(t)
result = eval(tmp)
print(result)

print("----------------------------__call__方法------------------------------------")
#使得"对象"具备当做函数,来调用的能力
class Person:
    def __call__(self, *args, **kwargs):
        print("xxx", args, kwargs)
        pass

p = Person()
p(123, 456, name="fh")

#应用场景举例
# 创建很多个画笔, 画笔的类型(钢笔, 铅笔), 画笔的颜色(红, 黄色, 青色, 绿色)

#使用偏函数实现
# def createPen(p_color, p_type):
#     print("创建了一个%s这个类型的画笔, 它是%s颜色" % (p_type, p_color))
#
# import functools
#
# gangbiFunc = functools.partial(createPen, p_type="钢笔")
#
# gangbiFunc("红色")
# gangbiFunc("黄色")
# gangbiFunc("绿色")

#使用面向对象实现
class PenFactory:
    def __init__(self, p_type):
        self.p_type = p_type

    def __call__(self, p_color):
        print("创建了一个%s这个类型的画笔, 它是%s颜色" % (self.p_type, p_color))

gangbiF = PenFactory("钢笔")
gangbiF("红色")
gangbiF("绿色")
gangbiF("黄色")

qianbiF = PenFactory("铅笔")
qianbiF("红色")
qianbiF("绿色")
qianbiF("黄色")

print("----------------------------索引操作------------------------------------")
#就是像字典一样的去操作对象
class Person:
    def __init__(self):
        self.cache = {} #初始化的时候,就可以有一个字典,以后就可以去操作这个字典

    def __setitem__(self, key, value):
        # print("setitem", key, value)
        self.cache[key] = value

    def __getitem__(self, item):
        # print("getitem", item)
        return self.cache[item]

    def __delitem__(self, key):
        # print("delitem", key)
        del self.cache[key]

p = Person()  #实现上述的三个内置方法,就可以像操作字典一样,操作对象
p["name"] = "fh" #对应上边的__setitem__

print(p["name"]) #对应上边的__getitem__

del p["name"] #对应上边的__delitem__

print(p.cache)

print("----------------------------切片操作------------------------------------")
#相当于列表中获取某段的数据
l = [1, 2, 3, 4, 5]
# print(l[3])
print(l[1: 4: 2])

class Person:

    def __init__(self):
        self.items = [1, 2, 3, 4, 5, 6, 7, 8]

    def __setitem__(self, key, value):
        # print(key, value)
        # print(key.start)
        # print(key.stop)
        # print(key.step)
        # print(value)

        if isinstance(key, slice):#判断所传的key,必须是一个slice对象
            self.items[key.start: key.stop: key.step] = value
            # self.items[key] = value #最好用这种方式,上边那种赋值方法也行

    def __getitem__(self, item):
        print("getitem", item)

    def __delitem__(self, key):
        print("delitem", key)

p = Person()
p[0: 4: 2] = ["a", "b"]
print(p.items)
slice  #切片对象的意思

p[0: 5: 2]  #获取 __getitem__
del p[0: 5: 2]  #删除__delitem__

print("----------------------------比较操作------------------------------------")
class Person:
    def __init__(self, age, height):
        self.age = age
        self.height = height

    # == != > >= < <=
    def __eq__(self, other):
        print("eq")
        return self.age == other.age

    def __ne__(self, other): #不相等的意思
        pass

    def __gt__(self, other): #大于
        pass

    def __ge__(self, other): #大于等于
        pass

    def __lt__(self, other): #小于
        # print("lt")
        print(self.age)
        print(other.age)
        return self.age < other.age

    def __le__(self, other):#小于等于
        pass

p1 = Person(18, 190)
p2 = Person(19, 190)
print(p1 < p2)
print(p1 <= p2) # p2 < p1
print(p1 == p2)
print(p1 != p2)
print(p1 <= p2)

print("---------------------------比较操作-补充------------------------------------")
import functools

@functools.total_ordering  #可以通过__lt__和__eq__推算出其他的方法
class Person:
    def __lt__(self, other):
        print("lt")
        # pass
        return False

    def __eq__(self, other):
        print("eq")
        pass

    # def __le__(self, other):
    #     print("le")

p1 = Person()
p2 = Person()

print(p1 <= p2) #函数内部并没有<=的方法实现 因为用了@functools.total_ordering的装饰器,系统会自动补全其他的方法

print(Person.__dict__)

print("----------------------------上下文环境的布尔值------------------------------------")

class Person:
    def __init__(self):
        self.age = 20

    def __bool__(self):
        return self.age >= 18
    pass

p = Person()
if p:#控制p是true还是false 通过__bool__方法实现
    print("xx")

print("----------------------------遍历操作-迭代器------------------------------------")
#让自己创建的对象 可以通过for in 遍历
#方式1:__getitem__
class Person:
    def __init__(self):
        self.result = 1

    def __getitem__(self, item):
        self.result += 1
        if self.result >= 6:
            raise StopIteration("停止遍历") #抛出异常

        return self.result
    pass

p = Person()
for i in p:
    print(i)

# 方式2:__iter__
print("-------------")
class Person:
    def __init__(self):
        self.result = 1

    def __iter__(self):
        print("iter")
        self.result = 1
        # return iter([1, 2, 3, 4, 5])
        return self

    def __next__(self):
        self.result += 1
        if self.result >= 6:
            raise StopIteration("停止遍历")
            return self.result

p = Person()
result = next(p)
while result:
    print(result)
    result = next(p)

print("----------------------------恢复迭代器初始值(复用)------------------------------------")
class Person:
    def __init__(self):
        self.age = 1

    def __getitem__(self, item):
        return 1

    def __iter__(self):
        self.age = 1
        return self

    def __next__(self):
        self.age += 1
        if self.age >= 6:
            raise StopIteration("stop")

        return self.age

p = Person()

for i in p:
    print(i)

for i in p:  #能够再次遍历的原因是,__iter__又重置了age的值,这样在进行又一次遍历的时候,__iter__会被重新调用,由此实现了迭代器的可以再次使用
    print(i)

import collections
print(isinstance(p, collections.Iterator))  #实现__iter__和__next__的才是一个迭代器
print(isinstance(p, collections.Iterable))  #查看是否是个可迭代对象  只要实现__iter__

'''
总结:一个可迭代对象 一定可以通过forin进行遍历 但可以for in遍历的,不一定是一个可迭代对象
比如,一个类实现了__getitem__方法,他就可以通过forin遍历,但没有实现__iter__方法,就不是一个可迭代对象
'''
print("---------------------------iter函数的使用------------------------------------")
#方式1:
class Person:
    def __init__(self):
        self.age = 1

    def __getitem__(self, item):
        self.age += 1
        if self.age >= 6:
            raise StopIteration("stop")
        return self.age

p = Person()
pt = iter(p) #类里必须实现__getitem__方法,让这个对象有迭代的功能,再调用iter()可以把该对象转换成迭代器
for i in pt:
    print(i)

print("-----------")
#方式2:
class Person:
    def __init__(self):
        self.age = 1

    def __iter__(self):
        self.age = 1
        return self

    def __next__(self):
        self.age += 1
        if self.age >= 6:
            raise StopIteration("stop")
        return self.age

    # def __call__(self, *args, **kwargs):
    #     self.age += 1
    #     if self.age >= 6:
    #         raise StopIteration("stop")
    #     return self.age

p = Person()
pt = iter(p)
for i in pt:
    print(i)

print(pt is p) #因为类现实了__iter__和__next__方法,所以本身就是一个迭代器,再调用iter(),其实是一个迭代器

# pt = iter(p.__next__, 4) # pt还可以传第二个参数,意思就是到4的时候就结束
# pt = iter(p, 4)  #也可以直接传p,但要注意,传对象的时候 要实现__call__方法,此时__next__方法可以注释,因为p会自动调用__call__里边的方法

l = [1, 2, 3]
lt = iter(l)
print(lt)
import collections
print(isinstance(l, collections.Iterator)) #列表不是迭代器
print(isinstance(l, collections.Iterable)) #列表可以迭代
print(isinstance(lt, collections.Iterator)) #转换后是迭代器
print(isinstance(lt, collections.Iterable)) #依旧可以迭代

print("----------------------------描述器-定义方式1------------------------------------")
#通过property实现
class Person:
    def __init__(self):
        self.__age = 10

    @property
    def age(self):
        return self.__age

    @age.setter
    def age(self, value):
        if value < 0:
            value = 0
        self.__age = value

    @age.deleter
    def age(self):
        print("del age")
        del self.__age

p = Person()
p.age = 19
print(p.age)

del p.age

print("---------------------------描述器-定义方式2------------------------------------")
class Age:
    def __get__(self, instance, owner):
        print("get")

    def __set__(self, instance, value):
        print("set")

    def __delete__(self, instance):
        print("delete")

class Person:
    age = Age()

p = Person()
p.age = 10
print(p.age)
del p.age

print("----------------------------描述器-调用细节------------------------------------")

class Age(object):
    def __get__(self, instance, owner):
        print("get")

    def __set__(self, instance, value):
        print("set")

    def __delete__(self, instance):
        print("delete")

class Person(object):
    age = Age()  #定义完一个描述器后,一般通过该类生成的实例去调用
    def __getattribute__(self, item): #这个方法会去调用装饰器的get方法,当你重写时,可能会进行覆盖
        print ("xxxxx")

#操作描述器的时候,如果是通过类进行调用,不会调用Age描述器里的方法(所以不能通过类调用)
# print(Person.age)
# Person.age = 19
# del Person.age

p = Person()
p.age = 10
print(p.age)
del p.age

print("----------------------------描述器-和实例属性同名时, 操作优先级------------------------------------")

# 资料描述器 get set
# 非资料描述器 仅仅实现了 get 方法, 那么他就是一个非资料描述器
# 资料描述器 > 实例属性 > 非资料描述器

class Age(object):  #它就是一个资料描述器
    def __get__(self, instance, owner):
        print("get")

    def __set__(self, instance, value):
        print("set")

    def __delete__(self, instance):
        print("delete")

class Person(object):
    age = Age()
    def __init__(self):
        self.age = 10

p = Person()

p.age = 10
print(p.age)
# del p.age

print(p.__dict__)

print("----------------------------描述器-值的存储问题------------------------------------")
class Age(object):
    def __get__(self, instance, owner):
        print("get")
        return instance.v  #取值要用instance,因为age是共享的,使用self会使所有的实例对象操作的都是一个age

    def __set__(self, instance, value):
        print("set", self, instance, value)
        instance.v = value

    def __delete__(self, instance):
        print("delete")
        del instance.v

class Person(object):
    age = Age()  #这里age在实例对象中,是共享的

p = Person()
p.age = 10
print(p.age)
# del p.age

p2 = Person()
p2.age = 11
print(p2.age)

#再次检测p的age,看是否被p2改变了
print(p.age)

print("----------------------------使用类, 实现装饰器------------------------------------")
#需求,在发说说前,增加一个登陆验证的功能
#方式1:使用闭包实现
def check(func):
    def inner():
        print("登录验证")
        func()
    return inner

@check
def fashuoshuo():
    print("发说说")

# fashuoshuo = check(fashuoshuo)  #这种写法,相当于上边加了@check

fashuoshuo()

#方式2: 使用类实现装饰器
class checkLei:
    def __init__(self, func):
        self.f = func

    def __call__(self, *args, **kwargs):
        print("登录验证")
        self.f()

# @checkLei
def fashuoshuo2():
    print("发说说2")

fashuoshuo2 = check(fashuoshuo2)
fashuoshuo2()

print("----------------------------对象的生命周期------------------------------------")
class Person:
    # def __new__(cls, *args, **kwargs):
    #     print("新建一个对象,但是,被我拦截了")

    def __init__(self):
        print("初始化方法")
        self.name = "fh"
    def __del__(self):
        print("这个对象被释放了")

p = Person()
print(p)
print(p.name)

print("----------------------------监听对象声明周期的方法-小案例------------------------------------")
# Person, 打印一下, 当前这个时刻, 由Person类, 产生的实例, 由多少个
# 创建了一个实例, 计数 + 1, 如果, 删除了一个实例, 计数 - 1
class Pet:
    __petCount = 0
    def __init__(self):
        Pet.__petCount += 1

    def __del__(self):
        self.__class__.__petCount -= 1 # 等价于 Pet.__personCount -= 1

    @classmethod #定义一个类方法
    def log(cls):
        print("当前的pet的个数是%d个" % cls.__petCount)

Pet.__petCount = 100 #私有属性,不让他在外部改动
p = Pet()
p2 = Pet()
Pet.log()
del p
del p2
Pet.log()

print("----------------------------map函数------------------------------------")
#映射函数
#函数需要一个参数
m = map(lambda x: x * x, [1, 2, 3])
for tmp in m:
    print(tmp)

print("*"*3)
#函数需要两个参数
m = map(lambda x,y:x+y,[1,2,3],[4,5,6])
for tmp in m:
    print(tmp)

print("*" * 3)
def f1(x,y):
    return (x,y)
l1 = [0,1,2,3,4,5,6]
l2 = ['sun','m','t','w','t','f','s']
l3 = map(f1,l1,l2)
print(list(l3))

print("----------------------------filter函数------------------------------------")
#过滤函数
f = filter(lambda x:x%2,[1,2,3,4])
for temp in f:
    print(temp)

print("----------------------------reduce函数------------------------------------")
#会对参数序列中元素进行累积
from functools import  reduce
r = reduce(lambda x,y:x+y,[1,2,3,4]) #1给x,2给y,结果3传给x,然后再取出3,给y,再加,依次类推
print(r)

r = reduce(lambda x,y:x+y,[1,2,3,4],5) #如果后边有值,则是把5给x,列表中1给y,和是6给x,再取2给y,然后相加再给x,依次类推
print(r)

r = reduce(lambda x,y:x+y,['aa','bb','cc'],'dd') #把dd当做x,aa当做y,之后组合成ddaa,然后ddaa当做x,bb当做y,再组成ddaabb,依次类推
print(r)

print("----------------------------sorted函数------------------------------------")
#sorted是内建函数
ss = sorted(['dd','aa','cc','bb'])
print(ss)

#和sort函数区别
aa = [113,2,44,3,2,5,6,66,7,788]
aa.sort()
print(aa)
aa.sort(reverse=True)
print(aa)