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

n, k = map(int, input().split())
color = [[2 for _ in range(n+2)]]
for _ in range(n):
    color.append([2] + list(map(int, input().split())) + [2])
color.append([2 for _ in range(n+2)])

board = [[deque() for _ in range(n+2)] for _ in range(n+2)]
marker = []
dir = []
for i in range(k):
    y, x, d = map(int, input().split())
    board[y][x].append(i)
    marker.append([y, x])
    dir.append(d - 1)

def opposite(k):
    if k == 0:
        return 1
    elif k == 1:
        return 0
    elif k == 2:
        return 3
    else:
        return 2

# idx 번째 marker를 이동시킴
def move(idx):
    tmp = deque()
    dy = [0, 0, -1, 1]
    dx = [1, -1, 0, 0]
    y, x = marker[idx]
    d = dir[idx]

    ny, nx = y + dy[d], x + dx[d]

    if color[ny][nx] == 0: # 흰색
        # 현재 board의 모든 말을 순서를 유지한 채(pop -> appendleft) tmp로 이동시킴
        while board[y][x]:
            popped = board[y][x].pop()
            tmp.appendleft(popped)
            # 각 marker의 현재 위치 갱신
            marker[popped] = [ny, nx]
        # tmp를 이동할 위치에 이어 붙임
        board[ny][nx] += tmp

        if len(board[ny][nx]) >= 4:
            return True

    elif color[ny][nx] == 1: # 빨간색
        # 현재 board의 모든 말을 순서를 반전시킨 채(pop -> append) tmp로 이동시킴
        while board[y][x]:
            popped = board[y][x].pop()
            tmp.append(popped)
            marker[popped] = [ny, nx]
        board[ny][nx] += tmp

        if len(board[ny][nx]) >= 4:
            return True

    else: # 파란색
        # 일단 방향을 바꿈
        dir[idx] = opposite(d)
        nny, nnx = y + dy[opposite(d)], x + dx[opposite(d)]
        # 바꾼 방향의 칸이 파란색이 아니면 이동
        if color[nny][nnx] != 2:
            return move(idx)

    return False

# move_marker: 모든 marker를 이동, 단 각 칸의 맨 아래에 있지 않은 marker는 이동하지 않음
def move_marker():
    for i in range(k):
        my, mx = marker[i]
        if board[my][mx].index(i) == 0:
            if move(i):
                return True

# 턴이 지날 때마다 turn 1씩 증가
turn = 1
while True:
    if turn > 1000:
        print(-1)
        break

    if move_marker():
        print(turn)
        break

    turn += 1


"""Written as part of the Design of Computer Programs course by Peter Norvig"""

import random
import itertools
from collections import Counter

mydeck = [r + s for r in '23456789TJQKA' for s in 'SHDC']

def deal(numhands, n=5, deck=mydeck):

    random.shuffle(deck)

    return [deck[n * i:n * (i + 1)] for i in range(numhands)]

def poker(hands):
    '''Return the best hand: poker([hand, ...]) => [best_hand, ...]'''

    # return max(hands, key=abs)
    # return max(hands, key=hand_rank)

    return allmax(hands, func=hand_rank)

def allmax(iterable, func=None):
    """Return a list of all items equal to the max of the iterable."""

    # sorted_items = sorted(iterable, key=key, reverse=True)

    # return [x for x in sorted_items if key(x) == key(sorted_items[0])]

    func = func or (lambda x: x)

    max_item = max(iterable, key=func)
    result = list(filter(lambda i: func(i) == func(max_item), iterable))

    return result

def hand_rank(hand):
    '''
    There are nine different types of hands.

    But assigning an integer value to type of hand
    is insufficient to assign winners. E.g., pair of 10s vs of 9s.
    We need to also rank the same types of hands.

    # if straight(ranks) and flush(hand):            # straight flush
    #     return (8, max(ranks))
    # elif kind(4, ranks):                           # 4 of a kind
    #     return (7, kind(4, ranks), kind(1, ranks))
    # elif kind(3, ranks) and kind(2, ranks):        # full house
    #     return (6, kind(3, ranks), kind(2, ranks))
    # elif flush(hand):                              # flush
    #     return (5, ranks)
    # elif straight(ranks):                          # straight
    #     return (4, max(ranks))
    # elif kind(3, ranks):                           # 3 of a kind
    #     return (3, kind(3, ranks), ranks)
    # elif two_pair(ranks):                          # 2 pair
    #     return (2, two_pair(ranks), ranks)
    # elif kind(2, ranks):                           # kind
    #     return (1, kind(2, ranks), ranks)
    # else:                                          # high card
    #     return (0, ranks)

    '''

    # use tuples for lexicographic ordering
    ranks = card_ranks(hand)
    counts = tuple(sorted(Counter(ranks).values(), reverse=True))

    straight = sum([x - ranks[-1] for x in ranks]
                   ) == 10 and len(set(ranks)) == 5
    flush = len(set([s for r, s in hand])) == 1

    rank_map = {
        (5,): 10,
        (4, 1): 7,
        (3, 2): 6,
        (3, 1, 1): 3,
        (2, 2, 1): 2,
        (2, 1, 1, 1): 1,
        (1, 1, 1, 1, 1): 0
    }

    return (max(rank_map[counts], 4 * straight + 5 * flush), ranks)

def card_ranks(cards):
    '''Return a list of the ranks, sorted with higher first.'''
    ranks = [r for r, s in cards]
    names_to_ranks = {
        'T': 10,
        'J': 11,
        'Q': 12,
        'K': 13,
        'A': 14
    }

    ranks = [names_to_ranks[x] if x in names_to_ranks.keys() else int(x)
             for x in ranks]

    # alternative
    # ranks = ['--23456789TJQKA'.index(r) for r,s in hands]

    ranks.sort(reverse=True)

    # special case: Ace-low straght
    if ranks == [14, 5, 4, 3, 2]:
        ranks = [5, 4, 3, 2, 1]

    return ranks

def straight(ranks):
    '''Return True if the ordered ranks form a 5-card straight.'''
    # sum of differences from the lowest number
    # sum_diff = sum([x - ranks[-1] for x in ranks]) # this should be 10

    sum_diff = sum([x - ranks[-1] for x in ranks])
    return sum_diff == 10 and (max(ranks) - min(ranks)) == 4 and len(set(ranks)) == 5

def flush(hand):
    '''Return True if all the cards have the same suit.'''
    suits = [s for r, s in hand]
    return len(set(suits)) == 1

def kind(n, ranks):
    """Return the first rank that this hand has exactly n of.
    Return None if there is no n-of-a-kind in the hand."""

    # generator
    # try:
    #     first_rank = next(x for x in ranks if ranks.count(x) == n)
    #     return first_rank
    # except StopIteration: # no rank of kind n
    #     return None

    return next((x for x in ranks if ranks.count(x) == n), None)

def two_pair(ranks):
    """If there are two pair, return the two ranks as a
    tuple: (highest, lowest); otherwise return None."""
    kind_generator = (x for x in set(ranks) if ranks.count(x) == 2)

    first_pair = next(kind_generator, None)
    second_pair = next(kind_generator, None)

    pairs = (first_pair, second_pair)

    if None in pairs:
        return None

    else:
        return pairs

def best_hand(hand):
    """From a 7-card hand, return the best 5 card hand."""

    five_card_hands = itertools.combinations(hand, 5)

    # best_rank = (-1, None)
    # best_h = None
    # for h in five_card_hands:
    #     rank = hand_rank(h)
    #     if rank > best_rank:
    #         best_rank = rank
    #         best_h = h

    return max(five_card_hands, key=hand_rank)

def best_wild_hand(hand):
    "Try all values for jokers in all 5-card selections."

    five_card_hands = itertools.combinations(hand, 5)

    hands = [find_best_joker(h) for h in five_card_hands]

    return max(hands, key=hand_rank)

def find_best_joker(hand):

    # red_deck = [r+s for r in '23456789TJQKA' for s in 'HD']
    # black_deck = [r+s for r in '23456789TJQKA' for s in 'SC']
    # best_rank = (-1, None)
    # best_h = hand

    # if '?B' in hand or '?R' in hand:
    #     for black_card in black_deck:
    #         for red_card in red_deck:
    #             new_hand = list(map(lambda x: black_card if x == '?B' else \
    #                                             red_card if x == '?R' else x, hand))
    #             rank = hand_rank(new_hand)
    #             if rank > best_rank:
    #                 best_rank = rank
    #                 best_h = new_hand[:]

    # return best_h

    if not ('?B' in hand or '?R' in hand):
        return hand

    deck_map = {
        '?B': [r + s for r in '23456789TJQKA' for s in 'SC'],
        '?R': [r + s for r in '23456789TJQKA' for s in 'HD']
    }

    options = [
        deck_map[card] if card in deck_map else [card]
        for card in hand
    ]

    hands = itertools.product(*options)

    return list(max(hands, key=hand_rank))


import unittest
import pandas as pd
from spac.data_utils import downsample_cells

class TestDownsampleCells(unittest.TestCase):

    def setUp(self):
        """Create sample dataframes for testing."""
        self.df_single_random = pd.DataFrame({
            'annotations': ['annotations1'] * 50 +
                           ['annotations2'] * 30 +
                           ['annotations3'] * 20,
            'value': list(range(1, 101))
        })
        animals_random = [
            'a1', 'a1', 'a1', 'a2', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7'
        ]
        regions_random = [
            'r1', 'r1', 'r2', 'r2', 'r2', 'r3', 'r4', 'r5', 'r6', 'r7'
        ]
        values_random = list(range(1, 11))
        self.df_multi_random = pd.DataFrame({
            'animal': animals_random,
            'region': regions_random,
            'value': values_random
        })

        self.annotations_single = 'annotations'
        self.annotations_multi = ['animal', 'region']

    def test_annotations_existence(self):
        with self.assertRaises(ValueError):
            downsample_cells(self.df_single_random, 'nonexistent_column')
        with self.assertRaises(ValueError):
            downsample_cells(
                self.df_multi_random,
                ['nonexistent_column', 'region']
            )

    def test_downsample_single_without_stratify(self):
        n_samples = 20
        df_downsampled_single = downsample_cells(
            self.df_single_random, self.annotations_single,
            n_samples=n_samples, stratify=False
        )
        self.assertTrue(
            all(df_downsampled_single.groupby(
                self.annotations_single
            ).size().values <= n_samples)
        )

    def test_downsample_multi_without_stratify(self):
        n_samples = 20
        df_downsampled_multi = downsample_cells(
            self.df_multi_random, self.annotations_multi,
            n_samples=n_samples, stratify=False
        )
        self.assertTrue(
            all(df_downsampled_multi.groupby(
                self.annotations_multi
            ).size().values <= n_samples)
        )

    def test_group_with_zero_samples(self):
        """Test behavior when there's an extremely underrepresented group.

        In this test, 'group_small' makes up only 1% of the data. Despite this,
        the stratified downsampling should still ensure that at least one
        sample from 'group_small' is included in the downsampled dataset.
        """
        df_zero_sample = pd.DataFrame({
            'annotations': ['group_large'] * 990 + ['group_small'] * 10,
            'value': list(range(1, 1001))
        })
        n_samples = 20
        df_downsampled = downsample_cells(
            df_zero_sample, 'annotations',
            n_samples=n_samples, stratify=True
        )
        actual_counts = df_downsampled['annotations'].value_counts()
        self.assertTrue(actual_counts.get('group_small', 0), 1)

    def test_stratification_with_rounding(self):
        """Test stratification behavior with rounding.

        The distribution of groups in this test doesn't yield whole numbers
        when multiplied by the desired sample size (n_samples). This test
        ensures that the function correctly rounds the number of samples for
        each group and adjusts them if necessary to ensure the total number
        of samples matches the desired 'n_samples'. The expected outcome should
        closely match the original distribution of groups.
        """
        # Use Dictionary with Lists Directly
        data_dict = {
            'annotations': (
                ['group1'] * 487 +
                ['group2'] * 500 +
                ['group3'] * 13
            ),
            'value': list(range(1, 1001))
        }

        # Explicitly Specify Index
        df_rounding = pd.DataFrame(data_dict, index=range(1000))

        n_samples = 100
        df_downsampled = downsample_cells(
            df_rounding, 'annotations', n_samples=n_samples, stratify=True
        )
        self.assertEqual(len(df_downsampled), n_samples)
        counts = df_downsampled['annotations'].value_counts()
        # Expected to be 50% of n_samples
        self.assertEqual(counts['group2'], 50)
        # Expected to be rounded to 49 from 48.7% of n_samples
        self.assertEqual(counts['group1'], 49)
        # Expected to be rounded to 1 from 1.3% of n_samples
        self.assertEqual(counts['group3'], 1)

    def test_stratification_frequency_single_obs_large_dataset(self):
        """Test the frequency of stratification for single annotations
        with a larger dataset."""
        n_samples = 60  # Set a smaller number to force downsampling
        df_downsampled_stratified = downsample_cells(
            self.df_single_random, self.annotations_single,
            n_samples=n_samples, stratify=True, rand=True
        )

        # Expected counts based on stratified sampling
        # annotations1: round(50/total_cells * n_samples)
        # annotations2: round(30/total_cells * n_samples)
        # annotations3: round(20/total_cells * n_samples)
        expected_counts = {
            'annotations1': 30,  # 50% of total observations
            'annotations2': 18,  # 30% of total observations
            'annotations3': 12   # 20% of total observations
        }

        actual_counts = df_downsampled_stratified[
            self.annotations_single
        ].value_counts()
        for key, expected_count in expected_counts.items():
            self.assertEqual(expected_count, actual_counts.get(key, 0))

    def test_downsampling_effect_multi_obs(self):
        # Create a larger dataset
        df_large = pd.DataFrame({
            'animal': (
                ['a1'] * 400 + ['a2'] * 300 +
                ['a3'] * 200 + ['a4'] * 100
            ),
            'region': ['r1'] * 500 + ['r2'] * 300 + ['r3'] * 200,
            'value': list(range(1, 1001))
        })
        n_samples = 100
        df_downsampled_stratified = downsample_cells(
            df_large, ['animal', 'region'],
            n_samples=n_samples, stratify=True, rand=False,
            combined_col_name='_combined_'
        )

        """
        Expected counts based on stratified sampling:
        The chosen groups in expected_counts reflect dominant combinations.
        E.g., 'a1_r1' is significant due to high frequencies of 'a1' and 'r1'.
        'a2_r2' is dominant due to high occurrences of 'a2' and 'r2'.
        'a3_r2' and 'a3_r3' are expected: 'a3' is frequent, and 'r2', 'r3' are
        dominant. 'a4_r3' is due to 'a4' intersecting with dominant 'r3'.
        Other combinations might exist but are less frequent in the original
        data. Values for each group are scaled down to the desired sample size
        (n_samples).
        """
        expected_counts = {
            'a1_r1': 40,
            'a2_r2': 20,
            'a2_r1': 10,
            'a3_r2': 10,
            'a3_r3': 10,
            'a4_r3': 10
        }

        actual_counts = (
            df_downsampled_stratified['_combined_'].value_counts()
            .to_dict()
        )
        self.assertDictEqual(expected_counts, actual_counts)

if __name__ == '__main__':
    unittest.main()


#!/usr/bin/env python

from tthAnalysis.HiggsToTauTau.configs.addMEMConfig_3l import addMEMConfig_3l
from tthAnalysis.HiggsToTauTau.jobTools import query_yes_no
from tthAnalysis.HiggsToTauTau.analysisSettings import systematics
from tthAnalysis.HiggsToTauTau.runConfig import tthAnalyzeParser, filter_samples
from tthAnalysis.HiggsToTauTau.common import logging, load_samples

import os
import getpass
import re

sys_choices          = [ 'full' ] + systematics.an_addMEM_opts
mode_choices         = [ 'default', 'bdt', 'sync' ]
max_mem_integrations = 1000000
systematics.full     = systematics.an_addMEM

parser = tthAnalyzeParser(isAddMEM = True)
parser.add_modes(mode_choices)
parser.add_sys(sys_choices)
parser.add_preselect()
parser.add_nonnominal()
parser.add_tau_id()
parser.add_use_home(False)
parser.add_jet_cleaning()
parser.add_argument('-n', '--max-mem-integrations',
  type = int, dest = 'max_mem_integrations', metavar = 'integer', default = max_mem_integrations,
  required = False,
  help = 'R|Maximum number of input files per one job (default: %i)' % max_mem_integrations
)
parser.add_argument('-F', '--rle-filter',
  type = lambda s: s.lower() in ['true', 't', 'yes', '1'], dest = 'rle_filter', metavar = 'option', default = True,
  required = False,
  help = 'R|Apply RLE filter',
)
args = parser.parse_args()

# Common arguments
era                = args.era
version            = args.version
dry_run            = args.dry_run
no_exec            = args.no_exec
auto_exec          = args.auto_exec
check_output_files = not args.not_check_input_files
debug              = args.debug
sample_filter      = args.filter
num_parallel_jobs  = args.num_parallel_jobs
running_method     = args.running_method

# Additional arguments
mode              = args.mode
systematics_label = args.systematics
use_preselected   = args.use_preselected
use_nonnominal    = args.original_central
use_home          = args.use_home
tau_id            = args.tau_id
jet_cleaning      = args.jet_cleaning

# Custom arguments
max_mem_integrations = args.max_mem_integrations
rle_filter           = args.rle_filter

# Use the arguments
central_or_shifts = []
for systematic_label in systematics_label:
  for central_or_shift in getattr(systematics, systematic_label):
    if central_or_shift not in central_or_shifts:
      central_or_shifts.append(central_or_shift)
version = "%s_%s_%s" % (version, mode, 'nonNom' if use_nonnominal else 'nom')
jet_cleaning_by_index = (jet_cleaning == 'by_index')

hadTauWP_veto_map = {
  'dR03mva' : 'Loose',
  'deepVSj' : 'VLoose',
}
hadTau_selection_veto = tau_id + hadTauWP_veto_map[tau_id]
rle_filter_file = ''
hadTauWP = tau_id + hadTauWP_veto_map[tau_id]

if mode == 'default':
  samples = load_samples(era, suffix = "preselected" if use_preselected else "")
  leptonSelection = "Fakeable"
  if rle_filter:
    rle_filter_file = 'mem_{}_{}.root'.format(era, hadTauWP)
elif mode == 'bdt':
  if use_preselected:
    raise ValueError("Makes no sense to use preselected samples w/ BDT training mode")
  samples = load_samples(era, suffix = "BDT")
  leptonSelection = "Loose"
  if rle_filter:
    rle_filter_file = 'mem_forBDTtraining_{}_{}.root'.format(era, hadTauWP)
elif mode == 'sync':
  sample_suffix = "sync" if use_nonnominal else "sync_nom"
  if use_preselected:
    sample_suffix = "preselected_{}".format(sample_suffix)
  samples = load_samples(era, suffix = sample_suffix)
  leptonSelection = "Fakeable"
  if rle_filter:
    rle_filter_file = 'mem_sync_{}_{}.root'.format(era, hadTauWP)
else:
  raise ValueError("Invalid mode: %s" % mode)

if not mode.startswith("sync"):
  for sample_name, sample_info in samples.items():
    if sample_name == 'sum_events':
      continue
    if re.match("(^WZTo3LNu$|^WZTo3LNu_ext(\d)?$)", sample_info["process_name_specific"]):
      sample_info["use_it"] = True

if rle_filter_file:
  rle_filter_file = os.path.join(
    os.environ['CMSSW_BASE'], 'src', 'tthAnalysis', 'HiggsToTauTau', 'data', 'mem', rle_filter_file
  )
  if not os.path.isfile(rle_filter_file):
    raise ValueError("No such file: %s" % rle_filter_file)

if __name__ == '__main__':
  logging.info(
    "Running the jobs with the following systematic uncertainties enabled: %s" % \
    ', '.join(central_or_shifts)
  )

  if sample_filter:
    samples = filter_samples(samples, sample_filter)

  addMEMProduction = addMEMConfig_3l(
    treeName                 = 'Events',
    outputDir                = os.path.join("/local",              getpass.getuser(), "addMEM", era, version),
    localDir                 = os.path.join("/home",               getpass.getuser(), "addMEM", era, version),
    cfgDir                   = os.path.join("/scratch/persistent", getpass.getuser(), "addMEM", era, version),
    executable_addMEM        = "addMEM_3l",
    samples                  = samples,
    era                      = era,
    check_output_files       = check_output_files,
    running_method           = running_method,
    max_files_per_job        = 1, # so that we'd have 1-1 correspondence b/w input and output files
    mem_integrations_per_job = 50 if mode != 'sync' else 10,
    max_mem_integrations     = max_mem_integrations, # use -1 if you don't want to limit the nof MEM integrations
    num_parallel_jobs        = num_parallel_jobs,
    leptonSelection          = leptonSelection,
    isDebug                  = debug,
    jet_cleaning_by_index    = jet_cleaning_by_index,
    central_or_shift         = central_or_shifts,
    dry_run                  = dry_run,
    use_nonnominal           = use_nonnominal,
    use_home                 = use_home,
    rle_filter_file          = rle_filter_file,
    submission_cmd           = sys.argv,
  )

  goodToGo = addMEMProduction.create()

  if goodToGo:
    if auto_exec:
      run_addMEMProduction = True
    elif no_exec:
      run_addMEMProduction = False
    else:
      run_addMEMProduction = query_yes_no("Start jobs ?")
    if run_addMEMProduction:
      addMEMProduction.run()


"""
Starlette Exception handlers for the application.
There is a default handler for 403, 404 and 500 errors.
"""

from starlette.exceptions import HTTPException
from starlette.requests import Request
from stadsarkiv_client.core.templates import templates
from stadsarkiv_client.core.context import get_context
from stadsarkiv_client.core.translate import translate
from stadsarkiv_client.core.logging import get_log

HTML_403_PAGE = "403"
HTML_404_PAGE = "404"
HTML_500_PAGE = "500"

log = get_log()

async def not_found(request: Request, exc: HTTPException):
    context_values = {"title": translate("404 Not Found")}

    # No need to log full exception. It's a 404
    log.error(f"404 Not Found: {request.url}")
    context = await get_context(request, context_values=context_values)
    return templates.TemplateResponse("errors/default.html", context, status_code=404)

async def server_error(request: Request, exc: HTTPException):
    context_values = {"title": translate("500 Server Error")}

    log.exception(f"500 Error: {request.url}", exc_info=exc)
    context = await get_context(request, context_values=context_values)
    return templates.TemplateResponse("errors/default.html", context, status_code=500)

async def forbidden_error(request: Request, exc: HTTPException):
    context_values = {"title": translate("403 Forbidden Error")}

    log.error(f"403 Forbidden: {request.url}", exc_info=exc)
    context = await get_context(request, context_values=context_values)
    return templates.TemplateResponse("errors/default.html", context, status_code=403)

exception_handlers = {403: forbidden_error, 404: not_found, 500: server_error}


import numpy as np
from direction import Direction,Orientation
from shape import Shape
import itertools
import parameters
from scipy.optimize import minimize

def get_agents_coordinates(center, num_agents, hexa_type ="paper"):
    """
    Computes the correct coordinates of the agents, according to an hexagonal lattice.
    :param center: center of the world
    :param num_agents: number of coordinates to return
    :param hexa_type: one of {"paper", "rectangle"} to define the shape of the lattice
    :return: a list of the coordinates for all agents (including seeds)
    """
    c_x = center[0]
    c_y = center[1]

    hexa = np.array([[0, 0, 0], [1, -1, 0], [0, -1, 1], [1, 0, -1]])
    if hexa_type == "paper":
        hexa = np.array(
            [[0, 0, 0], [1, -1, 0], [0, -1, 1],  [1, 0, -1], [-1, -1, 2], [0, -2, 2], [-2, -1, 3],
             [-1, -2, 3], [0, -3, 3],[1, -4, 3], [-2, -2, 4], [-1, -3, 4], [0, -4, 4], [1, -5, 4],
             [-3, -2, 5], [-2, -3, 5], [-1, -4, 5], [0, -5, 5], [1, -6, 5], [-4, -2, 6], [-3, -3, 6],
             [-2, -4, 6], [-1, -5, 6], [0, -6, 6], [1, -7, 6], [-4, -3, 7]])
    elif hexa_type == "rectangle":
        hexa = np.vstack((np.array([[0, 0, 0], [1, -1, 0], [0, -1, 1], [1, 0, -1]]),
                          np.array([coord for coord in get_hexa_coord(20)])))

    cubic_hexa = hexa[:, [0, 2]]
    hex2cart = np.array([[np.sqrt(3), np.sqrt(3) / 2], [0, -3 / 2]])*1/np.sqrt(3)
    cart = np.dot(hex2cart, cubic_hexa.T)

    cart_sliced = cart[:,0: 4+num_agents].copy()
    cart_sliced[0, :] = cart_sliced[0, :] + c_x-0.5
    cart_sliced[1, :] = cart_sliced[1, :] + c_y
    coord = [(x, y) for x, y in cart_sliced.T]
    return coord

def get_deltas(dir, orn):
    '''
    :param dir: desired movement direction {Direction.FWD, Direction.FWD_CW, Direction.FWD_CCW}
    :param orn: current robot orientation
    :return: dx, dy = {-1,0,1},{-1,0,1},... : displacement direction required
    '''
    dx = 0
    dy = 0

    if dir == Direction.FWD:
        if orn in [Orientation.N, Orientation.NE, Orientation.NW]:
            dy = 1
        if orn in [Orientation.S, Orientation.SE, Orientation.SW]:
            dy = -1
        if orn in [Orientation.W, Orientation.NW, Orientation.SW]:
            dx = -1
        if orn in [Orientation.E, Orientation.NE, Orientation.SE]:
            dx = 1
    elif dir == Direction.FWD_CW:
        if orn in [Orientation.N, Orientation.W, Orientation.NW]:
            dy = 1
        if orn in [Orientation.S, Orientation.SE, Orientation.E]:
            dy = -1
        if orn in [Orientation.W, Orientation.S, Orientation.SW]:
            dx = -1
        if orn in [Orientation.E, Orientation.NE, Orientation.N]:
            dx = 1
    elif dir == Direction.FWD_CCW:
        if orn in [Orientation.N, Orientation.NE, Orientation.E]:
            dy = 1
        if orn in [Orientation.S, Orientation.W, Orientation.SW]:
            dy = -1
        if orn in [Orientation.W, Orientation.NW, Orientation.N]:
            dx = -1
        if orn in [Orientation.E, Orientation.S, Orientation.SE]:
            dx = 1
    else:
        raise NotImplementedError("get_deltas not implemented for dir : " + str(dir))
    factor = 1
    if abs(dx*dy) == 1:
        # the robot is going on a diagonal axis => performing sqrt(2) instead of 1 in a step
        factor = 1/np.sqrt(2)
    dx *= factor
    dy *= factor

    return dx, dy

def at_least_three_non_colinear(list_of_points):
    """
    :param list_of_points:
    :return: True if at least three point of the list_of_points are non colinear. False otherwise
    """
    # flag = 0
    for p0, p1, p2 in itertools.combinations(list_of_points, 3):
        if not collinear(p0, p1, p2):
            return True
    return False

def collinear(p0, p1, p2):
    """
    :param p0, p1, p2: triplets of coordinates
    :return: True if the triplets p0, p1 and p2 are colinear. It uses the determinant method
    """
    x1, y1 = p1[0] - p0[0], p1[1] - p0[1]
    x2, y2 = p2[0] - p0[0], p2[1] - p0[1]
    return abs(x1 * y2 - x2 * y1) < parameters.EPSILON

def trilateration(s_pos, s_pos_n_list, distances_n_list):
    """
    :param s_pos:
    :param s_pos_n_list:
    :param distances_n_list:
    :return: the result of the minimization of the trilateration problem
    """
    # locations: [ (lat1, long1), ... ]
    # distances: [ distance1,     ... ]
    result = minimize(
        my_rmse,  # The error function
        s_pos,  # The initial guess
        args=(s_pos_n_list, distances_n_list),  # Additional parameters for mse
        method='L-BFGS-B',  # The optimisation algorithm
        options={
            'ftol': 1e-4,  # Tolerance
            'maxiter': 1e+8  # Maximum iterations
        }
    )
    return result.x

def my_rmse(s_pos, s_pos_n_list, distances_n_list):
    """
    :param s_pos: (s_x, s_y)
    :param s_pos_n_list: [ (s_x_1, s_y_1), (s_x_2, s_y_2), ... ]
    :param distances_n_list: [ d1, d2, ... ]
    :return:  mean squared error between distances from local coord system and global coord system
    """
    a = np.array(s_pos)
    distances_s_pos = [np.linalg.norm(a - np.array(b), ord=2) for b in s_pos_n_list]
    res = np.sqrt(np.mean(np.square(np.array(distances_s_pos)-np.array(distances_n_list))))
    return res

def get_hexa_coord(radius):
    """
    :param radius: indicates the size of coordinates to compute. generally, ~20 for 100 robots works fine
    :return: a list of the hexagonal coordinates as defined in [6]. Only acceptable coordinates are generated.
    """
    hexa_j = []
    hexa_k = []
    hexa_l = []
    for i in range(radius):
        for j in range(-i, i + 1):
            for k in range(-i, i + 1):
                if k <= -1:
                    for l in range(-i, i + 1):
                        if l >= 3:
                            if (np.abs(j) + np.abs(k) + np.abs(l) == 2 * i) and (j + k + l == 0):
                                hexa_j.append(j)
                                hexa_k.append(k)
                                hexa_l.append(l)
                        elif l >= 2 and j < 1:
                            if (np.abs(j) + np.abs(k) + np.abs(l) == 2 * i) and (j + k + l == 0):
                                hexa_j.append(j)
                                hexa_k.append(k)
                                hexa_l.append(l)
    return list(zip(hexa_j, hexa_k, hexa_l))


import os

invExcludes = ['group_vars', 'host_vars']
projectExcludes = ['updates', 'base', 'servers', 'planviewer', '.git']

# Draws a block
def add_nodes(digraph, nodes):
    for n in nodes:
        if isinstance(n, tuple):
            digraph.node(n[0], **n[1])
        else:
            digraph.node(n)
    return digraph

# Draws a line
def add_edges(digraph, edges):
    for e in edges:
        if isinstance(e[0], tuple):
            digraph.edge(*e[0], **e[1])
        else:
            digraph.edge(*e)
    return digraph

# Apply styles from the servertype.py to a node
def apply_styles(digraph, srv):
    digraph.graph_attr.update(
        ('digraph' in srv and srv['digraph']) or {}
    )
    digraph.node_attr.update(
        ('nodes' in srv and srv['nodes']) or {}
    )
    digraph.edge_attr.update(
        ('edges' in srv and srv['edges']) or {}
    )
    return digraph

# Retrieves all projects in specified path
def projectRetriever(projectDir):
    projects = os.listdir(projectDir)
    deletes = []
    for project in projects:
        if project in projectExcludes:
            deletes.append(project)
    for item in reversed(deletes):
        del projects[projects.index(item)]
    # print projects
    return projects

# Retrieves all inventories of provided project
def invRetriever(projectDir, project):
    inventories = os.listdir(projectDir + project)
    deletes = []
    for inventory in inventories:
        if inventory in invExcludes:
            deletes.append(inventory)
    for item in reversed(deletes):
        del inventories[inventories.index(item)]
    # print invs
    return inventories

def fileIsValid(projectDir, project, inventory):
        # check wether file is empty or not
        invFile = open(projectDir + '/' + project + '/' + inventory, 'r')
        lines = invFile.readlines()
        if len(lines[1]) > 1:
            return True
        else:
            return False
        return


import copy
import timeit

from game import *

if not os.path.exists("logs"):
    print("Setting up logging...")
    os.mkdir("logs")
if not os.path.exists("logs/players"):
    os.mkdir("logs/players")

players = [ai_random(1, "red"), ai_random(2, "blue"), ai_minimax(3, "yellow")]
board_interface = board_interface(players)
board_interface.initial_placement()

board_interface.print_board(True)
print("\n\n\n\n")
print(board_interface.get_distance_between_nodes("a1", "b,e,g"))

time = timeit.timeit(
    "board_interface.get_distance_between_nodes('a1', 'b,e,g')",
    globals=globals(),
    number=10000,
)
print(f"Average time: {time / 10000} seconds")

# Data was re-printed here as it was wrongly sorted the first time

print("\nFinal Results:\n")

data = [
    ["Player 1 (random)", 0, 5, 87, 0.03],
    ["Player 3 (random)", 0, 4.5, 85, 0.03],
    ["Player 2 (minimax)", 75.0, 9.1, 41, 4.48],
    ["Player 4 (random)", 25.0, 7, 53, 0.03],
]

data.sort(key=lambda x: (x[1]), reverse=True)

for person in data:
    person[1] = f"{person[1]}%"
    person[2] = f"{person[2]:.2f}"
    person[3] = f"{person[3]:.3f}"
    person[4] = f"{person[4]:.2f}s"

tabbed_data = tabulate(
    data,
    headers=[
        "Player",
        "Win Rate",
        "Avg Victory Points",
        "Avg Turns to Win",
        "Avg Turn Time",
    ],
    tablefmt="simple_grid",
)
print(tabbed_data)

print("\nAverage Time per Match: 4.43 minutes")


from NeuralNet.data_stream import DataStream
from NeuralNet.net_builder import NetBuilder
from NeuralNet.trainer import Trainer

# prepare data stream (train and valid)
ds = DataStream(data_path="../Data/TrainingSamples/Leduc/", batch_size=100)
ds.load_train_data(name="train")
ds.load_valid_data(name="valid")

# build network
nb = NetBuilder()
nb.setup_in_out(input_size=73, output_size=72)
nb.setup_hidden_layer(1, hidden_layer_size=50)
network = nb.build_net()
# print(network)

# train the network
trainer = Trainer(network=network, data_stream=ds, learning_rate=0.001)
trainer.train(epoch_count=10)


######################################
#
#             EXPERIMENT 2
#
#  Train 4 Networks on the WH+TW datasets
#
######################################

import pandas as pd
from tensorflow.keras.preprocessing.text import Tokenizer
import os
import time

from lib.utils import texts_preprocessing, get_embedding_matrix, fit_es, plot_history, plot_model_evaluation#plot_confusion_matrix#, predict_class
from lib.models import lstm_net, gru_net
from sklearn import metrics

def get_metrics(y_test, y_pred, Y_feat_names):
    res = {}
    res["accuracy"] = metrics.accuracy_score(y_test, y_pred)
    tmp = metrics.precision_score(y_test, y_pred, average=None)
    for index, cls in enumerate(Y_feat_names):
        res["precision_"+cls] = tmp[index]
    tmp = metrics.recall_score(y_test, y_pred, average=None)
    for index, cls in enumerate(Y_feat_names):
        res["recall_"+cls] = tmp[index]
    tmp = metrics.f1_score(y_test, y_pred, average=None)
    for index, cls in enumerate(Y_feat_names):
        res["f1_"+cls] = tmp[index]
    res["f1-micro"] = metrics.f1_score(y_test, y_pred, average="micro")
    res["f1-macro"] = metrics.f1_score(y_test, y_pred, average="macro")
    return res

def get_prediction_metrics(x_test, y_test, tokenizer, glove_dim):
    # preprocessing
    x_test_pre = texts_preprocessing(x_test, tokenizer, glove_dim)
    y_pred_loc = model.predict(x_test_pre)
    y_pred_loc = [Y_feat_names[0] if x[0]>=0.5 else Y_feat_names[1] for x in y_pred_loc]
    met_dict_loc = get_metrics(y_test, y_pred_loc, Y_feat_names)
    return met_dict_loc

#### Variables

# directories
main_path = "./"
dataset_filename = main_path+"data/annotation_results__ann"
dataset_wt_filename = main_path+"data/sample_ann2_"
glove_path = main_path+"glove/"
results_path = main_path+"experiments_02/"
models_path = results_path+"models/"
stats_path = results_path+"results/"
Y_feat_names = ["ns", "sens"]

# recurrent NN
VOCABULARY_SIZE = 10000
BATCH_SIZE = 50
DROPOUT = 0.5

# variables of NNs
ann_list = [2, 2, 3, 3]
glove_dim_list = [100, 100, 200, 200]
rnn_list = ["gru", "lstm", "gru", "lstm"]
bidirectional_list = [True, True, False, False]
num_layers_list = [2, 2, 1, 2]
num_nodes_list = [128, 256, 128, 128]
pyramid_list = [False, True, False, False]
glove = "twit"

experiment_time = time.perf_counter()

for index in range(10):
    # get data
    train_loc = pd.read_csv(dataset_wt_filename+(("0"+str(index+1))[-2:])+"_training.csv")
    val_loc = pd.read_csv(dataset_wt_filename+(("0"+str(index+1))[-2:])+"_validation.csv")
    test_loc = pd.read_csv(dataset_wt_filename+(("0"+str(index+1))[-2:])+"_test.csv")
    # transform data into sets for models
    x_train = train_loc["text"].tolist()
    y_train = pd.get_dummies(pd.DataFrame({"class": train_loc["class"].tolist()})["class"])[Y_feat_names].values
    x_val = val_loc["text"].tolist()
    y_val = pd.get_dummies(pd.DataFrame({"class": val_loc["class"].tolist()})["class"])[Y_feat_names].values
    x_test = test_loc["text"].tolist()
    y_test = pd.get_dummies(pd.DataFrame({"class": test_loc["class"].tolist()})["class"])[Y_feat_names].values
    # create tokenizer from training data
    tokenizer = Tokenizer(num_words=VOCABULARY_SIZE, split=' ')
    tokenizer.fit_on_texts(x_train)
    length = len(tokenizer.word_index)
    # loop on models
    for ann, glove_dim, rnn, bidirectional, num_layers, num_nodes, pyramid in zip(ann_list, glove_dim_list, rnn_list, bidirectional_list, num_layers_list, num_nodes_list, pyramid_list):
        # other
        if bidirectional:
            bid = "_bid"
        pyr = ""
        div_nodes = 0
        if pyramid:
            pyr = "_pyr"
            div_nodes = 2
        # load embedding
        emb_path = glove_path+'/glove.twitter.27B.' + str(glove_dim) + 'd.txt'
        if emb_path != "":
            emb_matrix = get_embedding_matrix(emb_path, tokenizer, glove_dim)
        else:
            emb_matrix = []
        # create and compile the model
        if rnn == "lstm":
            model = lstm_net(length, glove_dim, emb_matrix, glove_dim, num_layers, num_nodes, num_layers, DROPOUT, div_nodes, bidirectional)
        elif rnn == "gru":
            model = gru_net(length, glove_dim, emb_matrix, glove_dim, num_layers, num_nodes, num_layers, DROPOUT, div_nodes, bidirectional)
        # fit the model
        training_time = time.perf_counter()
        history = fit_es(model, x_train, y_train, x_val, y_val, tokenizer, glove_dim)
        training_time = time.perf_counter() - training_time
        print("Training time:", time.strftime("%H:%M:%S",time.gmtime(training_time)))
        # save model
        model_name = "sample"+(("0"+str(index+1))[-2:])+"_ann"+str(ann)+"_"+glove+"_gdim"+str(glove_dim)+"_"+rnn+bid+"_nlayers"+str(num_layers)+"_nnodes"+str(num_nodes)+pyr
        model.save(models_path+model_name)
        # model evaluation on test set
        met_dict = get_prediction_metrics(x_test, [Y_feat_names[0] if x[0]>=0.5 else Y_feat_names[1] for x in y_test], tokenizer, glove_dim)
        pd.DataFrame([met_dict]).to_csv(stats_path+model_name+".csv")

experiment_time = time.perf_counter() - experiment_time
print("Experiment time in seconds:", int(experiment_time))
print("Experiment time:", time.strftime("/%d, %H:%M:%S",time.gmtime(experiment_time)))


#euler 348

import math
import sys
from tqdm import tqdm

def is_square(integer):
    root = math.sqrt(integer)
    return integer == int(root + 0.5) ** 2

def is_cube(n):
    cube_root = n**(1./3.)
    return round(cube_root) ** 3 == n

def s_and_c(n_list):
	n1 = n_list[0]
	n2 = n_list[1]

	if is_square(n1):
		if is_cube(n2):
			return True
		else:
			return False
	elif is_cube(n1):
		if is_square(n2):
			return True
		else:
			return False
	else:
		return False

def pal(n):
	number = list(str(n))
	#number = n_list

	half = len(number) / 2
	i = 0
	last = len(number) - 1

	if len(number) % 2 == 0:
		end = half
	else:
		end = math.floor(half)

	while i < end:
		if number[i] == number[last]:
			i += 1
			last -= 1
		else:
			return False
	return True

def gen_halves(n):
	halfway = math.ceil(n/2) + 1
	for i in range(2, halfway):
		yield (i, (n-i))

all_nums = {}

s_min = 2
s_max = 50000
c_min = 2
c_max = 25000

print("Generating numbers")

squares = []
for i in range(s_min, s_max + 1):
	squares.append(i*i)

cubes = []
for j in range(c_min, c_max + 1):
	cubes.append(j*j*j)

print("Checking")

results = []

for s in tqdm(squares):

	for c in cubes:

		num = s + c
		if pal(num):

			if num not in all_nums:
				all_nums[num] = 1
			else:
				all_nums[num] += 1

				if all_nums[num] == 4:
					#print("Winner: " + str(num))
					results.append(num)

print(results)
print(sum(results))


import concurrent.futures
from urllib.request import urlopen
from urllib.parse import unquote

def load_url(url, timeout):
        with urlopen(url, timeout=timeout) as conn:
            return conn.read()

with open('res.txt', encoding="utf-8") as URLS:

    with concurrent.futures.ThreadPoolExecutor(max_workers=100) as executor:
        future_to_url = {executor.submit(load_url, url, 60): url for url in URLS.read().split("\n")}
        for future in concurrent.futures.as_completed(future_to_url):
            url = future_to_url[future]
            try:
                data = future.result()
            except Exception as exc:
                print('%r generated an exception: %s' % (url, exc))
            else:
                print('%r page is %d bytes' % (url, len(data)))


import pandas as pd
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler

# QUESTION 1
df_full = pd.read_csv( "specs/SensorData_question1.csv" )

"""
1. Generate a new attribute called Original Input3 which is a copy of the
attribute Input3. Do the same with the attribute Input12 and copy it into
Original Input12
"""
df_full['Original Input3'] = df_full['Input3']
df_full['Original Input12'] = df_full['Input12']

"""
2. Normalise the attribute Input3 using the z-score transformation method
"""
#Z-Score= (X - Mean) / Standard Dev
Input3_Mean= df_full['Input3'].mean()
Input3_STD = df_full['Input3'].std()

df_full['Input3'] = (df_full['Input3']-Input3_Mean)/Input3_STD

"""
3. Normalise the attribute Input12 in the range [0.0, 1.0].
"""
#Normalized Data = (x-min(x))/(max(x)-min(x))
Input12_Min = df_full['Input12'].min()
Input12_Max = df_full['Input12'].max()

df_full['Input12'] = (df_full['Input12'] - Input12_Min)/(Input12_Max-Input12_Min)

"""
4. Generate a new attribute called Average Input, which is the average of all
the attributes from Input1 to Input12. This average should include the
normalised attributes values but not the copies that were made of these.
"""
df_full.loc[:,'Average Input'] = df_full.drop(['Original Input3','Original Input12'], axis=1).sum(axis=1)/len(df_full.drop(['Original Input3','Original Input12'], axis=1).columns)

"""
5. Save the newly generated dataset to ./output/question1 out.csv.
"""
df_full.to_csv('output/question1_out.csv', index=False)

# QUESTION 2
"""
1. Reduce the number of attributes using Principal Component Analysis
(PCA), making sure at least 95% of all the variance is explained.
"""
df_full2 = pd.read_csv( "specs/DNAData_question2.csv" )
# Get headings of columns
col_head= df_full2.columns.values
# Get values of each column
x = df_full2.loc[:, col_head].values
print(x.shape)
# normalize the columns
x = StandardScaler().fit_transform(x)
print(x.shape)
# Get the header of each columm that is equal to the shape of X on axis 1 (column header)
header_cols = [col_head[i] for i in range(x.shape[1])]
# Store the normalised data in a new df
normalised_data = pd.DataFrame(x,columns=header_cols)
print(normalised_data.shape)
# Allow PCA to determine the minimum number of components required to have a 95% explained variance
pca_data = PCA(.95)
# Z-Score the PCA data using x, the standardized data
PC_data = pca_data.fit_transform(x)
# Convert PC_data into a dataframe
PCA_DF = pd.DataFrame(data = PC_data)
# Print the resulting explained variance for each column
#---print('Explained Variance: {}'.format(pca_data.explained_variance_ratio_))

"""
2. Discretise the PCA-generated attribute subset into 10 bins, using bins of
equal width. For each component X that you discretise, generate a new
column in the original dataset named pcaX width. For example, the first
discretised principal component will correspond to a new column called
pca1 width.
"""

# For each 'new' attribute of PCA, do the following (skipping index column)
for i in range(1,len(PCA_DF.columns)):
    # store the values of each column in col_values
    col_values = PCA_DF[i].values
    # bin each column into equal widths of 10 depending on that columns values
    bins = pd.cut(col_values, 10)
    # Get the value of each row entry we are working on (i.e value by value of the column)
    labels = bins.codes
    # Get each bin range of the current value
    bin_range = bins.categories
    # create a blank series
    pca_width_values = pd.Series([])
    # for each value in the column get its bin label/range
    for j in range(len(col_values)):
        # get the label of the current bin range
        bin_label = labels[j]
        # create a new column and add the bin label for each value
        pca_width_values[j] = bin_range[bin_label]
    # Create a new column for the new bin width labels
    df_full2['pca' + str(i) + '_width'] = pca_width_values

"""
3. Discretise PCA-generated attribute subset into 10 bins, using bins of equal
frequency (they should all contain the same number of points). For each
component X that you discretise, generate a new column in the original
1
dataset named pcaX freq. For example, the first discretised principal
component will correspond to a new column called pca1 width.
"""
# does the same as above but replace cut with qcut for quartile cutting, bin set to 10.
for i in range(1,len(PCA_DF.columns)):
    col_values = PCA_DF[i].values
    bins2 = pd.qcut(col_values, 10)
    labels2 = bins2.codes
    bin_range2 = bins2.categories
    pca_width_values2 = pd.Series([])
    for j in range(len(col_values)):
        bin_label2 = labels2[j]
        pca_width_values2[j] = bin_range2[bin_label2]
    df_full2['pca' + str(i) + '_freq'] = pca_width_values2

# Export to a CSV
df_full2.to_csv('output/question2_out.csv', index=False)

#print(pd.qcut(col_values, 10).value_counts())


import sys
import logging
import boto3

sys.path.insert(0, '.serverless/requirements')
from session.cognito import IdentityPool
from util.exceptions import NotAuthorizedException
from util.extract import callback_url, authorization_token
from config.environment_config import EnvironmentConfig
from data.s3_client import S3
from session.user import User
from config.logging_config import LoggingConfig
from web import render

log           = None  # type: logging.Logger
s3_client     = None  # type: S3
user          = None  # type: User
identity_pool = None  # type: IdentityPool
env           = EnvironmentConfig()

def setup():
    global log
    global s3_client
    global user
    global identity_pool

    print("setting up")
    LoggingConfig()
    log = logging.getLogger()
    log.info("logger set")
    log.setLevel(logging.INFO)
    log.info(f"Config: {repr(env)}")

    boto3.setup_default_session(aws_access_key_id=env.aws_key, aws_secret_access_key=env.aws_secret, region_name=env.region)
    log.info("boto session is set")

    s3_client = S3(env.bucket_name)
    log.info("s3_client is set")
    user = User(env.cognito_client_id, env.cognito_secret, env.cognito_region)
    log.info("user class set")
    identity_pool = IdentityPool(env.cognito_pool_id, env.cognito_client_id, env.cognito_region)
    log.info("identity_pool set")

def index(event, context):
    try:
        return dict(
            statusCode=200,
            body=render.index(),
            headers={'Content-Type': 'text/html; charset=utf-8'}
        )
    except Exception as e:
        return dict(
            statusCode=500,
            body=str(e)
        )

def edit(event, context):
    try:
        setup()
        print(event)
        query_params = event.get('queryStringParameters', {})
        access_token = authorization_token(event)

        if query_params and query_params.get('code') and query_params.get('state'):
            access_code  = query_params.get('code')
            access_state = query_params.get('state')
            redirect_uri = callback_url(event)
            user.start_session(identity_pool.token_url, access_code, redirect_uri, access_state)
            access_token = user.access_token
        elif access_token:
            user.verify(access_token)
        else:
            raise NotAuthorizedException

        return dict(
            statusCode=200,
            body="Fuck yeah",
            headers={"Authorization": f'Bearer {access_token}'}
        )
    except NotAuthorizedException as e:
        log.exception(e)
        return dict(
            statusCode=302,
            body="Not Authorized",
            headers={"Location": identity_pool.authorize_url}
        )
    except Exception as e:
        log.exception(e)

# event = {'body': None,
#  'headers': {'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
#              'Authorization': 'Bearer ssFBMaebIVeFcumCUUvELF1wzaNVJRQ1XA5IalKHc1498FshEe1TWyLkdM16MM3w',
#              'Accept-Encoding': 'gzip, deflate, br',
#              'Accept-Language': 'en-US,en;q=0.5',
#              'CloudFront-Forwarded-Proto': 'https',
#              'CloudFront-Is-Desktop-Viewer': 'true',
#              'CloudFront-Is-Mobile-Viewer': 'false',
#              'CloudFront-Is-SmartTV-Viewer': 'false',
#              'CloudFront-Is-Tablet-Viewer': 'false',
#              'CloudFront-Viewer-Country': 'US',
#              'Host': '0pi.us-west-2.amazonaws.com',
#              'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; '
#                            'rv:57.0) Gecko/20100101 Firefox/57.0',
#              'Via': '2.0 05c830b46.cloudfront.net '
#                     '(CloudFront)',
#              'X-Amz-Cf-Id': 'kYcLP2KNDxNLVqw5ub4UdM',
#              'X-Amzn-Trace-Id': 'Root=1-e63464347ab1fe0c514ae0',
#              'X-Forwarded-For': '174.65.23.114, 216.137.44.11',
#              'X-Forwarded-Port': '443',
#              'X-Forwarded-Proto': 'https',
#              'cache-control': 'no-cache',
#              'pragma': 'no-cache',
#              'upgrade-insecure-requests': '1'},
#  'httpMethod': 'GET',
#  'isBase64Encoded': False,
#  'path': '/edit',
#  'pathParameters': None,
#  'queryStringParameters': {},
#  'requestContext': {'accountId': '428',
#                     'apiId': '0eiv1z65i4',
#                     'httpMethod': 'GET',
#                     'identity': {'accessKey': None,
#                                  'accountId': None,
#                                  'caller': None,
#                                  'cognitoAuthenticationProvider': None,
#                                  'cognitoAuthenticationType': None,
#                                  'cognitoIdentityId': None,
#                                  'cognitoIdentityPoolId': None,
#                                  'sourceIp': '174.65.23.114',
#                                  'user': None,
#                                  'userAgent': 'Mozilla/5.0 (Macintosh; Intel '
#                                               'Mac OS X 10.13; rv:57.0) '
#                                               'Gecko/20100101 Firefox/57.0',
#                                  'userArn': None},
#                     'path': '/dev/edit',
#                     'protocol': 'HTTP/1.1',
#                     'requestId': 'e14210b1-2720-11e8-9df4-0d105233d7ed',
#                     'requestTime': '14/Mar/2018:00:44:40 +0000',
#                     'requestTimeEpoch': 1520988280644,
#                     'resourceId': 'glglnt',
#                     'resourcePath': '/edit',
#                     'stage': 'dev'},
#  'resource': '/edit',
#  'stageVariables': None}

# print(pprint(event))
# edit(event, None)


import threading
import concurrent.futures
import multiprocessing

def f():
    pass

t1 = threading.Thread(target=f)
t1.start()
t1.join()

with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
    future = executor.submit(f)
if __name__ == '__main__':
    p1 = multiprocessing.Process(target=f)
    p1.start()
    p1.join()

    with concurrent.futures.ProcessPoolExecutor() as executor:
        executor.submit(f)

""" thread and process share almost same methods, high level object like Lock, RLock, Event, Semaphore, Condition, Barrier, Timer
Lock:
has two states as unlocked and locked.
acquire will blocked on locked until other thread release to unlocked, return immediately on unlocked and release.
Lock can't release on unlocked, will raise error, and one thread can release other thread's lock,
When release, only one of other thread will acquire the lock if multiple threads are acquiring.

RLock, can recursively lock acquire on locked state when owned by thread itself, will increase recursive count by one. release will reduce count by one.
And thread can only release the lock owned by itself, can't release another thread's lock.

Event: like the lock, it has internal flag true and false. set() to true, clear() to false. Once set(), it will change all the wait() thread to free, not like lock only one thread.

Semaphore: has a internal atomic counter as the limits of the number of thread can pass at the same time in acquire(),
which when acquire will reduce by one and when release will increase by one. It is very helpful to control a number of threads can do something first.

Condition: has a internal lock, but after acquire the internal lock, it can wait() for other threads to notify, when wait() it release the lock and wait for the notify.
The thread acquire the lock can notify others to wake up from wait() to acquire the lock.

Barrier: it is a gate which how many threads need to acquire this barrier before them can pass. After passing, barrier will reset to zero.

"""

barrier = threading.Barrier(3)

class thread(threading.Thread):
    def __init__(self, thread_ID):
        threading.Thread.__init__(self)
        self.thread_ID = thread_ID
    def run(self):
        print(str(self.thread_ID) + "\n")
        print("Parties = " + str(barrier.parties) + "\n")
        print("n_waiting = " + str(barrier.n_waiting) + "\n")
        barrier.wait()

thread1 = thread(100)
thread2 = thread(101)

thread1.start()
thread2.start()

thread5 = thread(104)
thread5.start()

#print(str(barrier.broken) + "\n")
print("n_waiting after free = " + str(barrier.n_waiting))
print("parties after free = " + str(barrier.parties))
#barrier.reset()
print("n_waiting after reset = " + str(barrier.n_waiting))
thread3 = thread(102)
thread3.start()
thread4 = thread(103)
thread4.start()
thread6 = thread(105)
thread6.start()

print("End")


import numpy as np
import math
import time

def rectMatrix(matrix):
    """
    Add zeros to the matrix to make it rectangular
    :param matrix to be rectangular
    :return: matrix
    """
    (m, n) = np.shape(matrix)
    if m > n:
        matrix = np.hstack((matrix, np.zeros((m, m - n))))
    elif m < n:
        matrix = np.vstack((matrix, np.zeros((n - m, n))))
    return matrix

def initRoute(n_route, n_dots):
    """
    Initialize the route
    :param n_route: number of routes
    :param n_dots: number of dots
    :return: route
    """
    routes = np.zeros((n_route, n_dots)).astype(int)
    for i in range(n_route):
        routes[i] = np.random.choice(range(n_dots), size=n_dots, replace=False)
    return routes

def getRouteE(route, P):
    """
    Calculate the Expectation of the route
    :param route: route
    :param P: matrix
    :return: Expectation
    """
    E = 0
    for i in range(len(route)):
        E += P[i][route[i]]
    return E

def getRoutesEs(routes, P):
    """
    Calculate the Expectation of the route
    :param routes: routes
    :param P: matrix
    :return: Expectation
    """
    Es = np.zeros(len(routes))
    for i in range(len(routes)):
        Es[i] = getRouteE(routes[i], P)
    return Es

def selectRoute(routes, E):
    """
    Select the route with the lowest Expectation
    :param routes: routes
    :param E: Expectation
    :return: route selected
    """
    selected_routes = np.zeros(routes.shape).astype(int)
    probability = E / np.sum(E)
    n_routes = routes.shape[0]
    for i in range(n_routes):
        choice = np.random.choice(range(n_routes), p=probability)
        selected_routes[i] = routes[choice]
    return selected_routes

def crossoverRoute(routes, n_dots):
    """
    Crossover the route
    :param routes: routes
    :param n_dots: number of dots
    :return: routes
    """
    for i in range(0, len(routes), 2):
        r1_new, r2_new = np.zeros(n_dots), np.zeros(n_dots)
        seg_point = np.random.randint(0, n_dots)
        cross_len = n_dots - seg_point
        r1, r2 = routes[i], routes[i + 1]
        r1_cross, r2_cross = r2[seg_point:], r1[seg_point:]
        r1_non_cross = r1[np.in1d(r1, r1_cross) == False]
        r2_non_cross = r2[np.in1d(r2, r2_cross) == False]
        r1_new[:cross_len], r2_new[:cross_len] = r1_cross, r2_cross
        r1_new[cross_len:], r2_new[cross_len:] = r1_non_cross, r2_non_cross
        routes[i], routes[i + 1] = r1_new, r2_new
    return routes

def mutateRoute(routes, n_dots):
    """
    Mutate the route
    :param routes: routes
    :param n_dots: number of dots
    :return: routes
    """
    prob = 0.2
    p_rand = np.random.rand(len(routes))
    for i in range(len(routes)):
        if p_rand[i] < prob:
            mut_position = np.random.choice(range(n_dots), size=2, replace=False)
            l, r = mut_position[0], mut_position[1]
            routes[i, l], routes[i, r] = routes[i, r], routes[i, l]
    return routes

def toMatrixM(route):
    """
    Convert the route to matrix
    :param route: route
    :return: matrix
    """
    matrix = np.zeros((len(route), len(route)))
    for i in range(len(route)):
        matrix[i][route[i]] = 1
    return matrix

def calcMatrixM(P, n_route=100, epoch=100000):
    """
    Calculate the Expectation of the route
    :param routes: routes
    :param dots: dots
    :param n_dots: number of dots
    :return: best Matrix
    """
    n_dots = np.shape(P)[0]
    routes = initRoute(n_route, n_dots)
    Es = getRoutesEs(routes, P)
    best_index = Es.argmax()
    best_route, best_E = routes[best_index], Es[best_index]

    not_improve_time = 0
    for i in range(epoch):
        routes = selectRoute(routes, Es)
        routes = crossoverRoute(routes, n_dots)
        routes = mutateRoute(routes, n_dots)
        Es = getRoutesEs(routes, P)
        best_route_index = Es.argmax()
        if Es[best_route_index] > best_E:
            not_improve_time = 0
            best_route, best_E = (
                routes[best_route_index],
                Es[best_route_index],
            )
        else:
            not_improve_time += 1
        if (i + 1) % 200 == 0:
            print("epoch: {},  Max E: {}".format(i + 1, getRouteE(best_route, P)))
        if not_improve_time >= 2000:
            print("End of epoch")
            break

    return toMatrixM(best_route)

def main():
    P = np.loadtxt("./output/saved_P.out", delimiter=",")
    P = rectMatrix(P)
    M = calcMatrixM(P, 50, 10000)
    print(M)

if __name__ == "__main__":
    main()


import os
import numpy as np
from torch.utils.data import Dataset
from tqdm import tqdm
import torch
import random

ACC = 16            #quantize every 4 beats into 16 positions
SAMPLE_LEN = 32     #each sample has 2 bars
BAR_HOP_LEN = 1     #hop size is 1 bar
AUG_P = np.array([2, 2, 5, 5, 3, 7, 7, 5, 7, 3, 5, 1])  #prior for pitch transposition augmentation
NUM_INSTR_CLASS = 34    #number of supported instruments

#Supported instrument programs in Slakh2100 dataset
SLAKH_CLASS_PROGRAMS = dict({
    0: 'Acoustic Piano',    #0
    4: 'Electric Piano',    #1
    8: 'Chromatic Percussion',#2
    16: 'Organ',    #3
    24: 'Acoustic Guitar',  #4
    26: 'Clean Electric Guitar',    #5
    29: 'Distorted Electric Guitar',    #6
    32: 'Acoustic Bass',    #7
    33: 'Electric Bass',    #8
    40: 'Violin',   #9
    41: 'Viola',    #10
    42: 'Cello',    #11
    43: 'Contrabass',   #12
    46: 'Orchestral Harp',  #13
    47: 'Timpani',  #14
    48: 'String Ensemble',  #15
    50: 'Synth Strings',    #16
    52: 'Choir and Voice',  #17
    55: 'Orchestral Hit',   #18
    56: 'Trumpet',  #19
    57: 'Trombone', #20
    58: 'Tuba', #21
    60: 'French Horn',  #22
    61: 'Brass Section',    #23
    64: 'Soprano/Alto Sax', #24
    66: 'Tenor Sax',    #25
    67: 'Baritone Sax', #26
    68: 'Oboe', #27
    69: 'English Horn', #28
    70: 'Bassoon',  #29
    71: 'Clarinet', #30
    72: 'Pipe', #31
    80: 'Synth Lead',   #32
    88: 'Synth Pad' #33
})

#map an arbituary program to a supported Slakh2100 program
SLAKH_PROGRAM_MAPPING = dict({0: 0, 1: 0, 2: 0, 3: 0, 4: 4, 5: 4, 6: 4, 7: 4,\
                            8: 8, 9: 8, 10: 8, 11: 8, 12: 8, 13: 8, 14: 8, 15: 8,\
                            16: 16, 17: 16, 18: 16, 19: 16, 20: 16, 21: 16, 22: 16, 23: 16,\
                            24: 24, 25: 24, 26: 26, 27: 26, 28: 26, 29: 29, 30: 29, 31: 29,\
                            32: 32, 33: 33, 34: 33, 35: 33, 36: 33, 37: 33, 38: 33, 39: 33,\
                            40: 40, 41: 41, 42: 42, 43: 43, 44: 43, 45: 43, 46: 46, 47: 47,\
                            48: 48, 49: 48, 50: 50, 51: 50, 52: 52, 53: 52, 54: 52, 55: 55,\
                            56: 56, 57: 57, 58: 58, 59: 58, 60: 60, 61: 61, 62: 61, 63: 61,\
                            64: 64, 65: 64, 66: 66, 67: 67, 68: 68, 69: 69, 70: 70, 71: 71,\
                            72: 72, 73: 72, 74: 72, 75: 72, 76: 72, 77: 72, 78: 72, 79: 72,\
                            80: 80, 81: 80, 82: 80, 83: 80, 84: 80, 85: 80, 86: 80, 87: 80,\
                            88: 88, 89: 88, 90: 88, 91: 88, 92: 88, 93: 88, 94: 88, 95: 88})

#map Slakh2100 programs to continuous indices for embedding purposes
EMBED_PROGRAM_MAPPING = dict({
    0: 0, 4: 1, 8: 2, 16: 3, 24: 4, 26: 5, 29: 6, 32: 7,\
    33: 8, 40: 9, 41: 10, 42: 11, 43: 12, 46: 13, 47: 14, 48: 15,\
    50: 16, 52: 17, 55: 18, 56: 19, 57: 20, 58: 21, 60: 22, 61: 23,
    64: 24, 66: 25, 67: 26, 68: 27, 69: 28, 70: 29, 71: 30, 72: 31,\
    80: 32, 88: 33})

class Slakh2100_Pop909_Dataset(Dataset):
    def __init__(self, slakh_dir, pop909_dir, sample_len=SAMPLE_LEN, hop_len=BAR_HOP_LEN, debug_mode=False, split='train', mode='train', with_dynamics=False, merge_pop909=0):
        super(Slakh2100_Pop909_Dataset, self).__init__()
        self.split = split
        self.mode = mode
        self.debug_mode = debug_mode

        self.with_dynamics = with_dynamics
        self.merge_pop909 = merge_pop909

        self.memory = dict({'tracks': [],
                            'programs': [],
                            'dynamics': [],
                            'dir': []
                            })
        self.anchor_list = []
        self.sample_len = sample_len

        if slakh_dir is not None:
            print('loading Slakh2100 Dataset ...')
            self.load_data(slakh_dir, sample_len, hop_len)
        if pop909_dir is not None:
            print('loading Pop909 Dataset ...')
            self.load_data(pop909_dir, sample_len, hop_len)

    def __len__(self):
        return len(self.anchor_list)

    def __getitem__(self, idx):
        song_id, start = self.anchor_list[idx]
        tracks_sample = self.memory['tracks'][song_id][:, start: start+self.sample_len]
        program_sample = self.memory['programs'][song_id]
        if self.mode == 'train':    #delete empty tracks if any
            non_empty = np.nonzero(np.sum(tracks_sample, axis=(1, 2)))[0]
            tracks_sample = tracks_sample[non_empty]
            program_sample = program_sample[non_empty]
        if ((len(program_sample) <= 3) and (program_sample == 0).all()):
            #merge pop909 into a single piano track at certain probability
            if np.random.rand() < self.merge_pop909:
                tracks_sample = np.max(tracks_sample, axis=0, keepdims=True)
                program_sample = np.array([0])

        if self.with_dynamics:
            dynamics = self.memory['dynamics'][song_id][:, start: start+self.sample_len]
        else:
            dynamics = None

        return tracks_sample, program_sample, dynamics, self.memory['dir'][song_id]

    def slakh_program_mapping(self, programs):
        return np.array([EMBED_PROGRAM_MAPPING[SLAKH_PROGRAM_MAPPING[program]] for program in programs])

    def load_data(self, data_dir, sample_len, hop_len):
        song_list = [os.path.join(data_dir, self.split, item) for item in os.listdir(os.path.join(data_dir, self.split))]
        if self.debug_mode:
            song_list = song_list[: 10]
        for song_dir in tqdm(song_list):
            song_data = np.load(song_dir)
            tracks = song_data['tracks']   #(n_track, time, 128)
            if 'programs' in song_data:
                programs = song_data['programs']    #(n_track, )
            else:
                programs = np.array([0]*len(tracks))

            """clipping"""
            if (self.mode == 'train') and (self.split =='validation'):
                # during model training, no overlapping for validation set
                for i in range(0, tracks.shape[1], sample_len):
                    if i + sample_len >= tracks.shape[1]:
                        break
                    self.anchor_list.append((len(self.memory['tracks']), i))  #(song_id, start, total_length)
            else:
                # otherwise, hop size is 1-bar
                downbeats = np.nonzero(song_data['db_indicator'])[0]
                for i in range(0, len(downbeats), hop_len):
                    if downbeats[i] + sample_len >= tracks.shape[1]:
                        break
                    self.anchor_list.append((len(self.memory['tracks']), downbeats[i]))  #(song_id, start)
                #self.anchor_list.append((len(self.memory['tracks']), max(0, (tracks.shape[1]-sample_len))))

            self.memory['tracks'].append(tracks)
            self.memory['programs'].append(self.slakh_program_mapping(programs))
            self.memory['dir'].append(song_dir)

            if self.with_dynamics:
                self.memory['dynamics'].append(song_data['dynamics'])

def collate_fn(batch, device, pitch_shift=True):
    max_tracks = max([len(item[0]) for item in batch])

    tracks = []
    mixture = []
    instrument = []
    aux_feature = []
    mask = []   #track-wise pad mask
    function_pitch = []
    function_time = []

    if pitch_shift:
        aug_p = AUG_P / AUG_P.sum()
        aug_shift = np.random.choice(np.arange(-6, 6), 1, p=aug_p)[0]
    else:
        aug_shift = 0

    for pr, programs, _, _ in batch:
        pr = pr_mat_pitch_shift(pr, aug_shift)
        aux, fp, ft = compute_pr_feat(pr)
        mask.append([0]*len(pr) + [1]*(max_tracks-len(pr)))

        pr = np.pad(pr, ((0, max_tracks-len(pr)), (0, 0), (0, 0)), mode='constant', constant_values=(0,))
        programs = np.pad(programs, (0, max_tracks-len(programs)), mode='constant', constant_values=(NUM_INSTR_CLASS,))
        aux = np.pad(aux, ((0, max_tracks-len(aux)), (0, 0), (0, 0)), mode='constant', constant_values=(0,))
        fp = np.pad(fp, ((0, max_tracks-len(fp)), (0, 0)), mode='constant', constant_values=(0,))
        ft = np.pad(ft, ((0, max_tracks-len(ft)), (0, 0)), mode='constant', constant_values=(0,))

        mix = pr2grid(np.max(pr, axis=0), max_note_count=32)
        grid = np.array([pr2grid(matrix) for matrix in pr])

        tracks.append(grid)
        mixture.append(mix)
        instrument.append(programs)
        aux_feature.append(aux)
        function_pitch.append(fp)
        function_time.append(ft)

    return  torch.from_numpy(np.array(mixture)).long().to(device), \
            torch.from_numpy(np.array(instrument)).to(device), \
            torch.from_numpy(np.array(function_pitch)).float().to(device), \
            torch.from_numpy(np.array(function_time)).float().to(device),\
            torch.from_numpy(np.array(tracks)).long().to(device), \
            torch.from_numpy(np.array(aux_feature)).float().to(device), \
            torch.BoolTensor(mask).to(device)

def collate_fn_inference(batch, device):
    assert len(batch) == 1
    tracks, instrument, dynamics, song_dir = batch[0]

    track, time, _ = tracks.shape
    if time % 32 != 0:
        pad_len = (time//32+1)*32 - time
        tracks = np.pad(tracks, ((0, 0), (0, pad_len), (0, 0)))
        if dynamics is not None:
            dynamics = np.pad(dynamics, ((0, 0), (0, pad_len), (0, 0), (0, 0)))
            dynamics[:, -pad_len:, :, -1] = -1
    tracks = tracks.reshape(track, -1, 32, 128).transpose(1, 0, 2, 3)

    _, function_pitch, function_time = compute_pr_feat(tracks)

    mixture = np.array([pr2grid(matrix, max_note_count=32) for matrix in np.max(tracks, axis=1)])

    mixture = torch.from_numpy(mixture).long().to(device)
    instrument = torch.from_numpy(instrument).repeat(tracks.shape[0], 1).to(device)
    function_pitch = torch.from_numpy(np.array(function_pitch)).float().to(device)
    function_time = torch.from_numpy(np.array(function_time)).float().to(device)

    return (mixture, instrument, function_pitch, function_time), dynamics, song_dir

def pr_mat_pitch_shift(pr_mat, shift):
    pr_mat = pr_mat.copy()
    pr_mat = np.roll(pr_mat, shift, -1)
    return pr_mat

def pr2grid(pr_mat, max_note_count=16, max_pitch=127, min_pitch=0,
                       pitch_pad_ind=130, dur_pad_ind=2,
                       pitch_sos_ind=128, pitch_eos_ind=129):
    """pr_mat: (32, 128)"""
    sample_len = len(pr_mat)
    grid = np.ones((sample_len, max_note_count, 6), dtype=int) * dur_pad_ind
    grid[:, :, 0] = pitch_pad_ind
    grid[:, 0, 0] = pitch_sos_ind
    cur_idx = np.ones(sample_len, dtype=int)
    for t, p in zip(*np.where(pr_mat != 0)):
        if cur_idx[t] == max_note_count - 1:
            continue
        grid[t, cur_idx[t], 0] = p - min_pitch
        binary = np.binary_repr(min(int(pr_mat[t, p]), 32) - 1, width=5)
        grid[t, cur_idx[t], 1: 6] = \
            np.fromstring(' '.join(list(binary)), dtype=int, sep=' ')
        cur_idx[t] += 1
    grid[np.arange(0, sample_len), cur_idx, 0] = pitch_eos_ind
    return grid

def compute_pr_feat(pr):
    #pr: (track, time, 128)
    onset = (np.sum(pr, axis=-1) > 0) * 1.   #(track, time)
    rhy_intensity = np.clip(np.sum((pr > 0) * 1., axis=-1) / 14, a_min=None, a_max=1)    #(track, time)

    weight = np.sum(pr, axis=-1)
    weight[weight==0] = 1
    pitch_center = np.sum(np.arange(0, 128)[np.newaxis, np.newaxis, :] * pr, axis=-1) / weight / 128

    feature = np.stack((onset, rhy_intensity, pitch_center), axis=-1)

    func_pitch = np.sum((pr > 0) * 1., axis=-2) / 32

    func_time = rhy_intensity.copy()

    return feature, func_pitch, func_time

BACH_CHORALES_PROGRAMS = dict({
    52: 'Saprano',  #0
    52: 'Alto',     #1
    52: 'Tenor',    #2
    52: 'Bass',     #3
})

STRING_QUARTETS_PROGRAMS = dict({
    40: 'violin1',
    40: 'violin2',
    41: 'viola',
    42: 'cello',
})

class Voice_Separation_Dataset(Dataset):
    def __init__(self, bach_dir, quartets_dir, sample_len=SAMPLE_LEN, hop_len=BAR_HOP_LEN, debug_mode=False, split='train', mode='train', fold=0):
        super(Voice_Separation_Dataset, self).__init__()
        self.split = split
        self.mode = mode
        self.fold=fold
        self.debug_mode = debug_mode

        self.memory = dict({'voices': [],
                            'dir': []
                            })
        self.anchor_list = []
        self.sample_len = sample_len

        if bach_dir is not None:
            assert quartets_dir is None
            self.programs = np.array([0, 1, 2, 3])
            print('loading Bach Chorale Dataset ...')
            self.load_data(bach_dir, sample_len, hop_len)

        elif quartets_dir is not None:
            assert bach_dir is None
            self.programs = np.array([9, 9, 10, 11])
            print('loading String Quartets Dataset ...')
            self.load_data(quartets_dir, sample_len, hop_len)

    def __len__(self):
        return len(self.anchor_list)

    def __getitem__(self, idx):
        song_id, start = self.anchor_list[idx]
        if type(self.sample_len) == int:
            vocies_sample = self.memory['voices'][song_id][:, start: start+self.sample_len]
        elif self.sample_len == 'full':
            vocies_sample = self.memory['voices'][song_id][:, start:]
        program_sample = self.programs

        return vocies_sample, program_sample, None, self.memory['dir'][song_id]

    def load_data(self, data_dir, sample_len, hop_len):
        data_list = os.listdir(data_dir)
        random.seed(0)
        random.shuffle(data_list)
        folds = {}
        for i in range(10):
            folds[i] = data_list[int(len((data_list))*i/10): int(len(data_list)*(i+1)/10)]
        if self.split == 'train':
            data_list = []
            for i in range(2, 10):
                data_list += folds[(self.fold+i)%10]
        elif self.split == 'validation':
            data_list = folds[(self.fold+1)%10]
        elif self.split == 'test':
            data_list = folds[self.fold]
        elif self.split == 'full':
            data_list = []
            for i in range(0, 10):
                data_list += folds[i]
        if self.debug_mode:
            data_list = data_list[: 10]
        for song_dir in tqdm(data_list):
            song = np.load(os.path.join(data_dir, song_dir))
            try:
                song = np.stack([song['soprano'], song['alto'], song['tenor'], song['bass']], axis=0)    #(4, time, 128)
            except KeyError:
                song = np.stack([song['violin1'], song['violin2'], song['viola'], song['cello']], axis=0)    #(4, time, 128)

            if type(sample_len) == int:
                for idx in range(0, song.shape[1], hop_len*16):
                    if idx + sample_len > song.shape[1]:
                        break
                    self.anchor_list.append((len(self.memory['voices']), idx))
            elif sample_len == 'full':
                self.anchor_list.append((len(self.memory['voices']), 0))

            self.memory['voices'].append(song)
            self.memory['dir'].append(song_dir)

        #record = []
        #for key in folds:
        #    record.append(f'fold {key}\n')
        #    for name in folds[key]:
        #        record.append(f'{name}\n')
        #    record.append('\n')#
        #with open(f'./fold_record.txt', 'w') as f:
        #    f.writelines(record)


"""
Support for Blue Iris.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.blueiris/
"""
from datetime import timedelta
import voluptuous as vol

from homeassistant.const import (
    CONF_HOST, CONF_PORT, CONF_PASSWORD,
    CONF_USERNAME, CONF_SSL,
    CONF_ID, CONF_NAME)

from homeassistant.components.binary_sensor import DOMAIN as DOMAIN_BINARY_SENSOR
from homeassistant.components.camera import DOMAIN as DOMAIN_CAMERA
from homeassistant.components.switch import DOMAIN as DOMAIN_SWITCH

from homeassistant.components.mqtt import (
    CONF_PAYLOAD_AVAILABLE, DEFAULT_PAYLOAD_AVAILABLE,
    CONF_QOS, CONF_PAYLOAD_NOT_AVAILABLE, DEFAULT_PAYLOAD_NOT_AVAILABLE,
    DEFAULT_QOS)

VERSION = '2.0.0'

DOMAIN = 'blueiris'
DATA_BLUEIRIS = f'data_{DOMAIN}'
DATA_BLUEIRIS_API = f'{DATA_BLUEIRIS}_API'
DATA_BLUEIRIS_HA = f'{DATA_BLUEIRIS}_HA'
DATA_BLUEIRIS_HA_ENTITIES = f'{DATA_BLUEIRIS}_HA_Entities'
DEFAULT_NAME = "BlueIris"
DEFAULT_PORT = 80

DOMAIN_KEY_FILE = f"{DOMAIN}.key"

ATTR_ADMIN_PROFILE = 'Profile'
ATTR_SYSTEM_CAMERA_ALL_NAME = 'All'
ATTR_SYSTEM_CAMERA_ALL_ID = 'Index'
ATTR_SYSTEM_CAMERA_CYCLE_NAME = 'Cycle'
ATTR_SYSTEM_CAMERA_CYCLE_ID = '@Index'

AUDIO_EVENT_LENGTH = 2
RECONNECT_DELAY = 15

BLUEIRIS_AUTH_ERROR = "Authorization required"

SYSTEM_CAMERA_CONFIG = {
    ATTR_SYSTEM_CAMERA_ALL_NAME: ATTR_SYSTEM_CAMERA_ALL_ID,
    ATTR_SYSTEM_CAMERA_CYCLE_NAME: ATTR_SYSTEM_CAMERA_CYCLE_ID
}

SYSTEM_CAMERA_ID = [
    ATTR_SYSTEM_CAMERA_ALL_ID,
    ATTR_SYSTEM_CAMERA_CYCLE_ID
]

MQTT_AVAILABILITY_CONFIG = {
    CONF_PAYLOAD_AVAILABLE: DEFAULT_PAYLOAD_AVAILABLE,
    CONF_PAYLOAD_NOT_AVAILABLE: DEFAULT_PAYLOAD_NOT_AVAILABLE,
    CONF_QOS: DEFAULT_QOS
}

AUTHENTICATION_BASIC = 'basic'

NOTIFICATION_ID = f'{DOMAIN}_notification'
NOTIFICATION_TITLE = f'{DEFAULT_NAME} Setup'

DEFAULT_ICON = 'mdi:alarm-light'

CAMERA_ID_PLACEHOLDER = '[camera_id]'

ATTR_FRIENDLY_NAME = 'friendly_name'

PROTOCOLS = {
    True: 'https',
    False: 'http'
}

SCAN_INTERVAL = timedelta(seconds=30)

DEFAULT_FORCE_UPDATE = False

SENSOR_CONNECTIVITY_NAME = 'Connectivity'
SENSOR_MOTION_NAME = 'Motion'
SENSOR_AUDIO_NAME = 'Audio'
SENSOR_MAIN_NAME = 'Main'

SENSOR_DEVICE_CLASS = {
    SENSOR_AUDIO_NAME: 'sound'
}

MQTT_MESSAGE_TRIGGER = 'trigger'
MQTT_MESSAGE_TYPE = 'type'
MQTT_MESSAGE_VALUE_UNKNOWN = 'unknown'

MQTT_ALL_TOPIC = "BlueIris/+/Status"

CONFIG_OPTIONS = 'options'
CONFIG_CONDITIONS = 'conditions'
CONFIG_ITEMS = 'items'

ATTR_BLUE_IRIS_CAMERA = {
    "optionDisplay": CONF_NAME,
    "optionValue": CONF_ID,
    "FPS": "FPS",
    "audio": "Audio support",
    "width": "Width",
    "height": "Height",
    "isOnline": "Is Online",
    "isRecording": "Is Recording",
    "isYellow": "Issue",
    "nAlerts": "Alerts #",
    "nTriggers": "Triggers #",
    "nClips": "Clips #",
    "nNoSignal": "No Signal #",
    "error": "Error"
}
ATTR_BLUE_IRIS_STATUS = [
    "system name",
    "version",
    "license",
    "support",
    "user",
    "latitude",
    "longitude"
]

BI_DISCOVERY = f"{DOMAIN}_discovery"
BI_DISCOVERY_BINARY_SENSOR = f"{BI_DISCOVERY}_{DOMAIN_BINARY_SENSOR}"
BI_DISCOVERY_CAMERA = f"{BI_DISCOVERY}_{DOMAIN_CAMERA}"
BI_DISCOVERY_SWITCH = f"{BI_DISCOVERY}_{DOMAIN_SWITCH}"

BI_UPDATE_SIGNAL_CAMERA = f"{DOMAIN}_{DOMAIN_CAMERA}_UPDATE_SIGNAL"
BI_UPDATE_SIGNAL_BINARY_SENSOR = f"{DOMAIN}_{DOMAIN_BINARY_SENSOR}_UPDATE_SIGNAL"
BI_UPDATE_SIGNAL_SWITCH = f"{DOMAIN}_{DOMAIN_SWITCH}_UPDATE_SIGNAL"

CONFIG_FIELDS = {
    vol.Required(CONF_HOST): str,
    vol.Required(CONF_PORT): int,
    vol.Optional(CONF_SSL, default=False): bool,
    vol.Optional(CONF_USERNAME): str,
    vol.Optional(CONF_PASSWORD): str,
}

SUPPORTED_DOMAINS = [DOMAIN_SWITCH, DOMAIN_BINARY_SENSOR, DOMAIN_CAMERA]
SIGNALS = {
    DOMAIN_BINARY_SENSOR: BI_UPDATE_SIGNAL_BINARY_SENSOR,
    DOMAIN_CAMERA: BI_UPDATE_SIGNAL_CAMERA,
    DOMAIN_SWITCH: BI_UPDATE_SIGNAL_SWITCH
}

ENTITY_ID = "id"
ENTITY_NAME = "name"
ENTITY_STATE = "state"
ENTITY_ATTRIBUTES = "attributes"
ENTITY_ICON = "icon"
ENTITY_UNIQUE_ID = "unique-id"
ENTITY_EVENT = "event-type"
ENTITY_TOPIC = "topic"
ENTITY_DEVICE_CLASS = "device-class"
ENTITY_DEVICE_NAME = "device-name"
ENTITY_CAMERA_DETAILS = "camera-details"
ENTITY_BINARY_SENSOR_TYPE = "binary-sensor-type"

CONF_EXCLUDE_SYSTEM_CAMERA = "exclude-system-camera"
CONF_CLEAR_CREDENTIALS = "clear-credentials"

DOMAIN_LOAD = "load"
DOMAIN_UNLOAD = "unload"


from purdue.models.course_parser import Parser
from purdue.models.course import Course
import re

def parse_course_info(subject: str, number: int) -> Course:
    parser = Parser(subject, number)
    data = parser.get_course_info()

    title = data.find("h1", {"id": "course_preview_title"}).text

    description = data.find("hr").next_sibling
    description = re.sub("Credit Hours: .....", "", description)

    course_credits = data.find("strong").next_sibling

    course = Course(description, course_credits, title)

    return course


class Solution:
    def successfulPairs(self, spells: List[int], potions: List[int], success: int) -> List[int]:
        potions.sort()
        M = len(potions)
        p_first = potions[0]
        p_last = potions[M-1]
        ans = []
        for s in spells:
            if s * p_first >= success:
                ans.append(M)
                continue

            if s * p_last < success:
                ans.append(0)
                continue

            ok = M-1
            ng = -1
            while abs(ok - ng) > 1:
                mid = (ok + ng) // 2
                if s * potions[mid] >= success:
                    ok = mid
                else:
                    ng = mid
            ans.append(M - ok)

        return ans