class AutoCompleteEngine: """ AutoCompleteEngine generates suggestions given a users input. """ def __init__(self, magic_commands: Optional[Iterable[str]]): self._magics_args_suggesters: Dict[str, Callable] = {} self._commands_trie = CharTrie() if magic_commands is not None: for magic in magic_commands: self.add_magic_command(magic) def suggest(self, code: str, cursor_pos: int) -> Dict: """ @param code: string contains the current state of the user's input. It could be a CWL file or magic commands. @param cursor_pos: current position of cursor @return: {'matches': ['MATCH1', 'MATCH1'], 'cursor_end': , 'cursor_start': , } """ matches = [] cursor_end = cursor_pos cursor_start = cursor_pos line_classifier = re.compile( r'(?P<command>^%[ ]+[\w]*)(?P<args>( [\S]*)*)', re.MULTILINE) for match in line_classifier.finditer(code): # type: re.Match if match.start('command') <= cursor_pos <= match.end('command'): new_cursor_pos = cursor_pos - match.span()[0] code = match.group() matches, cursor_start, cursor_end = self._suggest_magic_command( code, new_cursor_pos) cursor_start += match.span()[0] cursor_end += match.span()[0] elif match.span()[0] <= cursor_pos <= match.span()[1]: new_cursor_pos = cursor_pos - match.start('args') code = match.group('args') command = match.group('command')[1:].strip() matches, cursor_start, cursor_end = self._suggest_magics_arguments( command, code, new_cursor_pos) cursor_start += match.start('args') cursor_end += match.start('args') return { 'matches': matches, 'cursor_end': cursor_end, 'cursor_start': cursor_start } def _suggest_magic_command(self, code: str, cursor_pos: int) -> Tuple[List[str], int, int]: cursor_end, cursor_start, token = self._parse_tokens(code, cursor_pos) if token == '%': token = '' try: matches = [ m for m in set(self._commands_trie.values(prefix=token)) ] matches.sort(key=len) except KeyError: matches = [] cursor_end = cursor_pos cursor_start = cursor_pos return matches, cursor_start, cursor_end def _suggest_magics_arguments( self, command: str, code: str, cursor_pos: int) -> Tuple[List[str], int, int]: """Stateless command's arguments suggester""" cursor_end, cursor_start, query_token = self._parse_tokens( code, cursor_pos) options: List[str] = self._magics_args_suggesters[command](query_token) return options, cursor_start, cursor_end def add_magic_commands_suggester(self, magic_name: str, suggester: Callable) -> None: self._magics_args_suggesters[magic_name] = suggester @classmethod def _parse_tokens(cls, code, cursor_pos): code_length = len(code) token_ends_at = code.find(" ", cursor_pos) cursor_end = min(token_ends_at + 1, code_length - 1) if token_ends_at == -1: token_ends_at = code_length - 1 cursor_end = code_length token_starts_at = code.rfind(" ", 0, cursor_pos) cursor_start = token_starts_at + 1 if token_starts_at == -1: token_starts_at = 0 cursor_start = cursor_pos token = code[token_starts_at:token_ends_at + 1].strip().upper() return cursor_end, cursor_start, token def add_magic_command(self, magic_command_name: str): for i in range(1, len(magic_command_name) + 1): self._commands_trie[ magic_command_name[-i:].upper()] = magic_command_name
class Save: def __init__(self, season='recent'): self.athlete_web = nx.DiGraph() self.athletes_by_name = CharTrie() self.athletes_by_id = {} self.athletes_by_index = [] self.race_history = set() self.athletes_considered = set() self.rankings = [] self.search_queue = [] self.season = season @classmethod def load(self, filename='my_save.bin'): with open(filename, 'rb') as file: s = pickle.load(file) return s def save(self, filename='my_save.bin'): with open(filename, 'wb') as file: pickle.dump(self, file) def update_athlete(self, athlete): if athlete.id in self: self[athlete.id].merge(athlete) else: self.athletes_by_id[athlete.id] = athlete self.athletes_by_name[athlete.name] = athlete self.athletes_by_index.append(athlete.id) self.athlete_web.add_node(athlete.id) def consider_athlete(self, a_ID): self.athletes_considered.add(a_ID) def add_race(self, race): self.race_history.add(race) def get_prefix_matches(self, name, count): if self.athletes_by_name.has_node(name): return tuple(map(lambda athlete: athlete.name + ': ' + str(athlete.id), \ self.athletes_by_name.values(prefix=name)[:count])) else: return () def get_ranking(self, id): return self.rankings.index(id) + 1 def update_graph(self): for race in self.race_history: surpassers = [] for runner_id in map(lambda x: x[0], race.results): for surpasser_id in surpassers: self[runner_id].lose() if self.athlete_web.has_edge(surpasser_id, runner_id): self.athlete_web[surpasser_id][runner_id]['count'] += 1 else: self.athlete_web.add_edge(surpasser_id, runner_id, count=1) surpassers.append(runner_id) for persons_defeated in map(lambda item: item[1], self.athlete_web.adj.items()): for person_defeated, connection in persons_defeated.items(): connection['weight'] = connection['count'] / self[ person_defeated].losses #takes athletes as starting points and dives into athletic.net. def import_data(self, num_races_to_add, athlete_id=None, progress_frame=None, backup_csv=None, \ focus_local=False, season='recent'): if athlete_id: self.search_queue.append(athlete_id) race_scraper.search_for_races(self, num_races_to_add, \ progress_frame=progress_frame, focus_local=focus_local, season=season) self.update_graph() self.update_rankings() if backup_csv: with open(backup_csv, 'r') as file: reader = csv.reader(file) athletes = set(map(tuple, reader)) with open(backup_csv, 'a') as file: writer = csv.writer(file, delimiter=',') for athlete in self.athletes_by_id.values(): if (athlete.name, str(athlete.id)) not in athletes: writer.writerow([athlete.name, athlete.id]) def update_rankings(self, precision=100): system = nx.to_numpy_array(self.athlete_web) rankings_by_index = self.get_rankings(system, precision=precision) score_pairs = [(self.athlete_at_index(pair[0]), pair[1]) for pair in enumerate(rankings_by_index)] score_pairs.sort(key=lambda x: -1 * x[1]) self.rankings = list(map(lambda x: x[0], score_pairs)) #We also assign an index elf.get_rankings(system)l athletes so we can reclaim them from a vector/matrix. def athlete_at_index(self, index): return self.athletes_by_index[index] def get_rankings(self, matrix, precision=100): current_scores = np.full(len(matrix), 1) for _ in range(precision): current_scores = np.matmul(matrix, current_scores) return current_scores #can subscript Save object using either athlete or athlete id for the same result. def __getitem__(self, request): if type(request) == int: return self.athletes_by_id[request] return self.athletes_by_name[request] def __contains__(self, request): if type(request) == int: return request in self.athletes_by_id return request in self.athletes_by_name def __len__(self): return len(self.athletes_by_index) def __repr__(self): return 'Save object containing ' + str(len(self)) + ' athletes.' def __str__(self): return ''.join([str(a[0] + 1) + '. ' + str(self.athletes_by_id[a[1]]) + '\n' for \ a in enumerate(self.rankings)])