🌐 AI搜索 & 代理 主页
Skip to content

Commit 3e3b493

Browse files
Add support for interactive submissions
1 parent db22c5b commit 3e3b493

File tree

6 files changed

+208
-4
lines changed

6 files changed

+208
-4
lines changed

examples/interactive.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import judge0
2+
3+
user_solution = judge0.Submission(
4+
source_code="""
5+
lower_bound, upper_bound = (int(x) for x in input().strip().split())
6+
while True:
7+
my_guess = (lower_bound + upper_bound) // 2
8+
print(my_guess)
9+
10+
command = input().strip()
11+
if command == "lower":
12+
lower_bound, upper_bound = lower_bound, my_guess
13+
elif command == "higher":
14+
lower_bound, upper_bound = my_guess, upper_bound
15+
else:
16+
break
17+
"""
18+
)
19+
20+
interactive_producer = judge0.Submission(
21+
source_code="""
22+
#include <stdio.h>
23+
24+
int main(int argc, char **argv) {
25+
int lower_bound, upper_bound, number_to_guess, max_tries;
26+
scanf("%d %d %d %d", &lower_bound, &upper_bound, &number_to_guess, &max_tries);
27+
28+
FILE *user_solution_stdin = fopen(argv[1], "w");
29+
FILE *user_solution_stdout = fopen(argv[2], "r");
30+
31+
fprintf(user_solution_stdin, "%d %d\\n", lower_bound, upper_bound);
32+
fflush(user_solution_stdin);
33+
34+
int user_guess;
35+
for (int i = 0; i < max_tries; i++) {
36+
fscanf(user_solution_stdout, "%d", &user_guess);
37+
if (user_guess > number_to_guess) {
38+
fprintf(user_solution_stdin, "lower\\n");
39+
fflush(user_solution_stdin);
40+
} else if (user_guess < number_to_guess) {
41+
fprintf(user_solution_stdin, "higher\\n");
42+
fflush(user_solution_stdin);
43+
} else {
44+
fprintf(user_solution_stdin, "correct\\n");
45+
fflush(user_solution_stdin);
46+
47+
printf("User successfully guessed the number.\\n");
48+
49+
return 0;
50+
}
51+
}
52+
53+
fprintf(user_solution_stdin, "failed\\n");
54+
fflush(user_solution_stdin);
55+
56+
printf("User failed to guess the number within %d guesses.\\n", max_tries);
57+
58+
return 0;
59+
}
60+
""",
61+
language=judge0.C,
62+
)
63+
64+
result = judge0.run(
65+
submissions=user_solution,
66+
interactive_producer=interactive_producer,
67+
test_cases=judge0.TestCase(input="0 100 42 7", expected_output="User successfully guessed the number.\n"),
68+
)
69+
70+
print(f"Submission status: {result.status}")
71+
print(f"Producer stdout: {result.stdout}")
72+
print(f'User stdout:\n{result.post_execution_filesystem.find("user.stdout")}')
73+
print(f'User stdin:\n{result.post_execution_filesystem.find("user.stdin")}')

src/judge0/__init__.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,10 @@ def _get_implicit_client(flavor: Flavor) -> Client:
101101
CE = Flavor.CE
102102
EXTRA_CE = Flavor.EXTRA_CE
103103

104-
PYTHON = LanguageAlias.PYTHON
104+
C = LanguageAlias.C
105105
CPP = LanguageAlias.CPP
106-
JAVA = LanguageAlias.JAVA
107-
CPP_GCC = LanguageAlias.CPP_GCC
108106
CPP_CLANG = LanguageAlias.CPP_CLANG
107+
CPP_GCC = LanguageAlias.CPP_GCC
108+
JAVA = LanguageAlias.JAVA
109+
PYTHON = LanguageAlias.PYTHON
109110
PYTHON_FOR_ML = LanguageAlias.PYTHON_FOR_ML

src/judge0/api.py

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
from typing import Optional, Union
22

3-
from .base_types import Flavor, TestCase
3+
from .base_types import Flavor, TestCase, LanguageAlias
44
from .clients import Client
55
from .retry import RegularPeriodRetry, RetryMechanism
66
from .submission import Submission
77

8+
from .filesystem import Filesystem, File
9+
import textwrap
10+
811

912
def resolve_client(
1013
client: Optional[Union[Client, Flavor]] = None,
@@ -133,13 +136,111 @@ def create_submissions_from_test_cases(
133136
return all_submissions
134137

135138

139+
def safe_format(f, arg):
140+
try:
141+
return (f or "") % (arg or "")
142+
except TypeError:
143+
return f or ""
144+
145+
146+
def create_fs(client, submission):
147+
fs = Filesystem(submission.additional_files)
148+
language_id = client.get_language_id(submission.language)
149+
if language_id != LanguageAlias.MULTI_FILE: # Multi-file program
150+
language = client.get_language(language_id)
151+
fs.files.append(File(language["source_file"], submission.source_code))
152+
fs.files.append(
153+
File(
154+
"compile.sh",
155+
safe_format(language["compile_cmd"], submission.compiler_options),
156+
)
157+
)
158+
fs.files.append(
159+
File("run.sh", f'{language["run_cmd"]} {submission.command_line_arguments}')
160+
)
161+
return fs
162+
163+
164+
def create_submission_with_interactive_producer(
165+
client, submission, interactive_producer
166+
):
167+
interactive_producer.command_line_arguments = "/box/stdin.fifo /box/stdout.fifo"
168+
169+
consumer_fs = create_fs(client, submission)
170+
producer_fs = create_fs(client, interactive_producer)
171+
172+
final_fs = Filesystem()
173+
for f in consumer_fs.files:
174+
final_fs.files.append(File(f"./consumer/{f.name}", f.content))
175+
176+
for f in producer_fs.files:
177+
final_fs.files.append(File(f"./producer/{f.name}", f.content))
178+
179+
final_fs.files.append(
180+
File(
181+
"compile.sh",
182+
textwrap.dedent(
183+
"""
184+
cd /box/consumer && bash compile.sh
185+
cd /box/producer && bash compile.sh
186+
"""
187+
),
188+
)
189+
)
190+
191+
final_fs.files.append(
192+
File(
193+
"run.sh",
194+
textwrap.dedent(
195+
"""
196+
mkfifo /box/stdin.fifo /box/stdout.fifo
197+
198+
cd /box/consumer
199+
tee >(bash run.sh 2> /box/user.stderr | tee /box/stdout.fifo /box/user.stdout &> /dev/null) /box/user.stdin < /box/stdin.fifo &> /dev/null &
200+
201+
cd /box/producer
202+
bash run.sh < /dev/stdin &
203+
PRODUCER_PID=$!
204+
205+
wait $PRODUCER_PID
206+
207+
rm -f /box/stdin.fifo /box/stdout.fifo
208+
"""
209+
),
210+
)
211+
)
212+
213+
return Submission(
214+
source_code="",
215+
additional_files=final_fs,
216+
language=LanguageAlias.MULTI_FILE,
217+
)
218+
219+
220+
def create_submissions_with_interactive_producer(
221+
client, submissions, interactive_producer
222+
):
223+
if isinstance(submissions, Submission):
224+
return create_submission_with_interactive_producer(
225+
client, submissions, interactive_producer
226+
)
227+
else:
228+
return [
229+
create_submission_with_interactive_producer(
230+
client, submission, interactive_producer
231+
)
232+
for submission in submissions
233+
]
234+
235+
136236
def _execute(
137237
*,
138238
client: Optional[Union[Client, Flavor]] = None,
139239
submissions: Optional[Union[Submission, list[Submission]]] = None,
140240
source_code: Optional[str] = None,
141241
wait_for_result: bool = False,
142242
test_cases: Optional[Union[TestCase, list[TestCase]]] = None,
243+
interactive_producer: Optional[Submission] = None,
143244
**kwargs,
144245
) -> Union[Submission, list[Submission]]:
145246
if submissions is not None and source_code is not None:
@@ -164,6 +265,11 @@ def _execute(
164265

165266
client = resolve_client(client, submissions=submissions)
166267

268+
if interactive_producer is not None:
269+
submissions = create_submissions_with_interactive_producer(
270+
client, submissions, interactive_producer
271+
)
272+
167273
all_submissions = create_submissions_from_test_cases(submissions, test_cases)
168274

169275
# We differentiate between creating a single submission and multiple
@@ -189,6 +295,7 @@ def async_execute(
189295
submissions: Optional[Union[Submission, list[Submission]]] = None,
190296
source_code: Optional[str] = None,
191297
test_cases: Optional[Union[TestCase, list[TestCase]]] = None,
298+
interactive_producer: Optional[Submission] = None,
192299
**kwargs,
193300
) -> Union[Submission, list[Submission]]:
194301
return _execute(
@@ -197,6 +304,7 @@ def async_execute(
197304
source_code=source_code,
198305
wait_for_result=False,
199306
test_cases=test_cases,
307+
interactive_producer=interactive_producer,
200308
**kwargs,
201309
)
202310

@@ -207,6 +315,7 @@ def sync_execute(
207315
submissions: Optional[Union[Submission, list[Submission]]] = None,
208316
source_code: Optional[str] = None,
209317
test_cases: Optional[Union[TestCase, list[TestCase]]] = None,
318+
interactive_producer: Optional[Submission] = None,
210319
**kwargs,
211320
) -> Union[Submission, list[Submission]]:
212321
return _execute(
@@ -215,6 +324,7 @@ def sync_execute(
215324
source_code=source_code,
216325
wait_for_result=True,
217326
test_cases=test_cases,
327+
interactive_producer=interactive_producer,
218328
**kwargs,
219329
)
220330

src/judge0/base_types.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ class LanguageAlias(IntEnum):
3232
CPP_GCC = 3
3333
CPP_CLANG = 4
3434
PYTHON_FOR_ML = 5
35+
C = 6
36+
MULTI_FILE = 89
3537

3638

3739
class Flavor(IntEnum):

src/judge0/data.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,34 @@
77
LanguageAlias.JAVA: 62,
88
LanguageAlias.CPP_GCC: 54,
99
LanguageAlias.CPP_CLANG: 76,
10+
LanguageAlias.C: 50,
11+
LanguageAlias.MULTI_FILE: 89,
1012
},
1113
"1.13.1-extra": {
1214
LanguageAlias.PYTHON: 10,
1315
LanguageAlias.CPP: 2,
1416
LanguageAlias.JAVA: 4,
1517
LanguageAlias.CPP_CLANG: 2,
1618
LanguageAlias.PYTHON_FOR_ML: 10,
19+
LanguageAlias.C: 1,
20+
LanguageAlias.MULTI_FILE: 89,
1721
},
1822
"1.14.0": {
1923
LanguageAlias.PYTHON: 100,
2024
LanguageAlias.CPP: 105,
2125
LanguageAlias.JAVA: 91,
2226
LanguageAlias.CPP_GCC: 105,
2327
LanguageAlias.CPP_CLANG: 76,
28+
LanguageAlias.C: 103,
29+
LanguageAlias.MULTI_FILE: 89,
2430
},
2531
"1.14.0-extra": {
2632
LanguageAlias.PYTHON: 25,
2733
LanguageAlias.CPP: 2,
2834
LanguageAlias.JAVA: 4,
2935
LanguageAlias.CPP_CLANG: 2,
3036
LanguageAlias.PYTHON_FOR_ML: 25,
37+
LanguageAlias.C: 1,
38+
LanguageAlias.MULTI_FILE: 89,
3139
},
3240
}

src/judge0/filesystem.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,16 @@ def encode(self) -> bytes:
5656
zip_file.writestr(file.name, file.content)
5757
return zip_buffer.getvalue()
5858

59+
def find(self, name: str) -> Optional[File]:
60+
if name.startswith("./"):
61+
name = name[2:]
62+
63+
for file in self.files:
64+
if file.name == name:
65+
return file
66+
67+
return None
68+
5969
def __str__(self) -> str:
6070
return b64encode(self.encode()).decode()
6171

0 commit comments

Comments
 (0)