from utils import *
#from trainer import Trainer
#from model import *
from sklearn.preprocessing import LabelBinarizer
import numpy as np
import multiprocessing as mp
import time
from sklearn.metrics import confusion_matrix

import random
#################################
#20180910#
'''import tensorflow as tf
config = tf.ConfigProto()
config.gpu_options.allow_growth = True
sess = tf.Session(config = config)'''
############################################
lock = mp.Lock()
counter = mp.Value('i', 0)

def main():
    # load 參數
    config = get_args()
    # load 資料
    X_train, y_train, X_val, y_val, X_test, y_test , fname_train ,fname_val = load_data(config)
    # normalize X
    X_train = np.array(X_train)/255
    X_val =  np.array(X_val)/255
    X_test = np.array(X_test)/255
    # 把 y 的 string 做成 one hot encoding 形式
    label_encoder = LabelBinarizer()
    y_train_onehot = label_encoder.fit_transform(y_train)
    y_val_onehot = label_encoder.transform(y_val)
    y_test_onehot = label_encoder.transform(y_test)
    # 印一些有的沒的
    print('X_train size:', len(X_train))
    max_x = 0
    for x in X_train:
        if max_x < len(x):
            max_x = len(x)
    print('max length:',max_x)
    if config.mode == 'train':
        print('===== train =====')
        return train(config, X_train, y_train_onehot, X_val, y_val_onehot), (X_train, y_train_onehot, X_val, y_val_onehot, X_test, y_test_onehot), label_encoder
    elif config.mode == 'test':
        print('===== test =====')
        return test(config, X_val, y_val_onehot, label_encoder), (X_train, y_train_onehot, X_val, y_val_onehot, X_test, y_test_onehot), label_encoder
    ##########################################
    ####check index to label
    elif config.mode == 'aaaaa':
        print('===== aaaaa =====')
        y_look = ['Chat','Email','File Transfer','Streaming','Torrent','VPN: Chat','VPN: Email','VPN: File Transfer','VPN: Streaming','VPN: Torrent','VPN: VoIP','VoIP' ]
        y_look_onehot = label_encoder.transform(y_look)

        for i in y_look_onehot:
            print(i)
        y_look2 = label_encoder.inverse_transform(y_look_onehot)
        print(y_look2)
        print(X_test.shape)
        print(len(y_test))
        return 0
    ###############################################
    elif config.mode == 'bbbbb' :
        sumar = np.zeros(12)
        y_look = ['Chat','Email','File Transfer','Streaming','Torrent','VPN: Chat','VPN: Email','VPN: File Transfer','VPN: Streaming','VPN: Torrent','VPN: VoIP','VoIP' ]
        for i in range(len(y_train)):
            for j in range(len(y_look)):
                if y_look[j]==y_train[i]:
                    sumar[j] += 1
        for i in range(12):
            print(y_look[i],':',sumar[i])
        return 0
    ########################################################
    elif config.mode == 'ccccc' :
        y_train_onehot = np.array([np.where(r==1)[0][0] for r in y_train_onehot])
        y_val_onehot = np.array([np.where(r==1)[0][0] for r in  y_val_onehot])
        y_test_onehot = np.array([np.where(r==1)[0][0] for r in  y_test_onehot])
        X_train, X_val,X_test = np.expand_dims(X_train, 1), np.expand_dims(X_val, 1),np.expand_dims(X_test, 1)
        pk.dump(X_train,open('X_train_ff.pickle','wb'))
        pk.dump(X_val,open('X_val_ff.pickle','wb'))
        pk.dump(y_train_onehot,open('y_train_ff.pickle','wb'))
        pk.dump(y_val_onehot,open('y_val_ff.pickle','wb'))
        return 0

    ###################################
    elif config.mode == 'tor':
        count = 0
        y_train_tor = []
        y_val_tor = []
        print(fname_train[:10])
        for i in fname_train:

            if (i.find('tor')!= -1) or (i.find('Tor')!=-1) :
                if i.find('orrent') == -1 :
                    y_train_tor.append(1)
                    count += 1
                else:
                    y_train_tor.append(0)
            else:
                y_train_tor.append(0)
        for i in fname_val:
            if (i.find('tor')!= -1) or (i.find('Tor')!=-1) :
                if i.find('orrent') == -1 :
                    y_val_tor.append(1)
                else:
                    y_val_tor.append(0)
            else:
                y_val_tor.append(0)
        y_train_tor = np.array(y_train_tor)
        y_val_tor = np.array(y_val_tor)
        print(count)
        pk.dump(y_train_tor,open('y_train_tor.pickle','wb'))
        pk.dump(y_val_tor,open('y_val_tor.pickle','wb'))
        return 0
    else:
        pass

#=================================================
#  Train
#=================================================
def train(config, X_train, y_train, X_val, y_val):
    if config.model_type == 'sae':
        ae1 = AutoEncoder(1500, 400, encoder_id = 0)
        ae2 = AutoEncoder(400, 300, encoder_id = 1)
        ae3 = AutoEncoder(300, 200, encoder_id = 2)
        ae4 = AutoEncoder(200, 100, encoder_id = 3)
        ae5 = AutoEncoder(100, 50, encoder_id = 4)
        aelist = [ae1, ae2, ae3, ae4, ae5]
        sae = StackedAutoEncoder(aelist)
        next_X_train = X_train.copy()
        next_X_val = X_val.copy()
        for i, ae in enumerate(aelist):
            trainer = Trainer(config, aelist[i],
             next_X_train, next_X_train, next_X_val, next_X_val, loss_fn = 'mse', metrics = 'mse')
            trainer.name = ('{}{}'.format(config.model_name, i+1))
            trainer.train(7)
            next_X_train = encode_by_ae(ae, next_X_train, batch_size = config.batch_size)
            next_X_val = encode_by_ae(ae, next_X_val, batch_size = config.batch_size)
        trainer = Trainer(config, sae, X_train, X_train, X_val, X_val, loss_fn = 'mse', metrics = 'mse')
        trainer.train(20)
        if config.task_type == 'app':
            sae_classifier = StackedAutoEncoderClassifier(sae)
        else:
            sae_classifier = StackedAutoEncoderClassifier2(sae)
        trainer = Trainer(config, sae_classifier, X_train, y_train, X_val, y_val,
                     loss_fn = 'categorical_crossentropy', metrics = 'f1_score')
        trainer.name = ('{}_classifier'.format(config.model_name))
        trainer.train(20)
        return trainer, (sae_classifier, sae)
        #trainer.train()
    elif config.model_type == 'cnn':
        if config.task_type == 'app':
            cnn = CNN()
        else:
            cnn = CNN2()
        X_train, X_val = np.expand_dims(X_train, 3), np.expand_dims(X_val, 3)
        print('Prepare Trainer...')
        trainer = Trainer(config, cnn, X_train, y_train, X_val, y_val,
                        loss_fn = 'categorical_crossentropy', metrics = 'f1_score')
        print('Trainer prepared.')
        trainer.train(20)
        #trainer.train(500)
        return trainer, cnn
    else:
        print('#ERROR# invalid type \'{}\''.format(config.type))
        return -1

#=================================================
#  Test
#=================================================
def test(config, X_test, y_test, label_encoder):
    if config.model_type == 'sae':
        ae1 = AutoEncoder(1500, 400, encoder_id = 0)
        ae2 = AutoEncoder(400, 300, encoder_id = 1)
        ae3 = AutoEncoder(300, 200, encoder_id = 2)
        ae4 = AutoEncoder(200, 100, encoder_id = 3)
        ae5 = AutoEncoder(100, 50, encoder_id = 4)
        aelist = [ae1, ae2, ae3, ae4, ae5]
        #for i, ae in enumerate(aelist):
        #    ae.load_weights('models/{}{}.checkpoint.pth.h5'.format(config.model_name, i+1))
        sae = StackedAutoEncoder(aelist)
        #sae.load_weights('models/{}.checkpoint.pth.h5'.format(config.model_name))
        if config.task_type == 'app':
            sae_classifier = StackedAutoEncoderClassifier(sae)
        else:
            sae_classifier = StackedAutoEncoderClassifier2(sae)
        sae_classifier.load_weights('models/{}_classifier.checkpoint.pth.h5'.format(config.model_name))
        model = sae_classifier
    elif config.model_type == 'cnn':
        if config.task_type == 'app':
            cnn = CNN()
        else:
            cnn = CNN2()
        cnn.load_weights('models/{}.checkpoint.pth.h5'.format(config.model_name))
        X_test = np.expand_dims(X_test, 3)
        model = cnn
    model.compile(optimizer = 'sgd', loss = 'categorical_crossentropy', metrics = [f1_score])
    print(model.evaluate(X_test, y_test, batch_size = config.batch_size, verbose = True))
    y_pred = model.predict_classes(X_test, batch_size = config.batch_size, verbose = True)
    labels = label_encoder.classes_
    cm = confusion_matrix(labels[y_test.argmax(1)], labels[y_pred], labels=labels)

    ##
    cm = cm.astype('int')
    np.savetxt('perpacket.csv',cm,delimiter=',')
    ##

    #dump(cm, 'objs/cm_{}.pickle'.format(config.model_name))
    return "testing without trainer", model

def check(filename):
    return not '_class' in filename

def load_data(config):
    if config.debug:
        max_data_nb = 10
    else:
        max_data_nb = 100000
    directory = 'data6'
    todo_list = gen_todo_list(directory, check = check)
    ### ver 1 ###
    train_rate = 0.8
    val_rate = 0.18
    X_train = []
    y_train = []
    X_val = []
    y_val = []
    X_test = []
    y_test = []
    fname_train = []
    fname_val = []
    for counter, filename in enumerate(todo_list):
        print(filename.split('.')[:-1])
        (tmpX, tmpy) = load(filename)

        if config.task_type == 'class':
            tmpy = load('.'.join(filename.split('.')[:-1]) + '_class.pickle')
        tmpX , tmpy = tmpX[:max_data_nb], tmpy[:max_data_nb]
        assert(len(tmpX) == len(tmpy))
        tmpX = processX(tmpX)

        random.shuffle(tmpX)

        train_num = int(len(tmpX) * train_rate)
        val_num = int(len(tmpX) * val_rate)
        X_train.extend(tmpX[:train_num])
        y_train.extend(tmpy[:train_num])
        #
        for iii in range(len(tmpy[:train_num]) ):
            fname_train.append(filename.split('.')[:-1][0]  )
        for iii in range(len(tmpy[train_num: train_num + val_num])):
            fname_val.append(filename.split('.')[:-1][0])
        #
        X_val.extend(tmpX[train_num: train_num + val_num])
        y_val.extend(tmpy[train_num: train_num + val_num])
        X_test.extend(tmpX[train_num + val_num:])
        y_test.extend(tmpy[train_num + val_num:])

        print('\rLoading... {}/{}'.format(counter+1,len(todo_list)), end = '')
    print('\r{} Data loaded.               '.format(len(todo_list)))
    return X_train, y_train, X_val, y_val, X_test, y_test , fname_train ,fname_val
    """
    ### ver2 ###
    cpus = mp.cpu_count() - 2
    oldtime = time.time()
    pool = mp.Pool(processes=cpus)
    manager = mp.Manager()
    ns = manager.Namespace()
    ns.X_train = []
    ns.y_train = []
    ns.X_val = []
    ns.y_val = []
    ns.X_test = []
    ns.y_test = []

    res = pool.map(task, [(ns, i, len(todo_list)) for i in todo_list])
    pool.close()
    pool.join()
    #pool.apply_async(task, (ns, i,))
    newtime = time.time()
    print('Using time:', newtime - oldtime, '(sec)')
    return ns.X_train, ns.y_train, ns.X_val, ns.y_val, ns.X_test, ns.y_test
    """

def processX(X ):
    if True:
        '''X = np.array(X)
        lens = [len(x) for x in X]
        maxlen = 1500
        tmpX = np.zeros((len(X), maxlen))
        mask = np.arange(maxlen) < np.array(lens)[:,None]
        tmpX[mask] = np.concatenate(X)
        return tmpX'''

        tmpX = np.zeros((len(X) , 50 , 200))
        for i in range(len(X)):

            for j in range(len(X[i])):
                if( j>=50 ):
                    break
                for k in range(len(X[i][j])):
                    tmpX[i][j][k] = X[i][j][k]
        #print(tmpX.shape)
        return tmpX

    else:
        for i, x in enumerate(X):
            tmp_x = np.zeros((1500,))
            tmp_x[:len(x)] = x
            X[i] = tmp_x
        return X

if __name__ == '__main__':
    (trainer, model), data, label_encoder = main()
    (X_train, y_train_onehot, X_val, y_val_onehot, X_test, y_test_onehot) = data


import unittest
from requests.exceptions import Timeout
from unittest.mock import Mock

# Mock requests to control its behavior
requests = Mock()

def get_holidays():
    r = requests.get("http://localhost/api/holidays")
    if r.status_code == 200:
        return r.json()
    return None

class TestCalendar(unittest.TestCase):
    def test_get_holidays_timeout(self):
        # Test a connection timeout
        requests.get.side_effect = Timeout
        with self.assertRaises(Timeout):
            get_holidays()

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

"""
You can test how get_holidays() will respond to a connection timeout by setting
requests.get.side_effect.

You use .assertRaises() to verify that get_holidays() raises an exception given the
new side effect of get().
"""


'''
Tudor Boran
August 17, 2016

This is a python script that queries the Wolfram Alpha API to populate a data set. To begin, we will be using crime statistics for some us cities

APP ID-
' TT8EJW-E7K35T3VUA '

http://api.wolframalpha.com/v2/query?appid=TT8EJW-E7K35T3VUA&input=murder%20rate%20in%20springfield%2C%20massachussets&format=plaintext

'''

import collections, requests, re
import unicodedata as unicode
import lxml.etree as etree #includes XPath

def read_city_names(fname):
	#
	#	This function reads the names of cities from 'fname'.
	#	Cities should be in format City, State\n
	#
	location = collections.namedtuple('Location', ['city','state'])
	places = []
	with open(fname, 'r') as infile:
		for line in infile:
			text = line.split(', ')
			text[1] = text[1][0:-1]						#strip the newline character
			places.append(location(text[0], text[1]))
	return places

def make_api_call(city, state, query, over_write):
	#
	#	This function makes an API call to Wolfram Alpha using 'query' as the query, about 'city, state'
	#	query is a space-separated series of words i.e. 'murder rate' or 'tornado frequency' etc
	#	It returns a string output - in XML
	#
	#	http://api.wolframalpha.com/v2/query?appid=TT8EJW-E7K35T3VUA&input=murder%20rate%20in%20springfield%2C%20massachussets&format=plaintext
	#

	url = 'http://api.wolframalpha.com/v2/query?appid=TT8EJW-E7K35T3VUA&input='
	query = query.lower()

	#first check cache if over_write is false, and if the object exists, break and return false
	if(not over_write):
		newquery = ''
		if(query == 'murder rate'):
			newquery = 'murders'
		else:
			newquery = query
		if(check_cache(city, newquery) != 'no_entry'):
			return False

	#assemble the query URL
	_query = query.split(' ')
	for word in _query:
		url += (word + '%20')
	url += 'in%20'
	_city = city.split(' ')
	for word in _city:
		url += (word+'%20')
	url = url[:-1]
	url += 'C%20'

	_state = state.split(' ')
	for word in _state:
		url += (word+'%20')
	url = url[:-3]
	url += '&format=plaintext'

	#print attempt to console
	print("Making api call for \'%s in %s, %s\' " % (query, city, state))
	print("The URL we're hitting is %s" % (url))
	#make request to server
	api_call = requests.get(url, stream=True)
	if(api_call.status_code != 200):
		print("API call failed, status code %s " % (str(api_call.status_code)))
		return False
	#else, API call went successfully and returned a result of 'content-type' text/xml in charset=utf-8
	else:
		with open('temp.dat', 'wb') as fd:		#write results to temp.dat
			for chunk in api_call.iter_content(256):
				fd.write(chunk)

	if(query == 'murder rate'):
		query = 'murders'
	#first check if the city, alone is in the cache. if not, let's add it
	if(check_cache(city, 'no_query') == 'no_entry'):
		print('Adding city %s to cache' % (city))
		add_city_to_cache(city)
	#we've added the city or it was already there. is the state field populated?
	if(check_cache(city, 'state') == 'no_entry'):
		print('Adding state %s to cache' % (state))
		add_entry_to_city(city, 'state', state, True)
	#now add the property from the query we made as a series of if-then's
	if(query == 'murders'):
		add_entry_to_city(city, query, murder_rate_from_file('temp.dat'), True)
	elif(query == 'latitude'):
		add_entry_to_city(city, query, latitude_longitude_from_file('temp.dat', 'latitude'), True)
	elif(query == 'longitude'):
		add_entry_to_city(city, query, latitude_longitude_from_file('temp.dat', 'longitude'), True)

	print('Adding property %s to cache' % (query))

	return True

def make_many_api_calls(citylist, statelist, querylist, over_write):
	#
	#	This function,for each city and state in citylist and statelist, each query in querylist. Before making any single city,state query
	#	the cache should be checked. If the entry exists in cache, then the call should either be made or not made based on over_writelist
	#
	querylist = querylist.lower()
	#error handling
	if(len(citylist) != len(statelist)):
		print('Citylist and statelist different lengths, please check format and try again.\n')
		return False

	for i in range(len(citylist)):
		if(make_api_call(citylist[i], statelist[i], querylist, over_write)):
			print('Successfully made API call.\n')
		else:
			print('Call failed.\n')

def  murder_rate_from_file(fname):
	#
	#	This function gets the murder rate per 100,000 people from a file, 'fname' that is in XML format; this is
	#	specific to the returns we get from querying Wolfram Alpha with 'murder rate in xxx, yyy' where xxx is city and yyy is state
	#
	i = 0
	with open(fname, 'r') as f:
		tree = etree.parse(f)
	root = tree.getroot()
	for element in root.iter("plaintext"):
		i += 1
		if(i == 2):
			rate = element.text.split()
			return rate[0]
	return 0

def latitude_longitude_from_file(fname, lat_or_long):
	#
	#	This function gets the latitude or longitude of a city from the return from Wolfram Alpha
	#	This function requires you to have the correct file in place with correct lat/long data.
	#	That is, it makes the assumption you know what's in the file
	#
	parser = etree.XMLParser(remove_blank_text=True)
	with open(fname, 'r') as f:
		tree = etree.parse(f, parser)
	root = tree.getroot()
	location = ''
	i = 0
	for element in root.iter("plaintext"):
		i += 1
		if(i == 2):
			loc = element.text.replace('\xc2', '')
			loc = loc.split(" ")
			position1 = loc[0].find('°')
			position2 = loc[1].find('\'')
			location += loc[0][0:position1]
			location += '.'
			if(position2 == -1):
				location += '00'
			else:
				location += loc[1][0:position2]

	print('%s = %s' % (lat_or_long, location))
	non_decimal = re.compile(r'[^\d.]+')
	non_decimal.sub('', location)
	return location

def latitude_longitude_from_cache(lat_or_long):
	#
	#	This function gets a list of all lat/lons depending on 'lat_or_long' from cache.xml
	#	Possibly problematically, this assumes every element city in cache has latitude/longitude
	#
	tree = etree.parse('cache.xml')
	root = tree.getroot()

	if(lat_or_long != 'latitude') and (lat_or_long != 'longitude'):
		print('lat_or_long was %s, which is not latitude or longitude, exactly.' % (lat_or_long))
		return False

	xpath_string = '//data/city/'
	xpath_string += lat_or_long
	query = tree.xpath(xpath_string)

	output = []
	for returned_element in query:
		if(lat_or_long == 'longitude'):
			output.append('-' + returned_element.text)
		else:
			output.append(returned_element.text)

	return output

def property_from_cache(property):
	#
	#	This function returns a list of all 'property' from cache.xml
	#	It assumes that all entry cities have 'property' else it throws an error/returns False
	#
	tree = etree.parse('cache.xml')
	root = tree.getroot()

	property = property.lower()

	if(property == 'murder rate'):
		property = 'murders'
	elif(property == 'lat'):
		property = 'latitude'
	elif(property == 'lon'):
		property = 'longitude'

	xpath_string = '//data/city/'
	xpath_string += property

	query_prop = tree.xpath(xpath_string)
	query_numcities = len(tree.xpath('//data/city'))

	if(len(query_prop) != query_numcities):
		print("Could not retreive %s from file because num cities != num instances of %s \n" % (property, property))
		return False

	output = []
	for returned_element in query_prop:
		output.append(returned_element.text)

	return output

def check_cache(cityname, query):
	#
	#	This function checks cache.xml for an entry 'city' in 'cities', then goes to 'data' in 'city' and checks if there's an entry for 'query'
	#	 if it doesn't find it, it returns 'no_entry'. Query can be 'no_query' to just check cache for the city
	#
	tree = etree.parse('cache.xml')
	root = tree.getroot()
	query = query.lower()
	for city in root.iter("city"):
		one_city = False								#one_city is used to flag when we find current name == cityname
		for element in city:
			if(element.tag == 'name'):
				if(element.text == cityname):
					one_city = True
					if(query=='no_query'):
						return element.text
			if(element.tag == query) and (one_city):	#one_city is only true for a single 'city' parent element; the city we search for
				return element.text

	return 'no_entry'

def add_city_to_cache(city):
	#
	#	This function reads cache.xml and writes a new entry <city name='city'> </city>
	#

	#in order to prettify the output, we need to do the following with the XML parser
	parser = etree.XMLParser(remove_blank_text=True)
	tree = etree.parse('cache.xml', parser)
	root = tree.getroot()
	for city_elements in root.iter('name'):
		if (city_elements.text == city):
			print("%s is already in the cache as a city entry!" % (city))
			return False

	num_cities = len(root.findall('city'))

	e_city = etree.Element('city')
	e_name = etree.Element('name')
	e_name.text = city
	e_city.append(e_name)

	root.insert(num_cities, e_city)
	tree.write('cache.xml', pretty_print=True)

	return True

def add_entry_to_city(city, property, value, over_write):
	#
	#	This method adds a property (attribute) and value (text) as sub-element to the city element specified. We assume there are not any duplicate
	#	city names. At this point. This will be ameliorated at a later point, so we can have similar-named cities from different states
	#
	#	over_write may be either 'True' or 'False'; if False, property is not overwritten if it already exists
	#
	#	This may be slow
	#
	parser = etree.XMLParser(remove_blank_text=True)
	tree = etree.parse('cache.xml', parser)
	root = tree.getroot()
	property = property.lower()

	e_prop = etree.Element(property)
	e_prop.text = value
	#first check to see if the city is in the list
	#we will use the XPath object 'finder' to utilize XPath for this purpose
	finder = tree.xpath('//data/city/name/text()')
	has_element = False

	if city not in finder:
		print("City %s not found in cache file." % (city))
		return False
	else:
		i = 0
		for cityname in finder:
			if cityname==city:
				break
			else:
				i += 1
	#get the parent 'city' block as city_root
	city_root = finder[i].getparent().getparent()
	#cycle through its children, re-assigning value if over_write = True
	for element in city_root:
		if(element.tag == property):
			has_element = True
			if(not over_write):
				return False
			else:
				element.text = value
				break

	if(not has_element):
		city_root.append(e_prop)

	tree.write('cache.xml', pretty_print=True)
	return True

'''
for i in range(len(cities)):
	if(check_cache(cities[i], 'no_query') == 'no_entry'):
		print('Adding city %s to cache' % (cities[i]))
		#add_city_to_cache(city)
	#we've added the city or it was already there. is the state field populated?
	if(check_cache(cities[i], 'state') == 'no_entry'):
		print('Adding state %s to cache under entry for city %s' % (states[i], cities[i]))
		#add_entry_to_city(city, 'state', state, True)
	#now add the property from the query we made as a series of if-then's
	if(check_cache(cities[i], 'murders') == 'no_entry'):
		print('Adding property %s to cache' % ('murder'))
	#	add_entry_to_city(city, query, murder_rate_from_file('temp.dat'), True)
'''


from typing import Any

from utils import get_input_list_from_day, convert_list_to_type, read_numbers_line

import pandas as pd

def read_bingo_line(line_str: str) -> list[int]:
    digits = '0123456789'
    numbers_in_row = []
    if line_str[0] in digits:
        prev_char_was_number = True
        current_number = line_str[0]
    else:
        prev_char_was_number = False
        current_number = ""
    for char in line_str[1:]:
        if char in digits and prev_char_was_number:
            current_number += char
            prev_char_was_number = True
        elif char in digits and ~prev_char_was_number:
            current_number = char
            prev_char_was_number = True
        elif char not in digits and prev_char_was_number:
            numbers_in_row.append(int(current_number))
            current_number = ""
            prev_char_was_number = False
    # final char
    if prev_char_was_number:
        numbers_in_row.append(int(current_number))
    return numbers_in_row

def transform_input_list_to_bingo_cards(input_list: list) -> tuple[list[int], list[Any]]:
    numbers_to_call = read_numbers_line(input_list[0])
    bingo_cards = []
    current_bingo_card = []
    for row in input_list[2:]:
        if row == '':
            bingo_cards.append(current_bingo_card)
            current_bingo_card = []
        else:
            bingo_line = read_bingo_line(row)
            current_bingo_card.append(bingo_line)
    return numbers_to_call, bingo_cards

class BingoCard:
    def __init__(self, list_of_rows):
        self.my_numbers = list_of_rows
        self.height = len(list_of_rows)
        self.width = len(list_of_rows[0])
        self.my_progress = [[False for _ in range(self.width)] for _ in range(self.height)]

    def print_board(self, numbers_so_far=None):
        if numbers_so_far is None:
            print(pd.DataFrame(self.my_numbers))
            print(pd.DataFrame(self.my_progress))
        else:
            print(pd.DataFrame(self.my_numbers).apply(lambda x: 'X' if x in numbers_so_far else x))

    def find_number(self, number):
        for y in range(self.height):
            for x in range(self.width):
                if self.my_numbers[y][x] == number:
                    return x, y

    def strike_position(self, x, y):
        self.my_progress[y][x] = True

    def find_and_strike_number(self, number):
        number_pos = self.find_number(number)
        if number_pos is not None:
            x, y = number_pos
            self.strike_position(x, y)

    def check_for_line(self):
        for row in self.my_progress:
            if all(row):
                return True
        for col in [[row[i] for row in self.my_progress] for i in range(self.width)]:
            if all(col):
                return True
        return False

    def make_full_turn(self, number):
        self.find_and_strike_number(number)
        if self.check_for_line():
            return "BINGO"

    def get_remaining_total(self):
        total = 0
        for y in range(self.height):
            for x in range(self.width):
                if bool(self.my_progress[y][x]):
                    continue
                else:
                    total += self.my_numbers[y][x]
        return total

class BingoManager:
    def __init__(self, numbers_to_call, bingo_cards):
        self.numbers_to_call = numbers_to_call
        self.numbers_called = []
        self.bingo_cards = [BingoCard(bingo_card) for bingo_card in bingo_cards]

    def call_number(self, number):
        winners = []
        for index, bingo_card in enumerate(self.bingo_cards):
            if bingo_card.make_full_turn(number) == 'BINGO':
                winners.append(index)
        if len(winners) > 0:
            return winners

    def play_game(self, print_board_num=None, continue_when_win=False):
        pos_list = []
        winners_so_far = []
        for number in self.numbers_to_call:
            winners = self.call_number(number)
            self.numbers_called.append(number)
            if print_board_num is not None:
                self.bingo_cards[print_board_num].print_board()
            if winners is not None:
                if not continue_when_win:
                    if len(winners) > 1:
                        return winners
                    else:
                        return winners[0]
                else:
                    new_winners = [winner for winner in winners if winner not in winners_so_far]
                    if len(new_winners) > 0:
                        pos_list.append(new_winners)
                    winners_so_far += new_winners
        return pos_list

if __name__ == "__main__":
    input_list = get_input_list_from_day(4)
    numbers_to_call, bingo_cards = transform_input_list_to_bingo_cards(input_list)
    bingo = BingoManager(numbers_to_call, bingo_cards)
    winner = bingo.play_game()
    winning_bingo_card = bingo.bingo_cards[winner]
    winners_remaining = winning_bingo_card.get_remaining_total()
    print(f"Part1: winner number {winner} had remainder {winners_remaining}, with last call {bingo.numbers_called[-1]}")
    # part 2
    bingo = BingoManager(numbers_to_call, bingo_cards)
    positions = bingo.play_game(continue_when_win=True)
    last_place = positions[-1][0]
    bingo = BingoManager(numbers_to_call, [bingo_cards[last_place]])
    winner = bingo.play_game()
    winning_bingo_card = bingo.bingo_cards[winner]
    winners_remaining = winning_bingo_card.get_remaining_total()
    print(f"Part 2: loser {last_place} had remainder {winners_remaining} with last call {bingo.numbers_called[-1]}")


from . import fileio
from . import scraper

from datetime import datetime as dt
from pprint import pprint
from time import sleep

from selenium import webdriver
from selenium.webdriver.common.keys import Keys

import os
import re

driver = None
dataframes_map = {}
searched_urls_map = {}

# TODO: Convert this module to IndeedSearch class eventually. That way this can be more
# than a command line tool. I should be able to integrate it with other code.

def init_driver():
    global driver
    driver = webdriver.Chrome()

def close_driver():
    global driver
    driver.close()

def add_to_dataframes_map(sheet_name, df):
    dataframes_map[sheet_name] = df

def add_to_searched_urls_map(search_name, search_url):
    searched_urls_map[search_name] = search_url

def get_search_timestamp():
    return dt.now().strftime('%y-%m-%dT%H-%M')

def get_stop_index(stop_index, num_rows):
    if stop_index == None:
        return num_rows
    else:
        return stop_index + 1 # Making upper bound inclusive

def get_XLSX_filename(filename):
    if '.' in filename:
        ext_len = len(filename.split('.')[-1])
        return filename[:len(filename)-ext_len] + 'xlsx'
    else:
        return filename + '.xlsx'

def get_scraped_filename(filename, timestamp, suffix='_bulk-job-searches.xlsx'):
    if filename == None:
        return os.path.join('searched_jobs', timestamp  + suffix)
    else:
        return get_XLSX_filename(filename) # Guarantee Excel workbook (all fileio uses)

def get_search_sheet_name(location, timestamp):
    # Apparently some programs cannot read sheets with names over 31 chars so timestamp length will just be 30.
    loc = re.sub(r'[^A-Za-z0-9 ]+', '', location[:16]) # If name too long, shorten it to get full timestamp
    tokens = re.split('\s+', loc)
    location_fname = "-".join(tokens)
    return (location_fname + '_' + timestamp)

def save_dataframes_map_to_file(filename, filename_timestamp, job_query):
    f = os.path.split(filename) # The URLS file is going into the same folder (hence the use of os.path.split)
    if "bulk-job-searches" in filename: # Default filename used --> use the default format for URLs file.
        searched_urls_filename = os.path.join(f[0], filename_timestamp + '_bulk-urls-searched.xlsx') # Works regardless if filepath == '' or not
    else: # Custom filename used --> just add _urls.xlsx
        searched_urls_filename = os.path.join(f[0], f[1][:len(f[1])-5] + '_urls.xlsx')
    fileio.export_bulk_dataframes_to_excel(dataframes_map, filename)
    fileio.export_search_urls_to_excel(searched_urls_map, searched_urls_filename, job_query)

def get_searched_page(job_query, location, url='https://indeed.com/'):
    driver.get(url)
    sleep(4)
    query_field = driver.find_element('xpath', '//*[@id="text-input-what"]')
    query_field.send_keys(Keys.CONTROL + "a")
    query_field.send_keys(Keys.DELETE)
    sleep(.6)
    query_field.send_keys(job_query)
    sleep(1)

    loc_field = driver.find_element('xpath', '//*[@id="text-input-where"]')
    loc_field.send_keys(Keys.CONTROL + "a")
    loc_field.send_keys(Keys.DELETE)
    sleep(.6)
    loc_field.send_keys(location)
    sleep(1)
    try:
        loc_field.send_keys(Keys.ENTER)
    except:
        driver.find_element('xpath', '//button[@type="submit"]').click()
    sleep(3)

    return driver.current_url, driver.page_source, get_search_timestamp()

def single_search(location, job_query, url='https://indeed.com/', save_to_file=True, scraped_filename=None):
    scraped_filename = get_scraped_filename(scraped_filename, get_search_timestamp(),
                                                suffix='_single-job-searches.xlsx')
    try:
        url, source, timestamp = get_searched_page(job_query, location, url)
        results_df = scraper.scrape_job_details(source)
    except KeyError as ke:
        print("KeyError: " + str(ke))
        print("\nURL searched:\n" + url)
    except Exception as e:
        print(e)
        print("\nURL searched:\n" + url)

    if save_to_file:
        search_name = get_search_sheet_name(location, timestamp)
        fileio.export_single_dataframe_to_excel(results_df, url, scraped_filename, sheet_name=search_name)
    else:
        print("Search results:")
        pprint(results_df)

    close_driver()

def batch_search(locations_file, job_query, url='https://indeed.com/', start_index=0,
                    stop_index=None, save_to_file=True, scraped_filename=None):

    file_success, locations_df = fileio.load_locations_from_CSV(locations_file)
    if not file_success: # Will then close driver at end of method execution
        print("The file could not be read. Please check the filepath and format.")
    else: # Proceed into the loop
        print('++++++++++++++++++++++++++++++++')
        print(locations_df.head(5))
        print('++++++++++++++++++++++++++++++++')

        filename_timestamp = get_search_timestamp()

        scraped_filename = get_scraped_filename(scraped_filename, timestamp=filename_timestamp)
        stop_index = get_stop_index(stop_index, num_rows=locations_df.shape[0])

        for i in range(start_index, stop_index):
            location = locations_df.iloc[i][1]
            try: location += ", " + locations_df.iloc[i][2] # Append loc2 if it exists
            except: pass

            try:
                url, source, timestamp = get_searched_page(job_query, location, url)
                results_df = scraper.scrape_job_details(source)
            except Exception as e:
                print(e)
                print(f"\nError at index {i} (line {i+1}) in locations file.")
                print("Use the index as an arg to start batch search from this location.")
                break # Break to send to save the data.

            # Name indicates location searched + time of search.
            # Will match to URL used in URLS file and name of the sheet in workbook where job searches stored.
            search_name = get_search_sheet_name(location, timestamp)

            add_to_searched_urls_map(search_name=search_name, search_url=url) # Store data in dictionary

            if results_df.shape[0] != 0:
                if save_to_file:
                    # Add to map (sheet names as keys, DataFrames as values) to export to file at end:
                    add_to_dataframes_map(sheet_name=search_name, df=results_df)
                else:
                    pprint(results_df) # Pretty print the results

        ## Save file or at least print out the search names, URLS
        if save_to_file:
            save_dataframes_map_to_file(scraped_filename, filename_timestamp, job_query)
        else:
            print("Searches:")
            pprint(searched_urls_map)

    close_driver()


'''
minist手写数字数据集，训练100个，过多个人电脑内存会溢出
'''

import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data
import os
os.environ['TP_CPP_MIN_LOG_LEVEL'] = '2'

#n为卷积次数
#x为需要进行卷积的样本(传入shape为[batch,height,width,channels]，可为None)
#filter_num为基础卷积次数
#F为卷积核的shape，填入长或宽其中一个即可，一般都是1或3或5
#multi参数为每次迭代卷积核的个数乘数的基数
#y_num参数为目标值类别的个数
#此函数不适用
def CNN_y_predict(x,n=2,filter_num=32,F=3,multi=2,y_num=10):
    #定义卷积核权重系数,需要梯度下降训练，用变量表示
    #由于多次卷积，这里用字典表示
    w_filters = {}
    b_filter = {}
    features = {}
    relu_features = {}
    max_pool_features = {}
    x_copy = x
    base_channel = list(x_copy.shape)
    for i in range(len(base_channel)):
        try:
            base_channel[i] = int(base_channel[i])
        except:
            pass

    for i in range(n):
        b_filter[i] = tf.Variable(initial_value=tf.random_normal(shape=[filter_num], dtype=tf.float32))
        w_filters[i] = tf.Variable(initial_value=tf.random_normal(shape=[F,F,base_channel[-1],filter_num],dtype=tf.float32)) + b_filter[i]
        #卷积
        features[i] = tf.nn.conv2d(x_copy,w_filters[i],[1,1,1,1],'SAME')
        #relu激活函数
        relu_features[i] = tf.nn.relu(features[i])
        #最大值池化
        max_pool_features[i] = tf.nn.max_pool(relu_features[i],[1,2,2,1],[1,2,2,1],'SAME')

        #计算池化后出来的shape,更新shape信息
        if base_channel[1] % 2 == 0:
            base_channel = (base_channel[0],int(base_channel[1] / 2),int(base_channel[2] / 2),filter_num)
        else:
            base_channel = (base_channel[0],int(base_channel[1] / 2)+1,int(base_channel[2] / 2)+1,filter_num)
        filter_num = filter_num * multi
        x_copy = max_pool_features[i]

    if str(base_channel[0]) == '?':
        base_channel = list(base_channel)
        base_channel[0] = -1

    x_copy = tf.reshape(x_copy,(base_channel[0],base_channel[1]*base_channel[2]*base_channel[3]))
    #计算出最后的w权重系数
    w = tf.Variable(initial_value=tf.random_normal(shape=(base_channel[1]*base_channel[2]*base_channel[3],y_num)))
    b = tf.Variable(initial_value=tf.random_normal(shape=[y_num]))
    y_predict = tf.matmul(x_copy,w) + b
    return (y_predict,w_filters,b_filter,features,relu_features,max_pool_features)

#卷积操作函数，传入图片进行两次卷积 - 激活 - 池化
def CNN_num_two(images_):
    with tf.variable_scope('create_convolution_ratio'):
        # 第一次大卷积层
        # 创建3*3卷积核,32个卷积核
        fil1 = tf.Variable(initial_value=tf.random_normal(shape=[3, 3, 1, 32]))
        # 卷积操作
        # 卷积结束后输出形状为[-1,28,28,32]
        # 卷积需要加上偏置
        features1 = tf.nn.conv2d(images_, fil1, [1, 1, 1, 1], 'SAME') + tf.Variable(initial_value=tf.random_normal(shape=[32]))
        # Relu激活函数
        # 激活函数不变shape
        relu_features1 = tf.nn.relu(features1)
        # 池化
        # 池化之后输出形状为[-1,14,14,32]
        max_pool_features1 = tf.nn.max_pool(relu_features1, [1, 2, 2, 1], [1, 2, 2, 1], 'SAME')
        # print(max_pool_features1)
        # 第二次大卷积层
        # 使用第一次池化完毕的输出作为输入
        # 增加到64个卷积核
        fil2 = tf.Variable(initial_value=tf.random_normal(shape=[3, 3, 32, 64]))
        # 输出结束后shape为[-1,14,14,64]
        features2 = tf.nn.conv2d(max_pool_features1, fil2, [1, 1, 1, 1], 'SAME') + tf.Variable(initial_value=tf.random_normal(shape=[64]))

        # Relu第二次激活
        relu_features2 = tf.nn.relu(features2)

        # 第二次池化
        # 最终输出为[-1,7,7,64]
        max_pool_features2 = tf.nn.max_pool(relu_features2, [1, 2, 2, 1], [1, 2, 2, 1], 'SAME')

        # 将最后输出形状改变成符合softmax函数的形状，一般是二维
        # 最后输出形状即为通过神经网络隐藏层后的特征值
        x = tf.reshape(max_pool_features2, [-1, 7 * 7 * 64])

        return x,fil1,fil2

#输出测试集卷积-激活-池化后的输出
def CNN_test_img(img_test_CNN,fil1,fil2):
    # 卷积操作
    # 卷积结束后输出形状为[-1,28,28,32]
    features1 = tf.nn.conv2d(img_test_CNN, fil1, [1, 1, 1, 1], 'SAME')
    # Relu激活函数
    # 激活函数不变shape
    relu_features1 = tf.nn.relu(features1)
    # 池化
    # 池化之后输出形状为[-1,14,14,32]
    max_pool_features1 = tf.nn.max_pool(relu_features1, [1, 2, 2, 1], [1, 2, 2, 1], 'SAME')

    # 输出结束后shape为[-1,14,14,64]
    features2 = tf.nn.conv2d(max_pool_features1, fil2, [1, 1, 1, 1], 'SAME')

    # Relu第二次激活
    relu_features2 = tf.nn.relu(features2)

    # 第二次池化
    # 最终输出为[-1,7,7,64]
    max_pool_features2 = tf.nn.max_pool(relu_features2, [1, 2, 2, 1], [1, 2, 2, 1], 'SAME')

    # 将最后输出形状改变成符合softmax函数的形状，一般是二维
    # 最后输出形状即为通过神经网络隐藏层后的特征值
    x = tf.reshape(max_pool_features2, [-1, 7 * 7 * 64])

    return x

minist_data = input_data.read_data_sets('02-代码/mnist_data',one_hot=True)

images = tf.placeholder(dtype=tf.float32,shape=(None,784))
labels = tf.placeholder(dtype=tf.float32,shape=(None,10))
images_ = tf.reshape(images,[-1,28,28,1])

x,fil1,fil2 = CNN_num_two(images_)

with tf.variable_scope('create_w_b'):
    # 构建w系数
    w = tf.Variable(initial_value=tf.random_normal(shape=[7 * 7 * 64, 10]))
    b = tf.Variable(initial_value=tf.random_normal(shape=[10]))
    y_predict = tf.matmul(x, w) + b

#构建测试集
img_test = tf.placeholder(tf.float32,shape=(None,784))
label_test = tf.placeholder(tf.float32,shape=(None,10))
img_test_CNN = tf.reshape(img_test,[-1,28,28,1])
img_test_ = CNN_test_img(img_test_CNN,fil1,fil2)

#计算测试集结果
y_test = tf.matmul(img_test_,w) + b

#求出测试集结果中的最大值所在位置即为对应类别
#横向求，求每一排的最大值，因为上面是x*w，所以特征是横着的
test_loc = tf.argmax(y_test,axis=1)

#求出真实值的对应类别位置
true_loc = tf.argmax(label_test,axis=1)

#比对两者是否相同，相同返回True，不同返回False
bool_j = tf.equal(true_loc,test_loc)

#最后需要求出准确率，bool型无法求，所以需要转换类型
accuracy = tf.reduce_mean(tf.cast(bool_j,tf.float32))

# y_predict,w_filters,b_filter,features,relu_features,max_pool_features = CNN_y_predict(images_)
with tf.variable_scope('calc_loss'):
    #构建全连接层，计算损失
    loss = tf.nn.softmax_cross_entropy_with_logits(labels=labels,logits=y_predict)
    loss = tf.reduce_mean(loss)

with tf.variable_scope('create_optimizer'):
    optimizer = tf.train.AdamOptimizer(learning_rate=0.001)
    optimizer_min = optimizer.minimize(loss)

tf.summary.scalar('loss',loss)
tf.summary.histogram('w',w)
tf.summary.histogram('b',b)
tf.summary.histogram('filter_1',fil1)
tf.summary.histogram('filter_2',fil2)
merged = tf.summary.merge_all()

init = tf.global_variables_initializer()
saver = tf.train.Saver()
with tf.Session() as sess:
    sess.run(init)
    images_all,labels_all = minist_data.train.next_batch(300)
    img_test_all,label_test_all = minist_data.test.next_batch(100)
    # file_writer = tf.summary.FileWriter('./temp/',graph=sess.graph)
    # for i in range(500):
    #     sess.run(optimizer_min,feed_dict={labels:labels_all,images:images_all})
    #     print(sess.run(loss,feed_dict={labels:labels_all,images:images_all}))
    #
    #     summary = sess.run(merged,feed_dict={labels:labels_all,images:images_all})
    #     file_writer.add_summary(summary)
    #     saver.save(sess,'./temp/minist.ckpt')

    saver.restore(sess,'./temp/minist.ckpt')
    print(sess.run(accuracy,feed_dict={img_test:img_test_all,label_test:label_test_all}))


#
# @lc app=leetcode.cn id=42 lang=python3
#
# [42] 接雨水
#
# https://leetcode-cn.com/problems/trapping-rain-water/description/
#
# algorithms
# Hard (57.23%)
# Likes:    2591
# Dislikes: 0
# Total Accepted:    298.2K
# Total Submissions: 521K
# Testcase Example:  '[0,1,0,2,1,0,1,3,2,1,2,1]'
#
# 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图，计算按此排列的柱子，下雨之后能接多少雨水。
#
#
#
# 示例 1：
#
#
#
#
# 输入：height = [0,1,0,2,1,0,1,3,2,1,2,1]
# 输出：6
# 解释：上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图，在这种情况下，可以接 6 个单位的雨水（蓝色部分表示雨水）。
#
#
# 示例 2：
#
#
# 输入：height = [4,2,0,3,2,5]
# 输出：9
#
#
#
#
# 提示：
#
#
# n == height.length
# 0
# 0
#
#
#

# @lc code=start

'''

关键字
技巧

一个柱子的高度是由其其本身，左边max，右边max决定的
'''

class Solution:
    def trap(self, height: List[int]) -> int:
        l_max = [0 for n in range(len(height))]
        r_max = [0 for n in range(len(height))]

        for n in range(len(height)):
            if n == 0:
                l_max[n] = 0
            else:
                l_max[n] = max(l_max[n-1], height[n-1])

        for n in range(len(height)-1, -1, -1):
            if n == len(height)-1:
                r_max[n] = 0
            else:
                r_max[n] = max(r_max[n+1], height[n+1])

        res = 0
        for n in range(len(height)):
            temp = min(l_max[n], r_max[n])

            rain = temp - height[n]

            if rain > 0:
                res += rain
        return res
        # @lc code=end


# Osman Furkan Kınlı - S002969
# Department of Computer Science @ Özyeğin University
# CS545 Homework-2: Discretization for Deep Q-Learning

from ReplayDQNAgent import ReplayDQNAgent
import numpy as np
import random

"""
    This class is Experience Replay with Target Network deep Q-Learning agent.
    (ReplayTargetDQNAgent)

    Memory: 1000 experience at one level
    Start iter: After 1000 experience, ready to sample and train
    Target model architecture: Same architecture with DQNAgent
"""

class ReplayTargetDQNAgent(ReplayDQNAgent):
    def __init__(self):
        super(ReplayTargetDQNAgent, self).__init__()
        self.name = "Furkan_Kinli_3.h5f"
        self.target_model = self._build_model(name="target")
        self.update_target_model()
        self.update_step = 10

    def update_target_model(self):
        self.target_model.set_weights(self.model.get_weights())

    def replay(self):
        batch_x = random.sample(self.memory, self.batch_size)

        replay_x = np.zeros((self.batch_size, self.state_size))
        replay_y = np.zeros((self.batch_size, self.step_size))

        for i, (state, action, reward, next_state, done) in enumerate(batch_x):
            out = self.model.predict(state)[0]

            if not done:
                out[np.where(self.d_action_space == action)] = \
                    reward + self.gamma * np.amax(self.target_model.predict(next_state)[0])
            else:
                out[np.where(self.d_action_space == action)] = reward

            replay_x[i] = state
            replay_y[i] = out

        self.model.fit(replay_x, replay_y, epochs=1, verbose=0)


import sys
import random

class MaxHeap:

    def __init__(self, maxsize):
        self.maxsize = maxsize
        self.size = 0
        self.Heap = [0]*(self.maxsize + 1)
        self.Heap[0] = 1 * sys.maxsize
        self.FRONT = 1

    # Function to return the position of
    # parent for the node currently
    # at pos
    def parent(self, pos):
        return pos//2

    # Function to return the position of
    # the left child for the node currently
    # at pos
    def leftChild(self, pos):
        return 2 * pos

    # Function to return the position of
    # the right child for the node currently
    # at pos
    def rightChild(self, pos):
        return (2 * pos) + 1

    # Function that returns true if the passed
    # node is a leaf node
    def isLeaf(self, pos):
        if pos > (self.size//2) and pos <= self.size:
            return True
        return False

    # Function to swap two nodes of the heap
    def swap(self, fpos, spos):
        self.Heap[fpos], self.Heap[spos] = self.Heap[spos], self.Heap[fpos]

    # Function to heapify the node at pos
    def maxHeapify(self, pos):

        # If the node is a non-leaf node and greater
        # than any of its child
        if not self.isLeaf(pos):
            if (self.Heap[pos] < self.Heap[self.leftChild(pos)] or
               self.Heap[pos] < self.Heap[self.rightChild(pos)]):

                # Swap with the left child and heapify
                # the left child
                if self.Heap[self.leftChild(pos)] > self.Heap[self.rightChild(pos)]:
                    self.swap(pos, self.leftChild(pos))
                    self.maxHeapify(self.leftChild(pos))

                # Swap with the right child and heapify
                # the right child
                else:
                    self.swap(pos, self.rightChild(pos))
                    self.maxHeapify(self.rightChild(pos))
            # print(self.Heap)

    # Function to insert a node into the heap
    def insert(self, element):
        if self.size >= self.maxsize :
            return
        self.size+= 1
        self.Heap[self.size] = element

        current = self.size

        while self.Heap[current] > self.Heap[self.parent(current)]:
            self.swap(current, self.parent(current))
            current = self.parent(current)

    # Function to print the contents of the heap
    def Print(self):
        if self.size == 1:
             print(" PARENT : "+ str(self.Heap[i])+" LEFT CHILD : "+
                                str(self.Heap[2 * i])+" RIGHT CHILD : "+
                                str(self.Heap[2 * i + 1]))

        for i in range(1, (self.size//2)+1):
            print(" PARENT : "+ str(self.Heap[i])+" LEFT CHILD : "+
                                str(self.Heap[2 * i])+" RIGHT CHILD : "+
                                str(self.Heap[2 * i + 1]))

    # Function to build the min heap using
    # the minHeapify function
    def maxHeap(self):

        for pos in range(1, self.size//2 + 1):
            self.maxHeapify(pos)

    # Function to remove and return the minimum
    # element from the heap
    def remove(self):

        popped = self.Heap[self.FRONT]
        self.Heap[self.FRONT] = self.Heap[self.size]
        self.size-= 1
        self.maxHeapify(self.FRONT)
        return popped

def karmarkar(S):
    length = len(S)
    maxHeap = MaxHeap(200)
    for i in range(length):
        maxHeap.insert(S[i])

    # condition to terminate is that both children of the max heap will be zeroes
    #the way we programmed, the left child is always smaller than the right child
    while maxHeap.Heap[2] != 0 or maxHeap.Heap[3] != 0:
        heap = maxHeap.Heap
        maximum = max(heap[2], heap[3])
        if maximum == heap[2]:
            maxHeap.Heap[1] = heap[1] - heap[2]
            maxHeap.Heap[2] = 0
            maxHeap.maxHeapify(2)
            maxHeap.maxHeapify(1)

        else:
            maxHeap.Heap[1] = heap[1] - heap[3]
            maxHeap.Heap[3] = 0
            maxHeap.maxHeapify(3)
            maxHeap.maxHeapify(1)

    return(maxHeap.Heap[1])

# starting below are things Michael added

def repeatedRandom(S):
    length = len(S)
    minResidue = float("inf")

    # Creating a array of +1 and -1 randomly of length length
    R = []

    for i in range(25000):
        residue = 0
        # randomly assign each element to a list
        for i in range(length):
            rand_bit = random.choice([-1, +1])
            residue += S[i]*rand_bit
            R.append(rand_bit)

        residue = abs(residue)
        if residue < minResidue:
            minResidue = residue

    return minResidue

def hillClimbing(S):
    length = len(S)

    # Creating a array of +1 and -1 randomly of length length
    R = []
    residue = 0

    # determine the initial residue while generating random bits
    for i in range(length):
        rand_bit = random.choice([-1, +1])
        residue += S[i]*rand_bit
        R.append(rand_bit)

    for i in range(25000):
        randNum1 = random.randint(0, length - 1)
        randNum2 = random.randint(0, length - 1)

        # do a random move
        R[randNum1] = -1 * R[randNum1]
        rand_number = random.random()

        madeMove2 = False
        if rand_number>0.5:
            R[randNum2] = -1 * R[randNum2]
            mademove2 = True

        # find the new residue
        new_res = 0
        for i in range(length):
            new_res += R[i]*S[i]

        # if new residue is less, then update residue
        if abs(new_res) < abs(residue):
            residue = abs(new_res)

        # if new residue is more, reverse the changes that were just made
        else:
            R[randNum1] = -1 * R[randNum1]
            if madeMove2 == True:
                R[randNum2] = -1 * R[randNum2]

    return residue

def simulatedA(S):
    import random
    import math

    length = len(S)

    # Creating a array of +1 and -1 randomly of length length
    R = []
    residue = 0

    # determine the initial residue while generating random bits
    for i in range(length):
        rand_bit = random.choice([-1, +1])
        residue += S[i]*rand_bit
        R.append(rand_bit)

    # make sure residue is positive
    residue = abs(residue)

    # keep track of the R that corresponds to the minimum residue so far
    Rlowest = R

    for i in range(25000):
        randNum1 = random.randrange(0, length - 1)
        randNum2 = random.randrange(0, length - 1)

        # do a random move
        R[randNum1] = -1 * R[randNum1]
        rand_number = (random.randint(0, 10000000) % 100) / 100

        madeMove2 = False
        if rand_number>0.5:
            R[randNum2] = -1 * R[randNum2]
            mademove2 = True

        # find the new residue
        new_res = 0
        for i in range(length):
            new_res += R[i]*S[i]

        # if new residue is less, then update residue
        if abs(new_res) < residue:
            residue = abs(new_res)

        # if new residue is more, reverse the changes that were just made but have a probability that the change is kept
        else:
            diff_in_residues = abs(new_res) - residue
            prob = math.exp(-diff_in_residues/((10**10)*(0.8)**(i / 300)))
            randNum3 = (random.randint(0, 10000000) % 100) / 100
            if randNum3 > prob: # notice that there's a probability that the changes will not be reversed even if it's not a helpful move
                R[randNum1] = -1 * R[randNum1]
                if madeMove2 == True:
                    R[randNum2] = -1 * R[randNum2]

        if residue > abs(new_res):
            Rlowest = R
            residue = abs(new_res)

    return residue

# Given array S provided in the inputfile and n, the length of array S, create a random solution using a prepartition.
def create_rand_solution_from_prepartition(S):

    # First, I create a prepartititon with 100 random integers each ranging from 0 to 100.
    print(str(S)+ "i am S")
    P = []

    for i in range(100):
        rand_number = random.randrange(0, 100)
        P.append(rand_number)

    A_prime = [0]*100

    for i in range(100):
        index = P[i]
        A_prime[index] += S[i]
    return A_prime

# print(create_rand_solution_from_prepartition([10,8,7,6,5], 5))

def prepartitioned_repeated_random(S):
    R = create_rand_solution_from_prepartition(S)

    max_iter = 5
    for i in range(0, max_iter):
        print(str(i) + " max_iter")
        print("this is before andom solution")

        R_prime = create_rand_solution_from_prepartition(S)
        print("hi i finished")
        res_R = karmarkar(R)
        res_R_prime = karmarkar(R_prime)

        if res_R_prime < res_R:
            R = R_prime

    return res_R

# print(prepartitioned_repeated_random([10,8,7,6,5, 6,7,88,9,73]))

# the following function generates a set of 100 random integers each of
# which is in the range (1, 10^12)
def random_number_generator():
    arr = []

    for i in range(100):
        rand_number = random.randrange(1, 10**12)
        arr.append(rand_number)

    return arr

# Driver Code
if __name__ == "__main__":
    array = []
    with open(sys.argv[3], 'r') as file:
        dimension = int(sys.argv[2])
        for line in file.readlines():
            line = int(line)
            array.append(line)

        if int(sys.argv[2]) == 0:
            karmarkar(array)
            print(karmarkar(array))

        if int(sys.argv[2]) == 1:
            repeatedRandom(array)
            print(repeatedRandom(array))

        if int(sys.argv[2]) == 2:
            hillClimbing(array)
            print(hillClimbing(array))

        if int(sys.argv[2]) == 3:
            simulatedA(array)
            print(simulatedA(array))

        if int(sys.argv[2]) == 11:
            prepartitioned_repeated_random(array)

    # print(random_number_generator())

    # # maxHeap.Print()
    # # print("The Min val is " + str(maxHeap.remove()))

    # print(karmarkar([2,1,4,3,6,5]))


SUITS = ["heart", "diamond", "club", "spade"]
RANKS = ["2", "3", "4", "5", "6", "7", "8", "9", "10", "jack", "quine", "king", "ace"]
# PRINTED = {словарь с рубашками}

#from PIL import Image

#pict1 = Image.open('pika1.jpg')
#coord = (87,90,635,860)
#np = pict1.crop(coord)
#np.show()

MESSAGES = {
    'ask_start': 'Do you want play? y/n: '
}


l = []
d = []
npe = []
nle = []
c = pe = le = 0
while True:
    d.append(str(input('Nome: ')).strip().title())
    d.append(int(input('Peso: ')))
    l.append(d[:])
    d.clear()
    if c == 0:
        pe = le = l[0][1]
    k = str(input('Deseja continuar? [S/N]: ')).strip().upper()
    c += 1
    if k == 'N':
        break
for x in l:
    if x[1] > pe:
        pe = x[1]
    if x[1] < le:
        le = x[1]
for y in l:
    print(y)
    if y[1] == pe:
        npe.append(y[0])
        print(npe)
    if y[1] == le:
        nle.append(y[0])
print(f'{c} pessoas cadastradas')
print(f'O maior peso é {pe} de {npe}')
print(f'O menor peso é {le} de {nle}')


import xbmc
import xbmcgui
import thesportsdb
from utilities.addonfileio import FileIO
from utilities.common_addon import *

api = thesportsdb.Api("7723457519235")

class Select(xbmcgui.WindowXMLDialog):

	def __init__( self, *args, **kwargs ):
		if os.path.exists(ignored_league_list_file):
			self.already_ignored = eval(FileIO.fileread(ignored_league_list_file))
		else:
			self.already_ignored = []

	def onInit(self):
		self.getControl(1).setLabel(translate(32003))
		self.getControl(3).setVisible(False)
		leagues = api.Search().Leagues(sport="Soccer")
		ignored_through_context_menu = self.already_ignored
		if leagues:
			items = []
			for league in leagues:
				if removeNonAscii(league.strLeague) in self.already_ignored:
					item = xbmcgui.ListItem("[COLOR selected]" + league.strLeague + "[/COLOR]")
					item.setProperty("isIgnored","true")
					ignored_through_context_menu.remove(removeNonAscii(league.strLeague))
				else:
					item = xbmcgui.ListItem(league.strLeague)
					item.setProperty("isIgnored","false")
				item.setArt({"thumb":league.strBadge})
				items.append(item)

			#ignore the ones ignored through context menu
			if ignored_through_context_menu:
				for league_ign in ignored_through_context_menu:
					item = xbmcgui.ListItem("[COLOR selected]" + league_ign + "[/COLOR]")
					item.setProperty("isIgnored","true")
					item.setArt({"thumb":os.path.join(addon_path,"resources","img","nobadge_placeholder.png")})
					items.append(item)

			self.getControl(6).addItems(items)
			self.setFocusId(6)
			#Krypton
			if int(xbmc.getInfoLabel("System.BuildVersion")[0:2]) >= 17:
				self.getControl(5).setLabel(translate(32052))
				self.getControl(7).setLabel(translate(32053))

	def onAction(self,action):
		if action.getId() == 10 or action.getId() == 92:
			self.close()
		elif action.getId() == 1:
			self.setFocusId(6)

	def onClick(self,controlId):
		#list
		if controlId == 6:
			is_ignored = self.getControl(controlId).getSelectedItem().getProperty("isIgnored")
			league_name = self.getControl(controlId).getSelectedItem().getLabel().replace("[COLOR selected]","").replace("[/COLOR]","")
			if is_ignored == "false":
				self.getControl(controlId).getSelectedItem().setProperty("isIgnored","true")
				self.getControl(controlId).getSelectedItem().setLabel("[COLOR selected]" + league_name + "[/COLOR]")
			else:
				self.getControl(controlId).getSelectedItem().setProperty("isIgnored","false")
				self.getControl(controlId).getSelectedItem().setLabel(league_name)
		#ok
		elif controlId == 5:
			ignored_items = []
			total_items = self.getControl(6).size()
			for i in xrange(0,total_items):
				item = self.getControl(6).getListItem(i)
				if item.getProperty("isIgnored") == "true":
					ignored_items.append(removeNonAscii(item.getLabel().replace("[COLOR selected]","").replace("[/COLOR]","")))
					addon.setSetting("manually-ignored-leagues",addon.getSetting("manually-ignored-leagues").replace("<league>"+removeNonAscii(item.getLabel().replace("[COLOR selected]","").replace("[/COLOR]",""))+"</league>",""))

			FileIO.filewrite(ignored_league_list_file,str(ignored_items))
			self.close()
			xbmcgui.Dialog().ok(translate(32000),translate(32009))

		elif controlId == 7:
			self.close()

def start():
	ignoreWindow = Select(
		'DialogSelect.xml',
		addon_path,
		'default',
		'',
	)
	ignoreWindow.doModal()
	del ignoreWindow


from flask import Flask, render_template, request
from function.model1_ML import model1_predict, model1_data_preprocessing
from function.model2_ML import model2_predict, model2_data_preprocessing
from function.model1_dl import predict_dl, data_pre
from function.model2_dl import predict_dl2, data_pre2
from function.model3_dl import predict_dl3, data_pre3

# flask 객체 인스턴스 생성
app = Flask(__name__)

@app.route('/', methods=['GET'])
def home():
    return render_template('index.html')

@app.route('/model1', methods=['GET', 'POST'])
def model1():
    # model1에 대한 처리 로직을 구현합니다.
    if request.method == 'GET':
        return render_template('model1.html')

    if request.method == 'POST':
        sex = request.form['Sex']
        length = float(request.form['Length'])
        diameter = float(request.form['Diameter'])
        height = float(request.form['Height'])
        whole_weight = float(request.form['Whole_weight'])
        shucked_weight = float(request.form['Shucked_weight'])
        viscra_weight = float(request.form['Viscra_weight'])
        shell_weight = float(request.form['Shell_weight'])

        model = request.form['items1']

        if model == 'ML':
            data = model1_data_preprocessing([sex, length, diameter, height, whole_weight, shucked_weight, viscra_weight, shell_weight])
            results = model1_predict(data)
        elif model == 'DL':
            data = data_pre([sex, length, diameter, height, whole_weight, shucked_weight, viscra_weight, shell_weight, 0, 0, 0])
            results = predict_dl(data)
        return render_template('model1.html', results=results)

@app.route('/model2', methods=['GET', 'POST'])
def model2():
    # model2에 대한 처리 로직을 구현합니다.
    if request.method == 'GET':
        return render_template('model2.html')

    if request.method == 'POST':
        IP_mean = float(request.form['IP_mean'])
        IP_devi = float(request.form['IP_devi'])
        IP_kur = float(request.form['IP_kur'])
        IP_ske = float(request.form['IP_ske'])
        DM_mean = float(request.form['DM_mean'])
        DM_devi = float(request.form['DM_devi'])
        DM_kur = float(request.form['DM_kur'])
        DM_ske = float(request.form['DM_ske'])

        model = request.form['items1']

        if model == 'ML':
            data = model2_data_preprocessing([IP_mean, IP_devi, IP_kur, IP_ske, DM_mean, DM_devi, DM_kur, DM_ske])
            results = model2_predict(data)
        elif model == 'DL':
            data = data_pre2([IP_mean, IP_devi, IP_kur, IP_ske, DM_mean, DM_devi, DM_kur, DM_ske])
            results = predict_dl2(data)

        return render_template('model2.html', results=results)

@app.route('/model3', methods=['GET', 'POST'])
def model3():
    # model3에 대한 처리 로직을 구현합니다.
    if request.method == 'GET':
        return render_template('model3.html')

    if request.method == 'POST':
        cols = ['X_Minimum', 'X_Maximum', 'Y_Minimum', 'Y_Maximum', 'Pixels_Areas',
       'X_Perimeter', 'Y_Perimeter', 'Sum_of_Luminosity',
       'Minimum_of_Luminosity', 'Maximum_of_Luminosity', 'Length_of_Conveyer',
       'TypeOfSteel_A300', 'TypeOfSteel_A400', 'Steel_Plate_Thickness',
       'Edges_Index', 'Empty_Index', 'Square_Index', 'Outside_X_Index',
       'Edges_X_Index', 'Edges_Y_Index', 'Outside_Global_Index', 'LogOfAreas',
       'Log_X_Index', 'Log_Y_Index', 'Orientation_Index', 'Luminosity_Index',
       'SigmoidOfAreas']

        data = []
        for col in cols:
            data.append(float(request.form[col]))
        print(data)
        data = data_pre3(data)
        results = predict_dl3(data)
        print(results)

        return render_template('model3.html', results=results)

if __name__ == '__main__':
    app.run(debug=True)


# COMPSCI - Assignment 03
# Hayden Wies
# Program takes in a csv file representing votes collected from an election and uses an IRV algorythm to determine a winner to the election. Results are printed in order of elimination process meaning the last id to be listed in the result is the wining contender.

# P.S. I know I didn't incorporate all the defined algorithms seperately into this program as specified in the assignment, however I chose to do this because I found some of the required functions (such as determining the winner of a tie) could be done with a single line of code, which I found more efficient than writing a seperate function. Hopefully you still find everything straight forward and easy to read. Thank you for acknowledging this disclaimer.

def get_data():
    """Prompts user for csv file and creates a nested array of every line entry as the var 'data'. Returns data."""

    data = []

    # Prompt user for file name.
    file_name = input("Enter the name of the file: ")

    # Locate file, format and add to data variable. Formatted as array of numbers for each line in file.
    with open(file_name, "r") as votes:
        for line in votes:
            formatted_line = line.strip().split(",")
            formatted_line = list(filter(None, formatted_line))
            for i in range(len(formatted_line)):
                formatted_line[i] = int(formatted_line[i])
            data.append(formatted_line)

    return(data)

def remove_one(data, remove_order, removed):
    """Used to remove a single candidate id from the data array and return a new version. Parameters: data (candidate data), remove_order (ordered list of which sequence candidated were removed), remove (id of candidate to be removed from data). Returns updated data and remove_order."""

    # Add removed id to remove_order variable.
    remove_order.append(removed)

    # Loop through each entry of data and edit it to remove the specified id.
    for idx, vote in enumerate(data):
        data[idx] = list(filter(lambda x: x != removed, vote))

    return(data, remove_order)

def remove_all(data, remove_order, winner):
    """Used to remove all candidate ids from the data array except a specified id and return a new version. Parameters: data (candidate data), remove_order (ordered list of which sequence candidated were removed), winner (id of candidate to not be removed from data). Returns updated data and remove_order."""

    container = []

    # Loop through data and add id value to container if it doesn't equal specified winning id.
    for vote in data:
        for id in vote:
            if id not in container and id != winner:
                container.append(id)

    # Loop through each array in data and remove all numberes except for specified winner.
    for idx, vote in enumerate(data):
        data[idx] = list(filter(lambda x: x == winner, vote))

    # Append all removed ids to remove_order sorted in ascending order.
    remove_order += sorted(container)

    return(data, remove_order)

def compare_votes(data, remove_order):
    """Reconstructs data to identify most and least voted for candidate(s), then removes specified candidate(s) based on conditions. Parameters: data (candidate data), remove_order (ordered list of which sequence candidated were removed). Returns updated data and remove_order."""
    vote_tally = {}
    vote_percent = {}
    vote_total = 0

    # Create dictionary which keeps track of how many first place votes each id recieved.
    for vote in data:
        try:
            if vote[0] in vote_tally:
                vote_tally[vote[0]] += 1
            else:
                vote_tally[vote[0]] = 1
            vote_total += 1
        except IndexError:
            continue

    # Convert previous dictionary to percentages.
    for vote in vote_tally:
        vote_percent[vote] = vote_tally[vote] / vote_total

    # Flip percent array and group ids that have the same percent value.
    flipped_dict = {}
    for key, value in vote_percent.items():
        if value not in flipped_dict:
            flipped_dict[value] = [key]
        else:
            flipped_dict[value].append(key)

    # Retrieve min and max percentages and associated values.
    min_percent = min(flipped_dict)
    min_id = flipped_dict[min_percent]
    max_percent = max(flipped_dict)
    max_id = flipped_dict[max_percent]

    # If there is a winner by majority, remove all ids except winner.
    if max_percent > .5:
        winner = max_id[0]
        data, remove_order = remove_all(data, remove_order, winner)

    # If there is a tie, set candidate with highest id to be removed.
    elif len(min_id) > 1:
        removed = max(min_id)
        data, remove_order = remove_one(data, remove_order, removed)

    # Otherwise, remove candidate with least amount of first place votes.
    else:
        removed = min_id[0]
        data, remove_order = remove_one(data, remove_order, removed)

    return(data, remove_order)

def count_ids(data):
    """Used to count the number of ids/candidates remaining in given data. Parameters: data (candidate data). Returns intiger representing number of candidates left in data."""

    id_counter = []

    # Loop through data and add all unique ids to array.
    for vote in data:
        for id in vote:
            if id not in id_counter:
                id_counter.append(id)

    # Return size of array representing number of unique ids within data.
    return(len(id_counter))

def get_last(data):
    """Once certain only one id remains in data, used to retrieve that candidate id. Parameters: data (candidate data). Returns intiger representing the id of last candidate from provided date."""

    last_id = None

    # Loop through data and set last_id to the remaining id within the data set.
    for vote in data:
        if vote != []:
            last_id = vote[0]

    return(last_id)

def main():
    data = None
    remove_order = []

    # Prompt user for file name and retrieve data.
    data = get_data()

    # Run through elimination process until only one candidate remains.
    id_counter = None
    while id_counter != 1:
        data, remove_order = compare_votes(data, remove_order)
        id_counter = count_ids(data)

    # Add winner to end of remove_order array.
    remove_order.append(get_last(data))

    # Convert array to printable format and log results.
    converted_array = [str(element) for element in remove_order]
    converted_string = ", ".join(converted_array)
    print(f"Elimination order: {converted_string}")

main()


''' Implements an Example / Sample scheme for Ogawa. '''

from cerberus import Validator

# Cerberus compatible schema.
REQUEST_SCHEMA = {
    'key': {'type': 'string'},
    'value': {'type': 'string'}
}

def validate(message):
    ''' Confirm the provided message matches the expected schema. '''
    linter = Validator(REQUEST_SCHEMA)
    if not linter.validate(message):
        raise AttributeError(linter.errors)

def transform(message):
    ''' Apply any required transformations to input message. '''
    return message