leetcode/solution/rating.py

165 lines
6.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from string import Template
import requests
import urllib3
urllib3.disable_warnings()
class Ranking:
def __init__(self, region='CN', retry=3):
self.retry = retry
self.region = region
if region == 'CN':
self.url = 'https://leetcode.cn/graphql'
self.page_query = Template(
"{\n localRankingV2(page:$page) {\nmyRank {\nattendedContestCount\n"
"currentRatingRanking\ndataRegion\nisDeleted\nuser {\nrealName\n"
"userAvatar\nuserSlug\n__typename\n}\n__typename\n}\npage\ntotalUsers\n"
"userPerPage\nrankingNodes {\nattendedContestCount\ncurrentRatingRanking\n"
"dataRegion\nisDeleted\nuser {\nrealName\nuserAvatar\nuserSlug\n__typename\n}\n__"
"typename\n}\n__typename\n }\n}\n"
)
else:
self.url = 'https://leetcode.com/graphql'
self.page_query = Template(
"{\nglobalRanking(page:$page){\ntotalUsers\nuserPerPage\nmyRank{\nranking\n"
"currentGlobalRanking\ncurrentRating\ndataRegion\nuser{\nnameColor\nactiveBadge{\n"
"displayName\nicon\n__typename\n}\n__typename\n}\n__typename\n}\nrankingNodes{\n"
"ranking\ncurrentRating\ncurrentGlobalRanking\ndataRegion\nuser{\nusername\nnameColor\n"
"activeBadge{\ndisplayName\nicon\n__typename\n}\nprofile{\nuserAvatar\ncountryCode\n"
"countryName\nrealName\n__typename\n}\n__typename\n}\n__typename\n}\n__typename\n}\n}\n"
)
def load_page(self, page):
query = self.page_query.substitute(page=page)
for _ in range(self.retry):
resp = requests.post(url=self.url, json={'query': query}, verify=False)
if resp.status_code != 200:
continue
nodes = resp.json()['data'][
'localRankingV2' if self.region == 'CN' else 'globalRanking'
]['rankingNodes']
if self.region == 'CN':
return [
(int(v['currentRatingRanking']), v['user']['userSlug'])
for v in nodes
]
else:
return [
(int(v['currentGlobalRanking']), v['user']['username'])
for v in nodes
]
return []
def _user_ranking(self, region, uid):
if region == 'CN':
key = 'userSlug'
url = 'https://leetcode.cn/graphql/noj-go/'
query = (
"\nquery userContestRankingInfo($userSlug:String!){\nuserContestRanking"
"(userSlug:$userSlug){\nattendedContestsCount\nrating\nglobalRanking\nlocalRanking\n"
"globalTotalParticipants\nlocalTotalParticipants\ntopPercentage\n}\n"
"userContestRankingHistory(userSlug:$userSlug){\nattended\ntotalProblems\n"
"trendingDirection\nfinishTimeInSeconds\nrating\nscore\nranking\ncontest{\n"
"title\ntitleCn\nstartTime\n}\n}\n}\n"
)
else:
key = 'username'
url = 'https://leetcode.com/graphql'
query = (
"\nquery userContestRankingInfo($username:String!){\nuserContestRanking"
"(username:$username){\nattendedContestsCount\nrating\nglobalRanking\n"
"totalParticipants\ntopPercentage\nbadge{\nname\n}\n}\nuserContestRankingHistory"
"(username:$username){\nattended\ntrendDirection\nproblemsSolved\n"
"totalProblems\nfinishTimeInSeconds\nrating\nranking\ncontest{\n"
"title\nstartTime\n}\n}\n}\n "
)
variables = {key: uid}
for _ in range(self.retry):
resp = requests.post(
url=url, json={'query': query, 'variables': variables}, verify=False
)
if resp.status_code != 200:
continue
res = resp.json()
if 'errors' in res:
break
ranking = res['data']['userContestRanking']
if ranking and 'rating' in ranking:
score = float(ranking['rating'])
if 'localRanking' in ranking:
return int(ranking['localRanking']), score
if 'globalRanking' in ranking:
return int(ranking['globalRanking']), score
return None, None
def get_user_ranking(self, uid):
for region in ['CN', 'US']:
# 美国站会有国服用户,这里最多需要查询两次
ranking, score = self._user_ranking(region, uid)
if score is not None:
return ranking, score
return None
def get_1600_count(self):
left, right = 1, 4000
while left < right:
mid = (left + right + 1) >> 1
page = self.load_page(mid)
print(f'{mid} 页:', page)
if not page:
return 0
ranking, score = self.get_user_ranking(page[0][1])
if score >= 1600:
left = mid
else:
right = mid - 1
page = [uid for _, uid in self.load_page(left) if uid]
print('校准中...')
left, right = 0, len(page) - 1
while left < right:
mid = (left + right + 1) >> 1
ranking, score = self.get_user_ranking(page[mid])
if score >= 1600:
left = mid
else:
right = mid - 1
return self.get_user_ranking(page[left])[0]
def get_user(self, rank):
p = (rank - 1) // 25 + 1
offset = (rank - 1) % 25
page = self.load_page(p)
_, score = self.get_user_ranking(page[offset][1])
return score, page[offset][1]
def fetch_ranking_data(self):
total = self.get_1600_count()
if not total:
return
print(f'[{self.region}] 1600 分以上共计 {total}')
guardian = int(total * 0.05)
knight = int(total * 0.25)
g_first, g_last = self.get_user(1), self.get_user(guardian)
print(
f'Guardian(top 5%): 共 {guardian} 名,守门员 {g_last[0]}uid: {g_last[1]}),最高 {g_first[0]}uid: {g_first[1]}'
)
k_first, k_last = self.get_user(guardian + 1), self.get_user(knight)
print(
f'Knight(top 25%): 共 {knight} 名,守门员 {k_last[0]}uid: {k_last[1]}),最高 {k_first[0]}uid: {k_first[1]}'
)
# 国服竞赛排名
lk = Ranking(region='CN')
lk.fetch_ranking_data()
print('\n------------------------------\n')
# 全球竞赛排名
gk = Ranking(region='US')
gk.fetch_ranking_data()