Created
January 26, 2018 16:52
-
-
Save ldacosta/10578211aac9534e3bfb852d026602a8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import itertools | |
from abc import abstractmethod | |
from typing import List, Set, Callable, Tuple, Dict | |
from enum import Enum, auto | |
class PlayerType(Enum): | |
DEFENSIVE = auto() | |
OFFENSIVE = auto() | |
NEUTRAL = auto() | |
# ########################################################################################### | |
# ########################################################################################### | |
# ########################################################################################### | |
class QValuesFetcher(object): | |
def lines_to_index(home_line: List[PlayerType], away_line: List[PlayerType]) -> str: | |
return ''.join(list(map(str, home_line + away_line))) | |
def __init__(self): | |
pass | |
@abstractmethod | |
def get(self, home_line: List[PlayerType], away_line: List[PlayerType]) -> float: | |
raise NotImplementedError | |
class QValuesFetcherFromDict(QValuesFetcher): | |
def __init__(self, a_dict: Dict): | |
QValuesFetcher.__init__(self) | |
self.dict = a_dict | |
@classmethod | |
def from_tuples(cls, q_value_tuples: List[Tuple[List[PlayerType], List[PlayerType], float]]): | |
q_value_dict = {} | |
for v in q_value_tuples: | |
home_line, away_line, q_value = v | |
for home_line_comb in list(set(itertools.permutations(home_line))): | |
for away_line_comb in list(set(itertools.permutations(away_line))): | |
q_value_dict[QValuesFetcher.lines_to_index(home_line_comb, away_line_comb)] = q_value | |
print("Q value dictionary has %d entries" % (len(q_value_dict))) | |
return cls(a_dict=q_value_dict) | |
def get(self, home_line: Set[PlayerType], away_line: Set[PlayerType]) -> float: | |
return self.dict[QValuesFetcher.lines_to_index(list(home_line), list(away_line))] | |
# ########################################################################################### | |
# ########################################################################################### | |
# ########################################################################################### | |
# ########################################################################################### | |
# ########################################################################################### | |
# ########################################################################################### | |
class CategoryFetcher(object): | |
def __init__(self): | |
pass | |
def category_of_player(self, player_id: int) -> PlayerType: | |
if player_id < 20: | |
return PlayerType.OFFENSIVE | |
else: | |
return PlayerType.NEUTRAL | |
# ########################################################################################### | |
# ########################################################################################### | |
# ########################################################################################### | |
# ########################################################################################### | |
# ########################################################################################### | |
# ########################################################################################### | |
class LineRecommender(object): | |
def __init__(self, player_category_fetcher: CategoryFetcher, q_values_fetcher: QValuesFetcher): | |
self.player_category_fetcher=player_category_fetcher | |
self.q_values_fetcher=q_values_fetcher | |
def recommend_lines( | |
self, | |
home_team_players_ids: Set[int], | |
away_team_lines: List[List[PlayerType]], | |
comb_q_values: Callable[[List[float]], float]) -> List[List[int]]: | |
""" | |
Builds optimal lines for home team. | |
Args: | |
home_team_players_ids: a list of id's for home team. | |
away_team_lines: 4 lines of away team. Each line is composed of 3 players, represented by their class (0 = defensive, 2 = offensive, 1 = neither) | |
q_values: a function that given 2 lines (represented by CLASSES of players) returns the q-value of the home team line. | |
comb_q_values: how to combine the q-values in order to obtain a "fitness" for a formation (larger is better). | |
Returns: | |
""" | |
import datetime | |
assert len(home_team_players_ids) == len(set(home_team_players_ids)), "There are repeated ids in the home team" | |
assert len(away_team_lines) == 4, "I need a formation (ie, 4 lines) for away team" | |
away_line_1, away_line_2, away_line_3, away_line_4 = away_team_lines | |
best_fitness = None | |
best_formation = None | |
how_many_first_lines_tried = 0 | |
# | |
entry_timestamp = datetime.datetime.now().timestamp() | |
for home_line_1_ids in itertools.combinations(home_team_players_ids, 3): | |
best_formation_found = False | |
for home_line_2_ids in itertools.combinations(home_team_players_ids - set(home_line_1_ids), 3): | |
for home_line_3_ids in itertools.combinations( | |
home_team_players_ids - set(home_line_1_ids) - set(home_line_2_ids), 3): | |
for home_line_4_ids in itertools.combinations( | |
home_team_players_ids - set(home_line_1_ids) - set(home_line_2_ids) - set(home_line_3_ids), 3): | |
home_formation = [home_line_1_ids, home_line_2_ids, home_line_3_ids, home_line_4_ids] | |
# print(home_formation) | |
# get lines with CATEGORY of players | |
home_line_1 = list(map(self.player_category_fetcher.category_of_player, home_line_1_ids)) | |
home_line_2 = list(map(self.player_category_fetcher.category_of_player, home_line_2_ids)) | |
home_line_3 = list(map(self.player_category_fetcher.category_of_player, home_line_3_ids)) | |
home_line_4 = list(map(self.player_category_fetcher.category_of_player, home_line_4_ids)) | |
# ok, then: | |
qs = [self.q_values_fetcher.get(home_line_1, away_line_1), | |
self.q_values_fetcher.get(home_line_2, away_line_2), | |
self.q_values_fetcher.get(home_line_3, away_line_3), | |
self.q_values_fetcher.get(home_line_4, away_line_4)] | |
fitness = comb_q_values(qs) | |
# print(qs) | |
if (best_fitness is None) or (fitness > best_fitness): | |
best_fitness = fitness | |
best_formation = home_formation | |
best_formation_found = True | |
print("Best fitness: %.2f by formation %s" % (best_fitness, best_formation)) | |
how_many_first_lines_tried += 1 | |
if best_formation_found: | |
time_it_took = datetime.datetime.now().timestamp() - entry_timestamp | |
time_per_cycle = time_it_took / how_many_first_lines_tried | |
print("=======> Took %.2f secs. to look at %d first-lines; I think we have around %.2f secs. to go" % ( | |
time_it_took, how_many_first_lines_tried, (220 - how_many_first_lines_tried) * time_per_cycle)) | |
print("ALL DONE!!!!!!") | |
print("================================") | |
print("Best fitness: %.2f by formation \n%s, to play against \n%s" % (best_fitness, best_formation, away_team_lines)) | |
print("================================") | |
return best_formation | |
def recommend_lines_maximize_average( | |
self, | |
home_team_players_ids: Set[int], | |
away_team_lines: List[List[PlayerType]]) -> List[List[int]]: | |
return self.recommend_lines(home_team_players_ids, away_team_lines, comb_q_values=(lambda a_list: sum(a_list) / len(a_list))) | |
def recommend_lines_maximize_max( | |
self, | |
home_team_players_ids: Set[int], | |
away_team_lines: List[List[PlayerType]]) -> List[List[int]]: | |
return self.recommend_lines(home_team_players_ids, away_team_lines, comb_q_values=(lambda a_list: max(a_list))) | |
# ########################################################################################### | |
# ########################################################################################### | |
# ########################################################################################### | |
# ############################################################ | |
# PUT ALL THE FOLLOWING INTO TESTING | |
q_value_tuples = [ | |
([PlayerType.NEUTRAL, PlayerType.NEUTRAL, PlayerType.OFFENSIVE], | |
[PlayerType.OFFENSIVE, PlayerType.DEFENSIVE, PlayerType.DEFENSIVE], | |
25), | |
([PlayerType.NEUTRAL, PlayerType.NEUTRAL, PlayerType.OFFENSIVE], | |
[PlayerType.NEUTRAL, PlayerType.NEUTRAL, PlayerType.NEUTRAL], | |
12), | |
([PlayerType.NEUTRAL, PlayerType.NEUTRAL, PlayerType.NEUTRAL], | |
[PlayerType.OFFENSIVE, PlayerType.DEFENSIVE, PlayerType.DEFENSIVE], | |
20), | |
([PlayerType.NEUTRAL, PlayerType.NEUTRAL, PlayerType.NEUTRAL], | |
[PlayerType.NEUTRAL, PlayerType.NEUTRAL, PlayerType.NEUTRAL], | |
1), | |
] | |
line_rec = LineRecommender( | |
player_category_fetcher=CategoryFetcher(), | |
q_values_fetcher=QValuesFetcherFromDict.from_tuples(q_value_tuples)) | |
# home_lines_rec = line_rec.recommend_lines(home_team_players_ids=set([1] + list(range(20, 31))), | |
# away_team_lines = [ | |
# [PlayerType.OFFENSIVE, PlayerType.DEFENSIVE, PlayerType.DEFENSIVE], | |
# [PlayerType.NEUTRAL, PlayerType.NEUTRAL, PlayerType.NEUTRAL], | |
# [PlayerType.NEUTRAL, PlayerType.NEUTRAL, PlayerType.NEUTRAL], | |
# [PlayerType.NEUTRAL, PlayerType.NEUTRAL, PlayerType.NEUTRAL], | |
# ], | |
# comb_q_values=(lambda a_list: sum(a_list) / len(a_list))) | |
# print(home_lines_rec) | |
# | |
# ### | |
home_lines_rec = line_rec.recommend_lines_maximize_average(home_team_players_ids=set([1] + list(range(20, 31))), | |
away_team_lines = [ | |
[PlayerType.OFFENSIVE, PlayerType.DEFENSIVE, PlayerType.DEFENSIVE], | |
[PlayerType.NEUTRAL, PlayerType.NEUTRAL, PlayerType.NEUTRAL], | |
[PlayerType.NEUTRAL, PlayerType.NEUTRAL, PlayerType.NEUTRAL], | |
[PlayerType.NEUTRAL, PlayerType.NEUTRAL, PlayerType.NEUTRAL], | |
]) | |
print(home_lines_rec) | |
# ### | |
home_lines_rec = line_rec.recommend_lines_maximize_max(home_team_players_ids=set([1] + list(range(20, 31))), | |
away_team_lines = [ | |
[PlayerType.OFFENSIVE, PlayerType.DEFENSIVE, PlayerType.DEFENSIVE], | |
[PlayerType.NEUTRAL, PlayerType.NEUTRAL, PlayerType.NEUTRAL], | |
[PlayerType.NEUTRAL, PlayerType.NEUTRAL, PlayerType.NEUTRAL], | |
[PlayerType.NEUTRAL, PlayerType.NEUTRAL, PlayerType.NEUTRAL], | |
]) | |
print(home_lines_rec) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment