Skip to content

Commit 6f831e5

Browse files
Fix issues of problem sources, authors, achievements, and LDM
1 parent c75fe27 commit 6f831e5

File tree

4 files changed

+65
-91
lines changed

4 files changed

+65
-91
lines changed

autokattis/api/enums.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,11 @@ class DefaultRanklistColumn(IntEnum):
109109
class ProblemAuthorsColumn(IntEnum):
110110
AUTHOR = 0
111111
PROBLEMS = 1
112-
AVG_DIFF = 2
112+
SOLVED = 2
113+
AVG_DIFF = 4
113114

114115
class ProblemSourcesColumn(IntEnum):
115116
SOURCE = 0
116117
PROBLEMS = 1
117-
AVG_DIFF = 2
118+
SOLVED = 2
119+
AVG_DIFF = 4

autokattis/api/openkattis.py

Lines changed: 29 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -42,14 +42,12 @@ def problems(self, show_solved=True, show_partial=True, show_tried=False, show_u
4242
Parameters:
4343
- low_detail_mode: True if you want only need the problem IDs and the links. Otherwise, it will contain many other details and thus will take more time. By default, this is set to False.
4444
45-
The below parameters only matter if low_detail_mode is set to False:
45+
The below parameters acts as filters and only matter if low_detail_mode is set to False:
4646
- show_solved: True if you want to include fully solved problems, False otherwise. By default, this is set to True.
4747
- show_partial: True if you want to include partially solved problems, False otherwise. By default, this is set to True.
4848
- show_tried: True if you want to include unsolved problems that you have attempted, False otherwise. By default, this is set to False.
4949
- show_untried: True if you want to include unsolved problems that you have never attempted, False otherwise. By default, this is set to False.
5050
51-
Note that if you already have a lot of submissions, the low detail mode might run slower for you.
52-
5351
Example for low detail mode:
5452
[
5553
{
@@ -99,57 +97,16 @@ def problems(self, show_solved=True, show_partial=True, show_tried=False, show_u
9997
has_content, data = True, []
10098

10199
if low_detail_mode:
102-
pid_set = set()
103-
104-
if show_solved:
105-
params = {
106-
'page': 0,
107-
'tab': 'submissions',
108-
'status': 'AC'
109-
}
110-
111-
with ThreadPoolExecutor(max_workers=self.get_max_workers()) as executor:
112-
futures = []
113-
while has_content:
114-
has_content = False
115-
futures.clear()
116-
for _ in range(self.get_max_workers()):
117-
futures.append(executor.submit(self.new_get, f'{self.get_base_url()}/users/{self.get_username()}', params=params.copy()))
118-
params['page'] += 1
119-
for f in as_completed(futures):
120-
response = f.result()
121-
soup = bs(response.content, features='lxml')
122-
if not soup: continue
123-
124-
table = table = soup.find('div', id='submissions-tab').find('section', class_='strip strip-item-plain').find('table', class_='table2')
125-
try: table_content = get_table_rows(table)
126-
except AttributeError: continue
127-
128-
for row in table_content:
129-
columns = row.find_all('td')
130-
if columns and len(columns) >= SubmissionsColumn.CONTEST_PROBLEM_NAME:
131-
has_content = True
132-
pid = get_last_path(columns[SubmissionsColumn.CONTEST_PROBLEM_NAME].find_all('a')[-1].get('href')) # might have two links if it belongs to a contest, so we take the latter
133-
if pid not in pid_set:
134-
pid_set.add(pid)
135-
data.append({
136-
'name': get_last_path(columns[SubmissionsColumn.CONTEST_PROBLEM_NAME].text),
137-
'id': pid,
138-
'link': f"{self.get_base_url()}/problems/{pid}"
139-
})
140-
else:
141-
# we can just take from the given dropdown list
142-
soup = self.get_soup_response(f'{self.get_base_url()}/users/{self.get_username()}')
143-
for option in soup.find_all('option')[1:]:
144-
pid = option.get('value').strip()
145-
if not pid: break
146-
if pid not in pid_set:
147-
pid_set.add(pid)
148-
data.append({
149-
'name': option.text.strip(),
150-
'id': pid,
151-
'link': f"{self.get_base_url()}/problems/{pid}"
152-
})
100+
# we can just take from the given dropdown list
101+
soup = self.get_soup_response(f'{self.get_base_url()}/users/{self.get_username()}?tab=submissions')
102+
for option in soup.find_all('option')[4:]:
103+
pid = option.get('value').strip()
104+
if not pid: break
105+
data.append({
106+
'name': option.text.strip(),
107+
'id': pid,
108+
'link': f"{self.get_base_url()}/problems/{pid}"
109+
})
153110
else:
154111
params = {
155112
'page': 1,
@@ -444,11 +401,21 @@ def achievements(self):
444401
"id": "zyxab",
445402
"runtime": 0.01,
446403
"length": 10,
447-
"achievement": "Within 100% of shortest",
404+
"achievement": "Fastest Solution, Within 100% of shortest",
448405
"difficulty": 2.6,
449406
"category": "Easy",
450407
"link": "https://open.kattis.com/problems/zyxab"
451408
},
409+
{
410+
"name": "Zyxac",
411+
"id": "zyxac",
412+
"runtime": 0.02,
413+
"length": 101,
414+
"achievement": "Fastest Solution",
415+
"difficulty": 8.6,
416+
"category": "Hard",
417+
"link": "https://open.kattis.com/problems/zyxac"
418+
},
452419
...
453420
]
454421
'''
@@ -473,7 +440,7 @@ def achievements(self):
473440
if columns[SolvedProblemsColumn.NAME].find('a') == None: continue
474441
has_content = True
475442
link = f"{self.get_base_url()}{columns[SolvedProblemsColumn.NAME].find('a').get('href')}"
476-
achievement = ', '.join(sorted(set(sp.text.strip() for sp in columns[SolvedProblemsColumn.ACHIEVEMENTS].find_all('span') if len(sp.find_all('span')) == 1)))
443+
achievement = ', '.join(sorted(set(sp.text.strip() for sp in columns[SolvedProblemsColumn.ACHIEVEMENTS].find_all('span') if not sp.find_all('span') and sp.text.strip())))
477444
if not achievement: continue
478445
try: difficulty = float(re.findall('[\d\.]+', columns[SolvedProblemsColumn.DIFFICULTY].text)[-1])
479446
except: difficulty = None
@@ -1079,13 +1046,15 @@ def problem_authors(self):
10791046
{
10801047
"name": "Lorem Ipsum",
10811048
"problems": 6,
1049+
"num_solved": 2,
10821050
"avg_difficulty": 1.3,
10831051
"avg_category": "Easy",
10841052
"link": "https://open.kattis.com/problem-authors/Lorem%20Ipsum"
10851053
},
10861054
{
10871055
"name": "Jack Julius Jill",
10881056
"problems": 12,
1057+
"num_solved": 10,
10891058
"avg_difficulty": 5.3,
10901059
"avg_category": "Medium",
10911060
"link": "https://open.kattis.com/problem-authors/Jack%20Julius%Jill"
@@ -1112,6 +1081,7 @@ def problem_authors(self):
11121081
data.append({
11131082
'name': columns_text[ProblemAuthorsColumn.AUTHOR].strip(),
11141083
'problems': int(columns_text[ProblemAuthorsColumn.PROBLEMS]),
1084+
'num_solved': int(columns_text[ProblemAuthorsColumn.SOLVED]),
11151085
'avg_difficulty': difficulty,
11161086
'avg_category': (re.findall('[A-Za-z]+', columns_text[ProblemAuthorsColumn.AVG_DIFF]) or ['N/A'])[0],
11171087
'link': f'{self.get_base_url()}{columns_url[ProblemAuthorsColumn.AUTHOR][0].get("href")}'
@@ -1128,13 +1098,15 @@ def problem_sources(self):
11281098
{
11291099
"name": "Lorem Ipsum Contest",
11301100
"problems": 6,
1101+
"num_solved": 2,
11311102
"avg_difficulty": 1.3,
11321103
"avg_category": "Easy",
11331104
"link": "https://open.kattis.com/problem-sources/Lorem%20Ipsum%20Contest"
11341105
},
11351106
{
11361107
"name": "Jack Julius Jill Cup",
11371108
"problems": 12,
1109+
"num_solved": 10,
11381110
"avg_difficulty": 5.3,
11391111
"avg_category": "Medium",
11401112
"link": "https://open.kattis.com/problem-authors/Jack%20Julius%Jill%20Cup"
@@ -1161,6 +1133,7 @@ def problem_sources(self):
11611133
data.append({
11621134
'name': columns_text[ProblemSourcesColumn.SOURCE].strip(),
11631135
'problems': int(columns_text[ProblemSourcesColumn.PROBLEMS]),
1136+
'num_solved': int(columns_text[ProblemSourcesColumn.SOLVED]),
11641137
'avg_difficulty': difficulty,
11651138
'avg_category': (re.findall('[A-Za-z]+', columns_text[ProblemSourcesColumn.AVG_DIFF]) or ['N/A'])[0],
11661139
'link': f'{self.get_base_url()}{columns_url[ProblemSourcesColumn.SOURCE][0].get("href")}'

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
setup_args = dict(
77
name='autokattis',
8-
version='2.1.0',
8+
version='2.1.1',
99
description='Updated Kattis API wrapper',
1010
long_description_content_type="text/markdown",
1111
long_description=README,

test/openkattis.py

Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,47 +5,46 @@
55
kt = Kattis(USER, PASSWORD)
66
kt = OpenKattis(USER, PASSWORD)
77

8-
test('open_problems_solved_fdm', kt.problems, {})
9-
test('open_problems_solved_ldm', kt.problems, {'low_detail_mode': True})
10-
test('open_problems_all_fdm', kt.problems, {'show_tried': True, 'show_untried': True})
11-
test('open_problems_all_ldm', kt.problems, {'show_tried': True, 'show_untried': True, 'low_detail_mode': True})
8+
test('open_problems_solved_fdm', kt.problems, {})
9+
test('open_problems_all_fdm', kt.problems, {'show_tried': True, 'show_untried': True})
10+
test('open_problems_ldm', kt.problems, {'low_detail_mode': True})
1211

13-
test('open_plot_problems_solved_nofile', kt.plot_problems, {})
14-
test('open_plot_problems_solved_file', kt.plot_problems, {'filepath': 'plot1.png'})
15-
test('open_plot_problems_all_file', kt.plot_problems, {'filepath': 'plot2.png', 'show_tried': True, 'show_untried': True})
12+
test('open_plot_problems_solved_nofile', kt.plot_problems, {})
13+
test('open_plot_problems_solved_file', kt.plot_problems, {'filepath': 'plot1.png'})
14+
test('open_plot_problems_all_file', kt.plot_problems, {'filepath': 'plot2.png', 'show_tried': True, 'show_untried': True})
1615

17-
test('open_problem_single_nodownload', kt.problem, {'problem_ids': '2048'})
18-
test('open_problem_single_download', kt.problem, {'problem_ids': '2048', 'download_files': True})
19-
test('open_problem_partial_scoring', kt.problem, {'problem_ids': 'golf'})
20-
test('open_problem_multiple_nodownload', kt.problem, {'problem_ids': ['2048', 'abinitio', 'teque']})
21-
test('open_problem_multiple_download', kt.problem, {'problem_ids': {'2048', 'abinitio', 'teque'}, 'download_files': True})
22-
test('open_problem_invalid', kt.problem, {'problem_ids': ('@?@?', 'ABC')})
16+
test('open_problem_single_nodownload', kt.problem, {'problem_ids': '2048'})
17+
test('open_problem_single_download', kt.problem, {'problem_ids': '2048', 'download_files': True})
18+
test('open_problem_partial_scoring', kt.problem, {'problem_ids': 'golf'})
19+
test('open_problem_multiple_nodownload', kt.problem, {'problem_ids': ['2048', 'abinitio', 'teque']})
20+
test('open_problem_multiple_download', kt.problem, {'problem_ids': {'2048', 'abinitio', 'teque'}, 'download_files': True})
21+
test('open_problem_invalid', kt.problem, {'problem_ids': ('@?@?', 'ABC')})
2322

24-
test('open_achievements', kt.achievements, {})
23+
test('open_achievements', kt.achievements, {})
2524

26-
test('open_stats_all', kt.stats, {})
27-
test('open_stats_single', kt.stats, {'languages': 'C++'})
28-
test('open_stats_multiple', kt.stats, {'languages': ['C++', 'Python 3']})
29-
test('open_stats_invalid', kt.stats, {'languages': ('@?@?', 'ABC')})
25+
test('open_stats_all', kt.stats, {})
26+
test('open_stats_single', kt.stats, {'languages': 'C++'})
27+
test('open_stats_multiple', kt.stats, {'languages': ['C++', 'Python 3']})
28+
test('open_stats_invalid', kt.stats, {'languages': ('@?@?', 'ABC')})
3029

31-
test('open_suggest', kt.suggest, {})
30+
test('open_suggest', kt.suggest, {})
3231

33-
test('open_user_ranklist', kt.user_ranklist, {})
32+
test('open_user_ranklist', kt.user_ranklist, {})
3433

35-
test('open_country_ranklist_top100', kt.country_ranklist, {})
36-
test('open_country_ranklist_specific_1', kt.country_ranklist, {'value': 'SGP'})
37-
test('open_country_ranklist_specific_2', kt.country_ranklist, {'value': 'Singapore'})
38-
test('open_country_ranklist_specific_3', kt.country_ranklist, {'value': 'ISL'})
34+
test('open_country_ranklist_top100', kt.country_ranklist, {})
35+
test('open_country_ranklist_specific_1', kt.country_ranklist, {'value': 'SGP'})
36+
test('open_country_ranklist_specific_2', kt.country_ranklist, {'value': 'Singapore'})
37+
test('open_country_ranklist_specific_3', kt.country_ranklist, {'value': 'ISL'})
3938

40-
test('open_affiliation_ranklist_top100', kt.affiliation_ranklist, {})
41-
test('open_affiliation_ranklist_specific_1', kt.affiliation_ranklist, {'value': 'nus.edu.sg'})
42-
test('open_affiliation_ranklist_specific_2', kt.affiliation_ranklist, {'value': 'National University of Singapore'})
43-
test('open_affiliation_ranklist_specific_3', kt.affiliation_ranklist, {'value': 'ru.is'})
39+
test('open_affiliation_ranklist_top100', kt.affiliation_ranklist, {})
40+
test('open_affiliation_ranklist_specific_1', kt.affiliation_ranklist, {'value': 'nus.edu.sg'})
41+
test('open_affiliation_ranklist_specific_2', kt.affiliation_ranklist, {'value': 'National University of Singapore'})
42+
test('open_affiliation_ranklist_specific_3', kt.affiliation_ranklist, {'value': 'ru.is'})
4443

45-
test('open_challenge_ranklist', kt.challenge_ranklist, {})
44+
test('open_challenge_ranklist', kt.challenge_ranklist, {})
4645

47-
test('open_ranklist', kt.ranklist, {})
46+
test('open_ranklist', kt.ranklist, {})
4847

49-
test('open_problem_authors', kt.problem_authors, {})
48+
test('open_problem_authors', kt.problem_authors, {})
5049

51-
test('open_problem_sources', kt.problem_sources, {})
50+
test('open_problem_sources', kt.problem_sources, {})

0 commit comments

Comments
 (0)