0
Help us understand the problem. What are the problem?

posted at

updated at

PowerShell「私はAtCoder参加できないの…?」

はじめに

PowerShellではAtCoderで提出することができないので、PowerShellユーザは他言語で提出する必要がある。だが、PowerShellでもやってみたい・・・。
と、いうことでPowerShellで書いたコードを手元でランダム生成したケースでテストして擬似的にバーチャル参加しちゃおう!という試みをした。今回は簡単な問題で試験をするためにこの問題を利用した。なお、模範解答生成用のコードはJavaで記述した。

ジャッジシステムの構築

まずはじめにジャッジ用のディレクトリを作成しておくと便利かなと思って作業ディレクトリJudgeを生成し、その中に模範解答用のJavaのclassファイル、判定するコード(今回はMain.ps1としている)を突っ込む。

Judge.ps1
"準備中・・・"
New-Item Judge -ItemType Directory | Out-Null   #Judgeの生成
javac -encoding UTF-8 Main.java   #Javaのコンパイル(メモ帳で作ったのでUTF-8)
Move-Item Main.class Judge | Out-Null   #classファイルの移動
Copy-Item Main.ps1 -Recurse Judge | Out-Null   #Main.ps1をJudge内にコピー

次に、テストケース生成用のps1を実行し、Judge内に移動する。

Judge.ps1
./MakeTest.ps1   #テスト生成用
cd Judge   #ディレクトリ移動
"完了。`r`nジャッジします。`r`n"

MakeTest.ps1の中身は以下のように記述した。

MakeTest.ps1
for([int]$i = 0; $i -lt 10; $i++){
	$n = Get-Random -Maximum 100   #最大値が100である乱数を生成
	Write-Output $n > Judge\test${i}.txt   #txtに出力
}

さて、ここからがジャッジシステムの心臓部である、Main.ps1に情報を渡して返ってきた内容を模範と照らし合わせるというコードであるが、まずは結果を管理する変数を準備する。今回はデバッグするときに扱いやすいかなと思って配列で管理することにした。

Judge.ps1
[string[]]$result = ("WA","WA","WA","WA","WA","WA","WA","WA","WA","WA")   #初期値はWA
[int[]]$resultTime = (0,0,0,0,0,0,0,0,0,0)   #初期値は0

ジャッジの流れは以下のようにしてみた。
①javaに実行させてtxtに出力(以下txt1)
②Main.ps1に実行させて違うtxtに出力(以下txt2)
③実行時間をresultTimeに格納(この時点で2000msを超えていたらWA→TLE)
④txt1とtxt2を一行ずつstring型配列として取得して比較
⑤全て同じならWA→AC
⑥実行時間と共に結果を出力!

では、実際にコードを見ていこう。

Judge.ps1
for([int]$i = 0; $i -lt 10; $i++){
	Write-Host "test${i}.txt	:" -NoNewLine
	cat test${i}.txt | java Main > result0${i}.txt   #Java実行(結果はtxtに)
	$timest = date   #実行開始時刻を記録
	try{
		./Main.ps1 (Get-Content test${i}.txt) > result1${i}.txt   #結果をtxtに出力
	}catch{
		$result[$i] = "RE"   #実行中に例外が起きたならRE
	}
	$timefi = date   #実行終了時刻の記録
	if(($timefi - $timest).TotalMilliseconds-ge2000){
		$result[$i] = "TLE"   #時間制限超過ならTLE
	}
	$resultTime[$i] = ($timefi - $timest).TotalMilliseconds   #実行時間を格納
	[string[]]$f1 = Get-Content result0${i}.txt   #模範解答の取得
	[string[]]$f2 = Get-Content result1${i}.txt   #実行結果の取得
	[boolean]$isTrue = $True   #txt比較用のboolean型配列
	if($f1.Length -eq $f2.Length){
		for([int]$j = 0; $j -lt $f1.Length; $j++){
			if($f1[$j] -ne $f2[$j]){
				$isTrue = $False   #異なる出力内容ならFalseに
			}
		}
	}
	else{
		$isTrue = $False   #そもそも出力された行数が異なるならFalse
	}
	if($isTrue -and $result[$i] -eq "WA"){
		$result[$i] = "AC"   #結果が正しくてRE、TLEでないならAC
	}
	Write-Host $result[$i] -NoNewLine   #結果の出力
	Write-Host "($($resultTime[$i] + 1)ms)"   #実行時間の出力
}

MLEは面倒なので実装しなかったが、他の判定はできるようになっている。
お好みで以下の記述を最後に入れても良いだろう。

Judge.ps1
cd ..   #ディレクトリ移動
Remove-Item -Recurse Judge   #ジャッジ用ディレクトリを削除

提出するコードの記述方法

さて、最初はPowerShellで入力値を読み込もうとして以下のように書いてみた。

Main.ps1
[int]$a = ReadHost   #受け取り
$a += 5   #変数に5を加算
return $a   #値を返す

が、何故か読み込まない。
理由はわからないが、多分コマンドプロンプト上に書き込まれたことしか読み込んでくれない(?)。

じゃあArgsに渡してしまおう!ということで以下のように書き直した。

Main.ps1
[int]$a = $Args[0]   #受け取り
$a += 5   #変数に5を加算
return $a   #値を返す

Judge.ps1の方もArgsに渡すように書き直してある。これで情報の伝達ができるようになった。

あとはちゃんとAC、WA、TLE、REと判定されるか見るために以下の記述に書き換えた。

Main.ps1
[int]$a = $Args[0]   #受け取り
$a += 5   #変数に5を加算
for([int]$i = 0; $i -le 1000000; $i++){}
if((Get-Random -Maximum 100) -lt 50){
	$a -= 100   #確率1/2で変数から100を引く
}
elseif((Get-Random -Maximum 100) -lt 50){
	throw "Bad thing happened"   #確率1/4で例外を返す
}
elseif((Get-Random -Maximum 100) -lt 50){
	sleep 2   #確率1/8で2秒以上実行に時間をかける
}
return $a   #値を返す

これで確認ができるだろう。

おわり

こんな感じでPowerShellでも擬似的にジャッジができるようになった。
ただ、これでは問題毎にMakeTest.ps1とJudge.ps1の制限時間部分を書き換える必要が出てくる。そこは面倒なので各々に任せたい。

良いPowerShell Lifeを!

Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
0
Help us understand the problem. What are the problem?