async def rating(self, ctx, *args: str): """Plots Codeforces rating graph for the handles provided.""" (zoom,), args = cf_common.filter_flags(args, ['+zoom']) handles = args or ('!' + str(ctx.author),) handles = await cf_common.resolve_handles(ctx, self.converter, handles) resp = [await cf.user.rating(handle=handle) for handle in handles] if not any(resp): handles_str = ', '.join(f'`{handle}`' for handle in handles) if len(handles) == 1: message = f'User {handles_str} is not rated' else: message = f'None of the given users {handles_str} are rated' raise GraphCogError(message) plt.clf() _plot_rating(resp) current_ratings = [rating_changes[-1].newRating if rating_changes else 'Unrated' for rating_changes in resp] labels = [f'\N{ZERO WIDTH SPACE}{handle} ({rating})' for handle, rating in zip(handles, current_ratings)] plt.legend(labels, loc='upper left') if not zoom: min_rating = 1100 max_rating = 1800 for rating_changes in resp: for rating in rating_changes: min_rating = min(min_rating, rating.newRating) max_rating = max(max_rating, rating.newRating) plt.ylim(min_rating - 100, max_rating + 200) discord_file = gc.get_current_figure_as_file() embed = discord_common.cf_color_embed(title='Rating graph on Codeforces') discord_common.attach_image(embed, discord_file) discord_common.set_author_footer(embed, ctx.author) await ctx.send(embed=embed, file=discord_file)
async def extreme(self, ctx, *args: str): """Plots pairs of lowest rated unsolved problem and highest rated solved problem for every contest that was rated for the given user. """ (solved, unsolved), args = cf_common.filter_flags(args, ['+solved', '+unsolved']) if not solved and not unsolved: solved = unsolved = True handles = args or ('!' + str(ctx.author),) handle, = await cf_common.resolve_handles(ctx, self.converter, handles) ratingchanges = await cf.user.rating(handle=handle) if not ratingchanges: raise GraphCogError(f'User {handle} is not rated') contest_ids = [change.contestId for change in ratingchanges] subs_by_contest_id = {contest_id: [] for contest_id in contest_ids} for sub in await cf.user.status(handle=handle): if sub.contestId in subs_by_contest_id: subs_by_contest_id[sub.contestId].append(sub) packed_contest_subs_problemset = [ (cf_common.cache2.contest_cache.get_contest(contest_id), cf_common.cache2.problemset_cache.get_problemset(contest_id), subs_by_contest_id[contest_id]) for contest_id in contest_ids ] rating = max(ratingchanges, key=lambda change: change.ratingUpdateTimeSeconds).newRating _plot_extreme(handle, rating, packed_contest_subs_problemset, solved, unsolved) discord_file = gc.get_current_figure_as_file() embed = discord_common.cf_color_embed(title='Codeforces extremes graph') discord_common.attach_image(embed, discord_file) discord_common.set_author_footer(embed, ctx.author) await ctx.send(embed=embed, file=discord_file)
async def stalk(self, ctx, *args): """Print problems solved by user sorted by time (default) or rating. All submission types are included by default (practice, contest, etc.) """ (hardest,), args = cf_common.filter_flags(args, ['+hardest']) filt = cf_common.SubFilter(False) args = filt.parse(args) handles = args or ('!' + str(ctx.author),) handles = await cf_common.resolve_handles(ctx, self.converter, handles) submissions = [await cf.user.status(handle=handle) for handle in handles] submissions = [sub for subs in submissions for sub in subs] submissions = filt.filter(submissions) if not submissions: raise CodeforcesCogError('Submissions not found within the search parameters') if hardest: submissions.sort(key=lambda sub: (sub.problem.rating or 0, sub.creationTimeSeconds), reverse=True) else: submissions.sort(key=lambda sub: sub.creationTimeSeconds, reverse=True) msg = '\n'.join( f'[{sub.problem.name}]({sub.problem.url})\N{EN SPACE}' f'[{sub.problem.rating if sub.problem.rating else "?"}]\N{EN SPACE}' f'({cf_common.days_ago(sub.creationTimeSeconds)})' for sub in submissions[:10] ) title = '{} solved problems by `{}`'.format('Hardest' if hardest else 'Recently', '`, `'.join(handles)) embed = discord_common.cf_color_embed(title=title, description=msg) await ctx.send(embed=embed)
async def rating(self, ctx, *args: str): """Plots Codeforces rating graph for the handles provided.""" (zoom, peak), args = cf_common.filter_flags(args, ['+zoom' , '+peak']) filt = cf_common.SubFilter() args = filt.parse(args) handles = args or ('!' + str(ctx.author),) handles = await cf_common.resolve_handles(ctx, self.converter, handles) resp = [await cf.user.rating(handle=handle) for handle in handles] resp = [filt.filter_rating_changes(rating_changes) for rating_changes in resp] if not any(resp): handles_str = ', '.join(f'`{handle}`' for handle in handles) if len(handles) == 1: message = f'User {handles_str} is not rated' else: message = f'None of the given users {handles_str} are rated' raise GraphCogError(message) def max_prefix(user): max_rate = 0 res = [] for data in user: old_rating = data.oldRating if old_rating == 0: old_rating = 1500 if data.newRating - old_rating >= 0 and data.newRating >= max_rate: max_rate = data.newRating res.append(data) return(res) if peak: resp = [max_prefix(user) for user in resp] plt.clf() plt.axes().set_prop_cycle(gc.rating_color_cycler) _plot_rating(resp) current_ratings = [rating_changes[-1].newRating if rating_changes else 'Unrated' for rating_changes in resp] labels = [gc.StrWrap(f'{handle} ({rating})') for handle, rating in zip(handles, current_ratings)] plt.legend(labels, loc='upper left') if not zoom: min_rating = 1100 max_rating = 1800 for rating_changes in resp: for rating in rating_changes: min_rating = min(min_rating, rating.newRating) max_rating = max(max_rating, rating.newRating) plt.ylim(min_rating - 100, max_rating + 200) discord_file = gc.get_current_figure_as_file() embed = discord_common.cf_color_embed(title='Rating graph on Codeforces') discord_common.attach_image(embed, discord_file) discord_common.set_author_footer(embed, ctx.author) await ctx.send(embed=embed, file=discord_file)
async def performance(self, ctx, *args: str): """Plots Codeforces performance graph for the handles provided.""" (zoom, peak), args = cf_common.filter_flags(args, ['+zoom', '+asdfgdsafefsdve']) filt = cf_common.SubFilter() args = filt.parse(args) handles = args or ('!' + str(ctx.author), ) handles = await cf_common.resolve_handles(ctx, self.converter, handles) resp = [await cf.user.rating(handle=handle) for handle in handles] # extract last rating before corrections current_ratings = [ rating_changes[-1].newRating if rating_changes else 'Unrated' for rating_changes in resp ] resp = cf.user.correct_rating_changes(resp=resp) resp = [ filt.filter_rating_changes(rating_changes) for rating_changes in resp ] if not any(resp): handles_str = ', '.join(f'`{handle}`' for handle in handles) if len(handles) == 1: message = f'User {handles_str} is not rated' else: message = f'None of the given users {handles_str} are rated' raise GraphCogError(message) plt.clf() plt.axes().set_prop_cycle(gc.rating_color_cycler) _plot_perf(resp) labels = [ gc.StrWrap(f'{handle} ({rating})') for handle, rating in zip(handles, current_ratings) ] plt.legend(labels, loc='upper left') if not zoom: min_rating = 1100 max_rating = 1800 for rating_changes in resp: for rating in rating_changes: min_rating = min(min_rating, rating.oldRating) max_rating = max(max_rating, rating.oldRating) plt.ylim(min_rating - 100, max_rating + 200) discord_file = gc.get_current_figure_as_file() embed = discord_common.cf_color_embed( title='Performance graph on Codeforces') discord_common.attach_image(embed, discord_file) discord_common.set_author_footer(embed, ctx.author) await ctx.send(embed=embed, file=discord_file)
async def stalk(self, ctx, *args): """Print problems solved by user sorted by time (default) or rating. All submission types are included by default (practice, contest, etc.) """ (hardest, ), args = cf_common.filter_flags(args, ['+hardest']) filt = cf_common.SubFilter(False) args = filt.parse(args) handles = args or ('!' + str(ctx.author), ) handles = await cf_common.resolve_handles(ctx, self.converter, handles) submissions = [ await cf.user.status(handle=handle) for handle in handles ] submissions = [sub for subs in submissions for sub in subs] submissions = filt.filter_subs(submissions) if not submissions: raise CodeforcesCogError( 'Submissions not found within the search parameters') if hardest: submissions.sort( key=lambda sub: (sub.problem.rating or 0, sub.creationTimeSeconds), reverse=True) else: submissions.sort(key=lambda sub: sub.creationTimeSeconds, reverse=True) def make_line(sub): data = (f'[{sub.problem.name}]({sub.problem.url})', f'[{sub.problem.rating if sub.problem.rating else "?"}]', f'({cf_common.days_ago(sub.creationTimeSeconds)})') return '\N{EN SPACE}'.join(data) def make_page(chunk): title = '{} solved problems by `{}`'.format( 'Hardest' if hardest else 'Recently', '`, `'.join(handles)) hist_str = '\n'.join(make_line(sub) for sub in chunk) embed = discord_common.cf_color_embed(description=hist_str) return title, embed pages = [ make_page(chunk) for chunk in paginator.chunkify(submissions[:100], 10) ] paginator.paginate(self.bot, ctx.channel, pages, wait_time=5 * 60, set_pagenum_footers=True)
async def rating(self, ctx, *args: str): """Plots Codeforces rating graph for the handles provided.""" (zoom, ), args = cf_common.filter_flags(args, ["+zoom"]) filt = cf_common.SubFilter() args = filt.parse(args) args = _mention_to_handle(args, ctx) handles = args or ("!" + str(ctx.author), ) handles = await cf_common.resolve_handles(ctx, self.converter, handles) resp = [await cf.user.rating(handle=handle) for handle in handles] resp = [ filt.filter_rating_changes(rating_changes) for rating_changes in resp ] if not any(resp): handles_str = ", ".join(f"`{handle}`" for handle in handles) if len(handles) == 1: message = f"User {handles_str} is not rated" else: message = f"None of the given users {handles_str} are rated" raise GraphCogError(message) plt.clf() _plot_rating(resp) current_ratings = [ rating_changes[-1].newRating if rating_changes else "Unrated" for rating_changes in resp ] labels = [ gc.StrWrap(f"{handle} ({rating})") for handle, rating in zip(handles, current_ratings) ] plt.legend(labels, loc="upper left") if not zoom: min_rating = 1100 max_rating = 1800 for rating_changes in resp: for rating in rating_changes: min_rating = min(min_rating, rating.newRating) max_rating = max(max_rating, rating.newRating) plt.ylim(min_rating - 100, max_rating + 200) discord_file = gc.get_current_figure_as_file() embed = discord_common.cf_color_embed( title="Rating graph on Codeforces") discord_common.attach_image(embed, discord_file) discord_common.set_author_footer(embed, ctx.author) await ctx.send(embed=embed, file=discord_file)
async def visualrank(self, ctx, contest_id: int, *args: str): """Plot rating changes by rank. Add handles to specify a handle in the plot. if arguments contains `+server`, it will include just server members and not all codeforces users. Specify `+zoom` to zoom to the neighborhood of handles.""" args = set(args) (in_server, zoom), handles = cf_common.filter_flags(args, ['+server', '+zoom']) handles = await cf_common.resolve_handles(ctx, self.converter, handles, mincnt=0, maxcnt=20) rating_changes = await cf.contest.ratingChanges(contest_id=contest_id) if in_server: guild_handles = set(handle for discord_id, handle in cf_common.user_db.get_handles_for_guild(ctx.guild.id)) rating_changes = [rating_change for rating_change in rating_changes if rating_change.handle in guild_handles or rating_change.handle in handles] if not rating_changes: raise GraphCogError(f'No rating changes for contest `{contest_id}`') users_to_mark = {} for rating_change in rating_changes: user_delta = rating_change.newRating - rating_change.oldRating if rating_change.handle in handles: users_to_mark[rating_change.handle] = (rating_change.rank, user_delta) ymargin = 50 xmargin = 50 if users_to_mark and zoom: xmin = min(point[0] for point in users_to_mark.values()) xmax = max(point[0] for point in users_to_mark.values()) ymin = min(point[1] for point in users_to_mark.values()) ymax = max(point[1] for point in users_to_mark.values()) else: ylim = 0 if users_to_mark: ylim = max(abs(point[1]) for point in users_to_mark.values()) ylim = max(ylim, 200) xmin = 0 xmax = max(rating_change.rank for rating_change in rating_changes) ymin = -ylim ymax = ylim ranks = [] delta = [] color = [] for rating_change in rating_changes: user_delta = rating_change.newRating - rating_change.oldRating if (xmin - xmargin <= rating_change.rank <= xmax + xmargin and ymin - ymargin <= user_delta <= ymax + ymargin): ranks.append(rating_change.rank) delta.append(user_delta) color.append(cf.rating2rank(rating_change.oldRating).color_graph) title = rating_changes[0].contestName plt.clf() fig = plt.figure(figsize=(12, 8)) plt.title(title) plt.xlabel('Rank') plt.ylabel('Rating Changes') mark_size = 2e4 / len(ranks) plt.xlim(xmin - xmargin, xmax + xmargin) plt.ylim(ymin - ymargin, ymax + ymargin) plt.scatter(ranks, delta, s=mark_size, c=color) for handle, point in users_to_mark.items(): plt.annotate(handle, xy=point, xytext=(0, 0), textcoords='offset points', ha='left', va='bottom', fontsize='large') plt.plot(*point, marker='o', markersize=5, color='black') discord_file = gc.get_current_figure_as_file() plt.close(fig) embed = discord_common.cf_color_embed(title=title) discord_common.attach_image(embed, discord_file) discord_common.set_author_footer(embed, ctx.author) await ctx.send(embed=embed, file=discord_file)
async def centile(self, ctx, *args: str): """Show percentile distribution of codeforces and mark given handles in the plot. If +zoom and handles are given, it zooms to the neighborhood of the handles.""" (zoom, nomarker), args = cf_common.filter_flags(args, ['+zoom', '+nomarker']) # Prepare data intervals = [(rank.low, rank.high) for rank in cf.RATED_RANKS] colors = [rank.color_graph for rank in cf.RATED_RANKS] ratings = cf_common.cache2.rating_changes_cache.get_all_ratings() ratings = np.array(sorted(ratings)) n = len(ratings) perc = 100*np.arange(n)/n users_to_mark = {} if not nomarker: handles = args or ('!' + str(ctx.author),) handles = await cf_common.resolve_handles(ctx, self.converter, handles, mincnt=0, maxcnt=50) infos = await cf.user.info(handles=list(set(handles))) for info in infos: if info.rating is None: raise GraphCogError(f'User `{info.handle}` is not rated') ix = bisect.bisect_left(ratings, info.rating) cent = 100*ix/len(ratings) users_to_mark[info.handle] = info.rating,cent # Plot plt.clf() fig,ax = plt.subplots(1) ax.plot(ratings, perc, color='#00000099') plt.xlabel('Rating') plt.ylabel('Percentile') for pos in ['right','top','bottom','left']: ax.spines[pos].set_visible(False) ax.tick_params(axis='both', which='both',length=0) # Color intervals by rank for interval,color in zip(intervals,colors): alpha = '99' l,r = interval col = color + alpha rect = patches.Rectangle((l,-50), r-l, 200, edgecolor='none', facecolor=col) ax.add_patch(rect) # Mark users in plot for user,point in users_to_mark.items(): x,y = point plt.annotate(user, xy=point, xytext=(0, 0), textcoords='offset points', ha='right', va='bottom') plt.plot(*point, marker='o', markersize=5, color='red', markeredgecolor='darkred') # Set limits (before drawing tick lines) if users_to_mark and zoom: xmargin = 50 ymargin = 5 xmin = min(point[0] for point in users_to_mark.values()) xmax = max(point[0] for point in users_to_mark.values()) ymin = min(point[1] for point in users_to_mark.values()) ymax = max(point[1] for point in users_to_mark.values()) plt.xlim(xmin - xmargin, xmax + xmargin) plt.ylim(ymin - ymargin, ymax + ymargin) else: plt.xlim(ratings[0], ratings[-1]) plt.ylim(-1.5, 101.5) # Draw tick lines linecolor = '#00000022' inf = 10000 def horz_line(y): l = mlines.Line2D([-inf,inf], [y,y], color=linecolor) ax.add_line(l) def vert_line(x): l = mlines.Line2D([x,x], [-inf,inf], color=linecolor) ax.add_line(l) for y in ax.get_yticks(): horz_line(y) for x in ax.get_xticks(): vert_line(x) # Discord stuff discord_file = gc.get_current_figure_as_file() embed = discord_common.cf_color_embed(title=f'Rating/percentile relationship') discord_common.attach_image(embed, discord_file) discord_common.set_author_footer(embed, ctx.author) await ctx.send(embed=embed, file=discord_file)
async def teamrate(self, ctx, *args: str): """Provides the combined rating of the entire team. If +server is provided as the only handle, will display the rating of the entire server. Supports multipliers. e.g: ;teamrate gamegame*1000""" (is_entire_server, peak), handles = cf_common.filter_flags(args, ['+server', '+peak']) handles = handles or ('!' + str(ctx.author), ) def rating(user): return user.maxRating if peak else user.rating if is_entire_server: res = cf_common.user_db.get_cf_users_for_guild(ctx.guild.id) ratings = [(rating(user), 1) for user_id, user in res if user.rating is not None] user_str = '+server' else: def normalize(x): return [i.lower() for i in x] handle_counts = {} parsed_handles = [] for i in handles: parse_str = normalize(i.split('*')) if len(parse_str) > 1: try: handle_counts[parse_str[0]] = int(parse_str[1]) except ValueError: raise CodeforcesCogError( "Can't multiply by non-integer") else: handle_counts[parse_str[0]] = 1 parsed_handles.append(parse_str[0]) cf_handles = await cf_common.resolve_handles(ctx, self.converter, parsed_handles, mincnt=1, maxcnt=1000) cf_handles = normalize(cf_handles) cf_to_original = {a: b for a, b in zip(cf_handles, parsed_handles)} original_to_cf = {a: b for a, b in zip(parsed_handles, cf_handles)} users = await cf.user.info(handles=cf_handles) user_strs = [] for a, b in handle_counts.items(): if b > 1: user_strs.append(f'{original_to_cf[a]}*{b}') elif b == 1: user_strs.append(original_to_cf[a]) elif b <= 0: raise CodeforcesCogError( 'How can you have nonpositive members in team?') user_str = ', '.join(user_strs) ratings = [(rating(user), handle_counts[cf_to_original[user.handle.lower()]]) for user in users if user.rating] if len(ratings) == 0: raise CodeforcesCogError("No CF usernames with ratings passed in.") left = -100.0 right = 10000.0 teamRating = Codeforces.composeRatings(left, right, ratings) embed = discord.Embed(title=user_str, description=teamRating, color=cf.rating2rank(teamRating).color_embed) await ctx.send(embed=embed)
async def scatter(self, ctx, *args): """Plot Codeforces rating overlaid on a scatter plot of problems solved. Also plots a running average of ratings of problems solved in practice.""" (nolegend, ), args = cf_common.filter_flags(args, ['+nolegend']) legend, = cf_common.negate_flags(nolegend) filt = cf_common.SubFilter() args = filt.parse(args) handle, bin_size, point_size = None, 10, 3 for arg in args: if arg[0:2] == 'b=': bin_size = int(arg[2:]) elif arg[0:2] == 's=': point_size = int(arg[2:]) else: if handle: raise GraphCogError('Only one handle allowed.') handle = arg if bin_size < 1 or point_size < 1 or point_size > 100: raise GraphCogError('Invalid parameters') handle = handle or '!' + str(ctx.author) handle, = await cf_common.resolve_handles(ctx, self.converter, (handle, )) rating_resp = [await cf.user.rating(handle=handle)] rating_resp = [ filt.filter_rating_changes(rating_changes) for rating_changes in rating_resp ] submissions = filt.filter_subs(await cf.user.status(handle=handle)) def extract_time_and_rating(submissions): return [(dt.datetime.fromtimestamp(sub.creationTimeSeconds), sub.problem.rating) for sub in submissions] if not any(submissions): raise GraphCogError(f'No submissions for user `{handle}`') solved_by_type = _classify_submissions(submissions) regular = extract_time_and_rating(solved_by_type['CONTESTANT'] + solved_by_type['OUT_OF_COMPETITION']) practice = extract_time_and_rating(solved_by_type['PRACTICE']) virtual = extract_time_and_rating(solved_by_type['VIRTUAL']) plt.clf() _plot_scatter(regular, practice, virtual, point_size) labels = [] if practice: labels.append('Practice') if regular: labels.append('Regular') if virtual: labels.append('Virtual') if legend: plt.legend(labels, loc='upper left') _plot_average(practice, bin_size) _plot_rating(rating_resp, mark='') # zoom ymin, ymax = plt.gca().get_ylim() plt.ylim(max(ymin, filt.rlo - 100), min(ymax, filt.rhi + 100)) discord_file = gc.get_current_figure_as_file() embed = discord_common.cf_color_embed( title=f'Rating vs solved problem rating for {handle}') discord_common.attach_image(embed, discord_file) discord_common.set_author_footer(embed, ctx.author) await ctx.send(embed=embed, file=discord_file)
async def visualrank(self, ctx, contest_id: int, *args: str): """Plot rating changes by rank. Add handles to specify a handle in the plot. if arguments contains `+server`, it will include just server members and not all codeforces users. Specify `+zoom` to zoom to the neighborhood of handles.""" args = set(args) (in_server, zoom), handles = cf_common.filter_flags(args, ["+server", "+zoom"]) handles = await cf_common.resolve_handles(ctx, self.converter, handles, mincnt=0, maxcnt=20) users = cf_common.cache2.rating_changes_cache.get_rating_changes_for_contest( contest_id) if not users: raise GraphCogError( f"No rating change cache for contest `{contest_id}`") if in_server: guild_handles = [ handle for discord_id, handle in cf_common.user_db.get_handles_for_guild(ctx.guild.id) ] users = [user for user in users if user.handle in guild_handles] ranks = [] delta = [] color = [] users_to_mark = dict() for user in users: user_delta = user.newRating - user.oldRating ranks.append(user.rank) delta.append(user_delta) color.append(cf.rating2rank(user.oldRating).color_graph) if user.handle in handles: users_to_mark[user.handle] = (user.rank, user_delta) title = users[0].contestName plt.clf() fig = plt.figure(figsize=(12, 8)) plt.title(title) plt.xlabel("Rank") plt.ylabel("Rating Changes") ymargin = 50 xmargin = 50 if users_to_mark and zoom: xmin = min(point[0] for point in users_to_mark.values()) xmax = max(point[0] for point in users_to_mark.values()) ymin = min(point[1] for point in users_to_mark.values()) ymax = max(point[1] for point in users_to_mark.values()) mark_size = 2e4 / (xmax - xmin + 2 * xmargin) plt.xlim(xmin - xmargin, xmax + xmargin) plt.ylim(ymin - ymargin, ymax + ymargin) else: ylim = 0 if users_to_mark: ylim = max(abs(point[1]) for point in users_to_mark.values()) ylim = max(ylim, 200) xmax = max(user.rank for user in users) mark_size = 2e4 / (xmax + 2 * xmargin) plt.xlim(-xmargin, xmax + xmargin) plt.ylim(-ylim - ymargin, ylim + ymargin) plt.scatter(ranks, delta, s=mark_size, c=color) for handle, point in users_to_mark.items(): plt.annotate( handle, xy=point, xytext=(0, 0), textcoords="offset points", ha="left", va="bottom", fontsize="large", ) plt.plot(*point, marker="o", markersize=5, color="black") discord_file = gc.get_current_figure_as_file() plt.close(fig) embed = discord_common.cf_color_embed(title=title) discord_common.attach_image(embed, discord_file) discord_common.set_author_footer(embed, ctx.author) await ctx.send(embed=embed, file=discord_file)
async def speed(self, ctx, *args): """Plot time spent on problems of particular rating during contest.""" (add_scatter, use_median), args = cf_common.filter_flags(args, ['+scatter', '+median']) filt = cf_common.SubFilter() args = filt.parse(args) if 'PRACTICE' in filt.types: filt.types.remove( 'PRACTICE') # can't estimate time for practice submissions handles, point_size = [], 3 for arg in args: if arg[0:2] == 's=': point_size = int(arg[2:]) else: handles.append(arg) handles = handles or ['!' + str(ctx.author)] handles = await cf_common.resolve_handles(ctx, self.converter, handles) resp = [await cf.user.status(handle=handle) for handle in handles] all_solved_subs = [ filt.filter_subs(submissions) for submissions in resp ] plt.clf() plt.xlabel('Rating') plt.ylabel('Minutes spent') max_time = 0 # for ylim for submissions in all_solved_subs: scatter_points = [] # only matters if +scatter solved_by_contest = collections.defaultdict(lambda: []) for submission in submissions: # [solve_time, problem rating, problem index] for each solved problem solved_by_contest[submission.contestId].append([ submission.relativeTimeSeconds, submission.problem.rating, submission.problem.index ]) time_by_rating = collections.defaultdict(lambda: []) for events in solved_by_contest.values(): events.sort() solved_subproblems = dict() last_ac_time = 0 for (current_ac_time, rating, problem_index) in events: time_to_solve = current_ac_time - last_ac_time last_ac_time = current_ac_time # if there are subproblems, add total time for previous subproblems to current one if len(problem_index) == 2 and problem_index[1].isdigit(): time_to_solve += solved_subproblems.get( problem_index[0], 0) solved_subproblems[problem_index[0]] = time_to_solve time_by_rating[rating].append(time_to_solve / 60) # in minutes for rating in time_by_rating.keys(): times = time_by_rating[rating] if use_median: time_by_rating[rating] = np.median(times) else: time_by_rating[rating] = sum(times) / len(times) if add_scatter: for t in times: scatter_points.append([rating, t]) max_time = max(max_time, t) xs = sorted(time_by_rating.keys()) ys = [time_by_rating[rating] for rating in xs] max_time = max(max_time, max(ys, default=0)) plt.plot(xs, ys) if add_scatter: plt.scatter(*zip(*scatter_points), s=point_size) labels = [gc.StrWrap(handle) for handle in handles] plt.legend(labels) plt.ylim(0, max_time + 5) # make xticks divisible by 100 ticks = plt.gca().get_xticks() base = ticks[1] - ticks[0] plt.gca().get_xaxis().set_major_locator( MultipleLocator(base=max(base // 100 * 100, 100))) discord_file = gc.get_current_figure_as_file() title = f'Plot of {"median" if use_median else "average"} time spent on a problem' embed = discord_common.cf_color_embed(title=title) discord_common.attach_image(embed, discord_file) discord_common.set_author_footer(embed, ctx.author) await ctx.send(embed=embed, file=discord_file)