现在很多大学都使用在线OJ给学生布置编程作业,甚至考试。理由有二:1. 教师节省了评阅作业的时间;2. 强迫学生进行编程练习。初学者因为不理解OJ的运作机制,常常遭受”我觉得我对,但OJ老判我错“的困局。本文试图帮助初学者理解OJ的运作机制。

1. 什么是OJ

Online Judge,简称OJ, 是一种在线判题系统。常见的有:

a1 a2 a3

​ 还有LeetCode/力扣之类, 本文以浙大背景的pintia.cn为基础进行说明。

2. OJ如何判题

我们以下题为例进行说明。该题要求从键盘以固定格式读入人的体重,身高,计算BMI, 然后根据不同的BMI值,给出判定结果,判定结果应为超轻/标准/超重/肥胖之一。

image-20201108200030028

当答题者提交程序后,OJ会在服务器上使用你选择的编译器编译并运行答题者提交的程序,并:

  1. 将命题者设定好的测试输入提供给答题者的程序作为输入(答题者程序通过input()函数获取这些输入)。
  2. 获取答题者程序的输出(通常是答题者程序通过print()函数输出的),并将该输出与命题者提供的标准答案进行比较,如果完全一致,则判定该测试点通过,否则判定失败。
  3. 观察答题者程序的内存消耗,运行时间等情况,如果超出设定,即判错。

在该题中,命题者设置了两个测试点,分别是:

编号 输入 期望输出
1 70,1.75 标准
2 55,1.2 肥胖

说明:测试点的输入及输出答题者在OJ网站上是看不到的,如果答题者能够看到测试输入和输出,则可以造假。

我们编写了下述程序并提交:

image-20201108203826672

OJ将两次运行我们提交的程序,提供输入并检查输出,检查后认为程序得到了正确的执行结果,判定为通过/答案正确。

image-20201108204129323

3. 新手常犯的错

3.1 固定输入内容

部分新手认为程序的输入“固定”为题目中的输入示例,直接将输入固化在程序中,如下述程序中的第1行。

image-20201108204453230

这种程序将会被OJ判错。程序的输入一定要用input()函数读取

3.2 input()函数内提供提示字符串

在下述程序中,答题者在input()函数中提供了提示字符串:”请输入体重,身高:”。从程序设计角度,这种做法很体贴,很好,但OJ是机器,它会将该提示字符串视为程序输出的一部分,然后发现程序输出与设定的标准输出不一致,判你错。

image-20201108204652070

3.3 输出格式不符合要求

下述程序中,答题者画蛇添足地在print()函数中增加了“诊断结果:”这几个字。如果是老师人肉判题,不会认为这是错误,但OJ是机器,它管程序的输出与设定的输出是否完全一致。 然后,你就被判错了。

image-20201108204933755

答题时,一定要确保程序的输出内容与要求的格式完全相同,包括内部的空格,空格个数,中英文符号等。必要情况下,应从输出示例中复制相关内容。

4. 答题的正确姿势

不要直接在OJ中编写代码! 应该先在Visual Studio Code、PyCharm这类IDE环境中编写,调试,测试无误后再提交给OJ判题。在进行测试时,肯定要将题目的输入示例作为输入进行测试,观察程序输出是否与输出示例一致! 但这不够,命题者在后台设置的测试点可能十分变态和极端,答题者应自行考虑各种极端输入,并通过测试来验证程序的正确性。

5. 非零返回

程序的每一次运行,都会在结束后向操作系统报错一个值,如果该值为0,意思是说我刚才运行得很成功,很愉快! 如果该值非0,即意味着程序异常退出。对于Python程序而言,非零返回即意味着你的程序在服务器端的运行发生了异常, 在得到结果前就中断退出了。

答题者通常感到奇怪,说我的程序我运行过了呀,输入样例输入,可以得到样例输出。 要知道,你的测试,仅能证明程序在该特定输入下正确,并不能证明在所有合法输入下都能正确执行。

老老实实检查程序,查找缺陷和错误!

6. 说说函数题

函数题并不是要求答题者提交一个完成的程序,它只要求答题者提交一个或多个函数的定义。这些函数接受题目规定的输入,然后返回符合题目要求的值。

image-20201108210124484

同样地,在答题者提交了函数代码之后,OJ会将函数代码与裁判测试程序合并(在裁判测试程序中,你的函数将会被调用),然后在服务器上执行,提供输入,并观察程序的输出是否与设定完全一致,从而间接判断你编写的函数代码是否正确。

实践中,答题者可以将裁判测试程序复制到Visual Studio Code这类的IDE中,再补充编写函数代码,并进行测试。如下图:

image-20201108210614705

注意:

  1. 在测试通过后,仅提交函数定义部分(本例中为1-5行),而不是整个程序。

  2. 函数应使用return返回结果,而不是print。