转自:http://blog.watashi.ws/2357/freopen-summary/
通常在Online Judge上的题目要求程序从标准输入流(standard input, stdin)读入输入数据,将答案输出到标准输出流(standard output, stdout),并通常会无视标准错误输出流(standard error, stderr)的输出。当然,过多的stderr输出必然会占用许多额外的时间,导致TLE,所以即使把debug信息输出到stderr,有时也要注意通过注释和条件编译去掉for循环中的debug语句。不过更多的时候,我们主要需要关心的还是stdin和stdout。
不过有时候,某些Online Judge的某些题目就会要求从给定文件读入测试数据,或将结果输出到给定文件中,或二者皆有。大多数编程语言在提供文件IO操作API的同时,都提供有许多直接操作标准输入输出的API。而很多初学者对这些API都要比对文件操作的API熟悉得多。所以如果在此时,能用标准输入输出的API来替代文件输入输出的API无疑是很方便的。在本地,这可以通过shell的重定向轻松办到
./p.exe < input.txt > output.txt 2> log.txt |
不过在Online Judge上,这一点就行不通了。好在很多语言都提供有一些API,能实现类似的功能。
除了希望使用标准输入输出的API来实现文件输入输出外。在本地调试程序的时候,有时候通过终端(屏幕)进行交互会方便得多,即使要进行文件输入输出也可以借由shell轻松自如的选择文件。所以,程序在本地使用stdio,而在Online Judge上才使用指定的文件会方便不少。
当然,前面还漏了一点,用标准输入输出的API除了可以节省力气外,还有一个好处是:如果同样一道题,在多数OJ上要用标准输入输出,但有些OJ要求文件输入输出时,代码无需经过太多修改就能AC。嗯,其实我就是在做Codeforces上新加的Andrew Stankevich Contests想到可以做这么一个总结的。下面的代码都用Think Positive这道题测试过,可以AC的完整代码请通过github访问。
C
首先是大家再熟悉不过的C语言的例子,直接通过"stdio.h"
中的freopen
函数实现标准输入输出的重定向,用#ifndef
实现条件编译。
3 | freopen ( "whatasha.in" , "r" , stdin); |
4 | freopen ( "whatasha.out" , "w" , stdout); |
理想的情况,我们希望新加的代码尽量独立而不影响其它部分。不过C语言标准中并不允许我们通过如下方法:
FILE * __dummy_in = freopen ( "whatasha.in" , "r" , stdin); |
将freopen
移到main
之外。而C语言虽然可以根据平台来实现在main
之前执行一段代码,不过方法都不可移植并且略微复杂,采用这样的方法来改写就本末倒置了。
C++
在C++中,我们就可以把这部分代码移到main
之外了,而且还可以做得更C++一点:
3 | $( const char * input, const char * output) { |
4 | freopen (input, "r" , stdin); |
5 | freopen (output, "w" , stdout); |
7 | } $( "whatasha.in" , "whatasha.out" ); |
这段代码虽然不符合C++标准,不过GNU C++, GNU C++0x和MS VC++都允许’$'符号作为标识符的一部分。选用$的目的是为了避免重名。当然依个人喜好也可以将$替换成_,再加上namespace什么的。
Pascal
Pascal中直接对input
和output
进行assign
即可。在Free Pascal中,如果打开了-So开关(Borland TP 7.0 compatible),那么相应的reset
和rewrite
是必须的,否则会得到Error 103 - File not open
。
3 | assign(input, 'whatasha.in' ); |
4 | assign(output, 'whatasha.out' ); |
Python
许多语言没有条件编译,视其为不必要的,甚至是邪恶的。这时候,我们可以使用环境变量。
1 | if os.getenv( 'USER' ) ! = 'watashi' : |
2 | sys.stdin = open ( 'whatasha.in' , 'r' ) |
3 | sys.stdout = open ( 'whatasha.out' , 'w' ) |
Perl
Perl中提供了BEGIN
, UNITCHECK
, CHECK
, INIT
和END
块,他们将按特定顺序在特定阶段被执行。将重定向代码放到BEGIN
块中好处是,即使把它放到程序末尾,它也会最先执行。Codeforces曾经不能正确处理带BEGIN
块的代码,不过INIT
就没有问题,用INIT
还有一个好处就是,它可以节省一个字节。
2 | if ( $ENV { 'USER' } ne 'watashi' ) { |
3 | open STDIN, '<' , 'whatasha.in' ; |
4 | open STDOUT, '>' , 'whatasha.out' ; |
需要注意的是,Perl中<>
和<STDIN>
其实是不同的,前者等价于<ARGV>
,不过由于通常@ARGV
都为空,二者的效果是一样的。
Ruby
Ruby中也有类似的BEGIN块。
2 | if ENV [ 'USER' ] != 'watashi' |
3 | $stdin = open( 'whatasha.in' ) |
4 | $> = open( 'whatasha.out' , 'w' ) |
需要说明的是,Ruby默认从$<
读入,输出到$>
。前者等价于ARGF,是个只读变量,试图给它赋值将会得到$< is a read-only variable (NameError)
。后者就是$stdout
的alias,所以我们也可以把代码改成。
$stdout = open( 'whatasha.out' , 'w' ) |
当ARGV为空时,从$>
读入就会变成从$stdin
读入。在这一方面Ruby借鉴了Perl的许多行为。
D
2 | if ( getenv ( "USER" ) != "watashi" ) { |
3 | stdin = File( "whatasha.in" , "r" ); |
4 | stdout = File( "whatasha.out" , "w" ); |
Go
2 | if (os.Getenv( "USER" ) != "watashi" ) { |
3 | os.Stdin, _ = os.Open( "whatasha.in" ) |
4 | os.Stdout, _ = os.Create( "whatasha.out" ) |
Scheme
Codeforces并不支持Scheme,倒是ZOJ支持。
1 | (define (freopen input output) |
2 | ( set -current-input-port (open-file input "r" )) |
3 | ( set -current-output-port (open-file output "w" ))) |
5 | ( if (not (equal? (getenv "USER" ) "watashi" )) |
6 | (freopen "whatasha.in" "whatasha.out" )) |
PHP
从下面的语言开始,就没有那么freopen的解决方案了。PHP不允许我们重新定义常量,所以不能修改STDIN
(php://stdin
)和STDOUT
(php://stdout
)于是我们只能定义个变量$STDIN再做个全局替换了。对于输出到php://output
的print
和echo
,还要用ob_start
处理一下。
1 | if ( getenv ( 'USER' ) == 'watashi' ) { |
4 | $STDIN = fopen ( 'whatasha.in' , 'r' ); |
5 | $STDOUT = fopen ( 'whatasha.out' , 'w' ); |
6 | ob_start( function ( $buffer ) { |
8 | fwrite( $STDOUT , $buffer ); |
Scala
Scala写一个”main”的方法比较多,不过都可以通过下面的代码来设置scala.Console.{in,out}
。scala.Predef._
中的各种IO函数都是scala.Console._
的alias。
1 | if (util.Properties.userName ! = "watashi" ) { |
3 | new BufferedInputStream( new FileInputStream( "whatasha.in" ))) |
5 | new PrintStream( new FileOutputStream( "whatasha.out" ))) |
不过scala.Console.{in,out}
和java.lang.System.{in,out}
并不像std::c{in,out}
和std{in,out}
那样会保持同步。所以如果用到了其它的IO函数,就可能会有一些问题,需要额外处理。
Java
理想的情况,我们只需要在静态代码块中调用System.set{In,Out}
就好了。这也是一个非常freopen的解决方案。
4 | System.setIn( new BufferedInputStream( new FileInputStream( "whatasha.in" ))); |
5 | System.setOut( new PrintStream( new FileOutputStream( "whatasha.out" ))); |
6 | } catch (IOException ex) { |
可遗憾的是由于Codeforces的权限限制,上面的代码会抛出java.security.AccessControlException: access denied (java.lang.RuntimePermission setIO)
。于是我们只好退而求其次,定义两个变量in
和out
,将原来的System.in
和System.out
全局替换掉。
3 | static PrintStream out; |
6 | if (System.getProperty( "ONLINE_JUDGE" ) == null ) { |
7 | in = new Scanner(System.in); |
10 | String file = "whatasha" ; |
12 | in = new BufferedInputStream( new FileInputStream(file + ".in" )); |
13 | out = new PrintStream( new FileOutputStream(file + ".out" )); |
14 | } catch (IOException ex) { |
这里套一层BufferedInputStream有时候是必要的,否则可能会TLE。更好的办法也许是伪造一个System,毕竟全局替换是很容易有所遗漏的。
2 | public static InputStream in = java.lang.System.in; |
3 | public static PrintStream out = java.lang.System.out; |
4 | public static PrintStream err = java.lang.System.err; |
6 | public static void exit( int status) { |
7 | java.lang.System.exit(status); |
11 | if (java.lang.System.getProperty( "ONLINE_JUDGE" ) != null ) { |
13 | String file = "whatasha" ; |
14 | in = new BufferedInputStream( new FileInputStream(file + ".in" )); |
15 | out = new PrintStream( new FileOutputStream(file + ".out" )); |
16 | } catch (IOException ex) { |
当然,Codeforces上那些专用Java的选手早有模板来处理Java的各种IO问题了。
C#
类似Java,理想的情况我们可以这么做:
3 | Console.SetIn( new StreamReader( "whatasha.in" )); |
4 | Console.SetOut( new StreamWriter( "whatasha.out" )); |
5 | (Console.Out as StreamWriter).AutoFlush = true ; |
不过与Java可以同时有多个静态代码块不同,C#只能有一个静态构造函数。所以可以考虑写成下面这样,避免和已有的静态构造函数冲突。
2 | public static bool __freopn = ((Func< string , string , bool >)( |
4 | Console.SetIn( new StreamReader(input)); |
5 | Console.SetOut( new StreamWriter(output)); |
6 | (Console.Out as StreamWriter).AutoFlush = true ; |
8 | }))( "whatasha.in" , "whatasha.out" ); |
不过类似java的情况,SetIn
和SetOut
会得到Runtime error。于是我们还可以类似地伪造一个Console
3 | class FakeConsole: StreamWriter { |
6 | public FakeConsole( string input, string output): base (output) { |
7 | reader = new StreamReader(input); |
15 | public string ReadLine() { |
16 | return reader.ReadLine(); |
20 | static FakeConsole Console = new FakeConsole( "positive.in" , "positive.out" ); |
顺带一提,之前在Codeforces一旦用C#进行文件操作就会抛出System.Security.SecurityException: Request for the permission of type 'System.Security.Permissions.FileIOPermission
。看来MikeMirzayanov同学已经fix了这个bug。
OCaml
OCaml并没有类似freopen
功能的函数,不过如果有Unix
模块的支持的话,通过openfile
和dup2
可以实现同样的功能。事实上glibc的freopen
就是通过dup2
系统调用实现的,而msvcrt的freopen
的源代码则较为复杂一些。
4 | let freopen = fun input output -> |
5 | let fin = openfile input [O_RDONLY] 0 in |
7 | let fout = openfile output [O_WRONLY; O_CREAT; O_TRUNC] 0o644 in |
12 | if getlogin () <> "watashi" then |
13 | freopen "whatasha.in" "whatasha.out" |
按照上面的规律,你应该已经猜到了这个方法在Codeforces上是不work的。如果所有的输入输出都是通过Scanf.scanf
和Printf.printf
完成的话,还可以做一个类似的全局替换。但如果还调用了Pervasives
中的IO函数,那就要麻烦不少了,基本就是ad-hoc地修改了。
Haskell
Haskell也没有类似freopen功能的函数(别说FFI)。但在Haskell中为了避免超时,通常要把String换成Data.ByteString.Char8,甚至有时候还要把getLine换成getContents。同时,作为纯函数式语言,IO Monad天然地把IO操作给隔离了出来,所以许多情况改起来还不算太麻烦。
假如原来的程序用的是interact,那么事情就更简单了。
4 | import qualified Data . ByteString . Char8 as C |
6 | interact ' :: (C . ByteString -> String ) -> IO () |
8 | interact ' = hInteract "whatasha.in" "whatasha.out" |
10 | hInteract :: FilePath -> FilePath -> (C . ByteString -> String ) -> IO () |
11 | hInteract input output f = |
12 | liftM f (C . readFile input) >>= writeFile output |
14 | interact ' = interact . ( . C . pack) |
63
//main.c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void main()
{
#ifndef __WATASHI__
freopen("whatasha.in", "r", stdin);
freopen("main_c.out", "w", stdout);
#endif
int n;
scanf("%d", &n);
printf("n=%d\n",n);
printf("out=1024\n");
}
//main.cpp
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#ifndef __WATASHI__
struct $ {
$(const char* input, const char* output) {
freopen(input, "r", stdin);
freopen(output, "w", stdout);
}
} $("whatasha.in", "whatasha.out");
#endif
void main()
{
int n;
cin >> n;
cout << "n="<<n <<endl;
cout << "out=1024" <<endl;
}
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
public class Test_Reopen {
static {
//init();
}
public static void main(String[] args) throws IOException {
init();
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
System.out.println(reader.readLine());
System.out.println("java=1024");
}
private static void exit() {
try {
System.out.println("exit");
System.in.close();
System.out.flush();
System.out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void init() {
try {
System.out.println("start");
System.setIn(new BufferedInputStream(new FileInputStream("whatasha.in")));
System.setOut(new PrintStream(new FileOutputStream("java.out")));
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
exit();
}
}));
} catch (IOException ex) {
ex.printStackTrace();
}
}
}