Skip to content

Commit f801869

Browse files
Add problems_v2 method
1 parent ad3590f commit f801869

File tree

5 files changed

+88
-29
lines changed

5 files changed

+88
-29
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ kt.problems() # problems you have solved so far
3939
kt.problems(show_partial=False) # exclude partial submissions
4040
kt.problems(*[True]*4) # literally all problems on Kattis
4141

42+
kt.problems_v2() # problems you have solved so far
43+
kt.problems_v2(show_non_ac=False) # literally all problems on Kattis
44+
4245
kt.list_unsolved() # let's grind!
4346

4447
kt.plot_problems() # plot the points distribution

autokattis/api/__init__.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,74 @@ def problems(self, show_solved=True, show_partial=True, show_tried=False, show_u
170170
})
171171
return self.Result(sorted(data, key=lambda x: x['id']))
172172

173+
@lru_cache
174+
def problems_v2(self, show_non_ac=False):
175+
'''
176+
Gets all accepted Kattis problems. Note that this is entirely different than the `problems`
177+
method due to possible gateway issues from the initial version.
178+
Returns a simpler JSON-like structure with these fields:
179+
name, id, link
180+
181+
Default: all solved and partially solved problems.
182+
'''
183+
184+
has_content = True
185+
params = {
186+
'page': 0,
187+
'tab': 'submissions',
188+
'status': 'AC'
189+
}
190+
191+
data = []
192+
pid_set = set()
193+
194+
with ThreadPoolExecutor(max_workers=self.MAX_WORKERS) as executor:
195+
futures = []
196+
while has_content:
197+
has_content = False
198+
futures.clear()
199+
for _ in range(self.MAX_WORKERS):
200+
futures.append(executor.submit(self.new_get, f'{self.BASE_URL}/users/{self.user}', params=params.copy()))
201+
params['page'] += 1
202+
for f in as_completed(futures):
203+
response = f.result()
204+
soup = bs(response.content, features='lxml')
205+
if not soup: continue
206+
table = table = soup.find('div', id='submissions-tab').find('section', class_='strip strip-item-plain').find('table', class_='table2')
207+
try:
208+
table_content = table.tbody.find_all('tr')
209+
except AttributeError:
210+
continue
211+
for row in table_content:
212+
columns = row.find_all('td')
213+
if columns and len(columns) > 2:
214+
has_content = True
215+
pid = columns[2].find_all('a')[-1].get('href').split('/')[-1] # might have two links if it belongs to a contest
216+
if pid not in pid_set:
217+
link = f"{self.BASE_URL}/problems/{pid}"
218+
name = columns[2].text.split('/')[-1].strip()
219+
pid_set.add(pid)
220+
data.append({
221+
'name': name,
222+
'id': pid,
223+
'link': link
224+
})
225+
226+
if show_non_ac:
227+
# we can just use the latest soup and take the dropdown
228+
for option in soup.find_all('option')[1:]:
229+
pid = option.get('value').strip()
230+
if not pid: break
231+
if pid not in pid_set:
232+
pid_set.add(pid)
233+
data.append({
234+
'name': option.text.strip(),
235+
'id': pid,
236+
'link': f"{self.BASE_URL}/problems/{pid}"
237+
})
238+
239+
return self.Result(sorted(data, key=lambda x: x['id']))
240+
173241
@lru_cache
174242
def plot_problems(self, filepath=None, show_solved=True, show_partial=True, show_tried=False, show_untried=False):
175243
'''

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='1.6.3',
8+
version='1.6.4',
99
description='Updated Kattis API wrapper',
1010
long_description_content_type="text/markdown",
1111
long_description=README,

test/main.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
print(df:=ret.to_df())
99
df.to_csv('test_problems_default.csv', index=False)
1010

11+
print('=== TEST PROBLEMS V2 (DEFAULT) ===')
12+
ret = kt.problems_v2() # problems you have solved so far
13+
print(df:=ret.to_df())
14+
df.to_csv('test_problems_v2_default.csv', index=False)
15+
1116
print('=== TEST PROBLEMS (NO PARTIAL) ===')
1217
ret = kt.problems(show_partial=False) # exclude partial submissions
1318
print(df:=ret.to_df())
@@ -18,6 +23,11 @@
1823
print(df:=ret.to_df())
1924
df.to_csv('test_problems_all.csv', index=False)
2025

26+
print('=== TEST PROBLEMS V2 (ALL PROBLEMS) ===')
27+
ret = kt.problems_v2(show_non_ac=True) # literally all problems on Kattis
28+
print(df:=ret.to_df())
29+
df.to_csv('test_problems_v2_all.csv', index=False)
30+
2131
print('=== TEST LIST UNSOLVED ===')
2232
ret = kt.list_unsolved() # let's grind!
2333
print(df:=ret.to_df())

test/nus.py

Lines changed: 6 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,15 @@
33

44
kt = NUSKattis(USER, PASSWORD)
55

6-
print('=== TEST PROBLEMS (DEFAULT) ===')
7-
ret = kt.problems() # problems you have solved so far
6+
print('=== TEST PROBLEMS V2 (DEFAULT) ===')
7+
ret = kt.problems_v2() # problems you have solved so far
88
print(df:=ret.to_df())
9-
df.to_csv('test_nus_problems_default.csv', index=False)
9+
df.to_csv('test_nus_problems_v2_default.csv', index=False)
1010

11-
print('=== TEST PROBLEMS (NO PARTIAL) ===')
12-
ret = kt.problems(show_partial=False) # exclude partial submissions
11+
print('=== TEST PROBLEMS V2 (ALL PROBLEMS) ===')
12+
ret = kt.problems_v2(show_non_ac=True) # literally all problems on Kattis
1313
print(df:=ret.to_df())
14-
df.to_csv('test_nus_problems_no_partial.csv', index=False)
15-
16-
print('=== TEST PROBLEMS (ALL PROBLEMS) ===')
17-
ret = kt.problems(*[True]*4) # literally all problems on Kattis
18-
print(df:=ret.to_df())
19-
df.to_csv('test_nus_problems_all.csv', index=False)
20-
21-
print('=== TEST LIST UNSOLVED ===')
22-
ret = kt.list_unsolved() # let's grind!
23-
print(df:=ret.to_df())
24-
df.to_csv('test_nus_list_unsolved.csv', index=False)
25-
26-
print('=== TEST PLOT PROBLEMS (DEFAULT) ===')
27-
ret = kt.plot_problems() # plot the points distribution
28-
print('Done!')
29-
30-
print('=== TEST PLOT PROBLEMS (TO FILEPATH) ===')
31-
ret = kt.plot_problems(filepath='nus_plot.png') # save to a filepath
32-
print('Done!')
33-
34-
print('=== TEST PLOT PROBLEMS (NO PARTIAL) ===')
35-
ret = kt.plot_problems(show_partial=False) # plot fully solved submissions
36-
print('Done!')
14+
df.to_csv('test_nus_problems_v2_all.csv', index=False)
3715

3816
print('=== TEST PROBLEM (SINGLE) ===')
3917
ret = kt.problem('2048') # fetch info about a problem

0 commit comments

Comments
 (0)