Example #1
0
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)])