How to design Codeforces interactive grader?
Question:
I came across one interactive problem in Codeforces. I want to know how the grader or interactor (as per Codeforces’ terms) might be designed.
Let’s say I want to create a grader for this problem: 1. Guess the Number.
My solution to the above problem is stored in 1_Guess_the_Number.py
file. It is a correct solution and is accepted by the CF grader.
#!/usr/bin/env python3
l, r = 1, 1000000
while l != r:
mid = (l + r + 1) // 2
print(mid, flush=True)
response = input()
if response == "<":
r = mid - 1
else:
l = mid
print("!", l)
I created the following grader.py
file:
#!/usr/bin/env python3
import sys
INP = 12
def interactor(n):
if n > INP:
return "<"
return ">="
while True:
guess = input()
if guess.startswith("!"):
print(int(guess.split()[1]) == INP, flush=True)
sys.exit()
print(interactor(int(guess)), flush=True)
So, when I run ./1_Guess_the_Number.py | ./grader_1.py
, I expect it to work correctly. But in the terminal, the above command runs for an infinite time with only the following output:
<
I don’t know what is going wrong. Also, it will be very helpful if someone can provide any other way.
Answers:
The comment from @user2357112 describes correctly why it is not working. While your pipe is sending the output of the first script to the grader, you’re not sending ‘grader.py”s responses to the first script.
So what we need to do is to establish a two way communication.
Here is one way to do it. In the grader, call the script you want to test as subprocess
and comunicate with it though pipes.
I added additional explanation to the code.
1_Guess_the_Number.py
is the same as yours:
#!/usr/bin/env python3
l, r = 1, 1000000
while l != r:
mid = (l + r + 1) // 2
print(mid, flush=True)
response = input()
if response == "<":
r = mid - 1
else:
l = mid
print("!", l)
grader.py
takes the name of the test file as input and executes it using subprocess.Popen
:
# !/usr/bin/env python3
import sys
import subprocess
INP = 12
def interactor(n):
if n > INP:
return "<"
return ">="
def decodeBytes(message):
# Helperfunction to decode the message from stdin
# guess has the format b'12345rn'
# rstrip() removes the rn, then we decode it as ascii
return message.rstrip().decode('ascii')
if __name__ == '__main__':
print(f'The secret answer is: {INP}')
# get script name:
name = sys.argv[1]
print(f'calling script {name}')
# start script as a subprocess
p = subprocess.Popen(["python3", name], stdout=subprocess.PIPE, stdin=subprocess.PIPE)
# create iterator to read subsequent guesses
stdout_iterator = iter(p.stdout.readline, b"")
# loop over all guesses
for msg_in in stdout_iterator:
#msg_in is a byte array, we need to decode it
guess = decodeBytes(msg_in)
print(f'got msg: {guess}')
if guess.startswith("!"):
final_answer = int(guess.split()[1])
print(f'The final answer is: {final_answer}')
if final_answer == INP:
print('Answer was correct!')
break
print('Answer is wrong!')
break
# we determine the message to send out
msg_out = interactor(int(guess))
print(f'send msg: {msg_out}')
# sending it to the scripts stdin.
p.stdin.write((msg_out + 'n').encode())
# we need to flush stdin, to make sure the message is not stuck in a buffer
p.stdin.flush()
From the commandline you call the grader by
python grader.py 1_Guess_the_Number.py
The print-out is:
The secret answer is: 12
calling script 1_Guess_the_Number.py
got msg: 500001
send msg: <
got msg: 250001
send msg: <
got msg: 125001
send msg: <
got msg: 62501
send msg: <
got msg: 31251
send msg: <
got msg: 15626
send msg: <
got msg: 7813
send msg: <
got msg: 3907
send msg: <
got msg: 1954
send msg: <
got msg: 977
send msg: <
got msg: 489
send msg: <
got msg: 245
send msg: <
got msg: 123
send msg: <
got msg: 62
send msg: <
got msg: 31
send msg: <
got msg: 16
send msg: <
got msg: 8
send msg: >=
got msg: 12
send msg: >=
got msg: 14
send msg: <
got msg: 13
send msg: <
got msg: ! 12
The final answer is: 12
Answer was correct!
I came across one interactive problem in Codeforces. I want to know how the grader or interactor (as per Codeforces’ terms) might be designed.
Let’s say I want to create a grader for this problem: 1. Guess the Number.
My solution to the above problem is stored in 1_Guess_the_Number.py
file. It is a correct solution and is accepted by the CF grader.
#!/usr/bin/env python3
l, r = 1, 1000000
while l != r:
mid = (l + r + 1) // 2
print(mid, flush=True)
response = input()
if response == "<":
r = mid - 1
else:
l = mid
print("!", l)
I created the following grader.py
file:
#!/usr/bin/env python3
import sys
INP = 12
def interactor(n):
if n > INP:
return "<"
return ">="
while True:
guess = input()
if guess.startswith("!"):
print(int(guess.split()[1]) == INP, flush=True)
sys.exit()
print(interactor(int(guess)), flush=True)
So, when I run ./1_Guess_the_Number.py | ./grader_1.py
, I expect it to work correctly. But in the terminal, the above command runs for an infinite time with only the following output:
<
I don’t know what is going wrong. Also, it will be very helpful if someone can provide any other way.
The comment from @user2357112 describes correctly why it is not working. While your pipe is sending the output of the first script to the grader, you’re not sending ‘grader.py”s responses to the first script.
So what we need to do is to establish a two way communication.
Here is one way to do it. In the grader, call the script you want to test as subprocess
and comunicate with it though pipes.
I added additional explanation to the code.
1_Guess_the_Number.py
is the same as yours:
#!/usr/bin/env python3
l, r = 1, 1000000
while l != r:
mid = (l + r + 1) // 2
print(mid, flush=True)
response = input()
if response == "<":
r = mid - 1
else:
l = mid
print("!", l)
grader.py
takes the name of the test file as input and executes it using subprocess.Popen
:
# !/usr/bin/env python3
import sys
import subprocess
INP = 12
def interactor(n):
if n > INP:
return "<"
return ">="
def decodeBytes(message):
# Helperfunction to decode the message from stdin
# guess has the format b'12345rn'
# rstrip() removes the rn, then we decode it as ascii
return message.rstrip().decode('ascii')
if __name__ == '__main__':
print(f'The secret answer is: {INP}')
# get script name:
name = sys.argv[1]
print(f'calling script {name}')
# start script as a subprocess
p = subprocess.Popen(["python3", name], stdout=subprocess.PIPE, stdin=subprocess.PIPE)
# create iterator to read subsequent guesses
stdout_iterator = iter(p.stdout.readline, b"")
# loop over all guesses
for msg_in in stdout_iterator:
#msg_in is a byte array, we need to decode it
guess = decodeBytes(msg_in)
print(f'got msg: {guess}')
if guess.startswith("!"):
final_answer = int(guess.split()[1])
print(f'The final answer is: {final_answer}')
if final_answer == INP:
print('Answer was correct!')
break
print('Answer is wrong!')
break
# we determine the message to send out
msg_out = interactor(int(guess))
print(f'send msg: {msg_out}')
# sending it to the scripts stdin.
p.stdin.write((msg_out + 'n').encode())
# we need to flush stdin, to make sure the message is not stuck in a buffer
p.stdin.flush()
From the commandline you call the grader by
python grader.py 1_Guess_the_Number.py
The print-out is:
The secret answer is: 12
calling script 1_Guess_the_Number.py
got msg: 500001
send msg: <
got msg: 250001
send msg: <
got msg: 125001
send msg: <
got msg: 62501
send msg: <
got msg: 31251
send msg: <
got msg: 15626
send msg: <
got msg: 7813
send msg: <
got msg: 3907
send msg: <
got msg: 1954
send msg: <
got msg: 977
send msg: <
got msg: 489
send msg: <
got msg: 245
send msg: <
got msg: 123
send msg: <
got msg: 62
send msg: <
got msg: 31
send msg: <
got msg: 16
send msg: <
got msg: 8
send msg: >=
got msg: 12
send msg: >=
got msg: 14
send msg: <
got msg: 13
send msg: <
got msg: ! 12
The final answer is: 12
Answer was correct!