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.

Asked By: sgalpha01

||

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!
Answered By: user_na
Categories: questions Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.