この記事はSLP-KBIT AdventCalendar202312日目の記事です。
目次
・はじめに
・ローカルテスト手順
・seeds.txtに乱数を書く
・提出用コードをファイル入出力用にする(C++)
・vis.rsをn回実行する(Rust)
・おわりに
はじめに
こんにちは、kaede9030です。先週行われたAHC027のローカルテスト環境構築について書きます。 最低限 のため並列処理とかはしません。
AHCやローカルテスト環境の必要性についてはこの記事を読んでください。
ローカルテスト手順
まずRust実行環境を用意し、問題文のページからローカル版ツールを入手しましょう。
tools
|---in
|---src
|---target
|---seeds.txt
|---README.html
|---その他.なにか
その中のREADME.html
を読み、ローカルテストの手順を考えますが、とりあえず動くことを確かめます。
入力生成
tools
ディレクトリに移動し、入力生成コマンド
cargo run -r --bin gen seeds.txt
を打つと、in
ディレクトリに0000.txt~0099.txt
ができると思います。
ビジュアライザ実行
tools
ディレクトリ内にin.txt
, out.txt
を作り、それぞれ問題文の入力例と出力例を書き、ビジュアライザ実行コマンド
cargo run -r --bin vis in.txt out.txt
を打つと、tools
ディレクトリにvis.html
が生成され、標準出力には
Score = 3302174
と表示されるはずです。
さて、ここまでまとめると
-
入力.txt
を用意する -
出力.txt
を用意する(コンテスト提出用コードをファイル操作に対応させる) - 上2つを使ってビジュアライザを動かす
↑これらを行えばよさそうです。
seeds.txtに乱数を書く
README.html によると
in ディレクトリに予め生成された seed=0~99 に対する入力ファイルが置かれています。 より多くの入力が欲しい場合は、seeds.txt に欲しい入力ファイルの数だけ乱数seed値(符号なし64bit整数値)を記入し、以下のコマンドを実行します。
cargo run -r --bin gen seeds.txt
らしいので、tools
ディレクトリに乱数をseeds.txt
に書き込むプログラム
//言語は何でもいい
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main(void){
int n=1000; //生成したい数
FILE *fp;
fp=fopen("seeds.txt","w");
if(fp==NULL){
printf("cannot open file\n");
exit(1);
}
srand((unsigned)time(NULL)); //今更だけどこれ符号なし32bitでは…
for(int i=0;i<n;i++){
unsigned tmp=rand();
fprintf(fp,"%u\n",tmp);
}
fclose(fp);
return 0;
}
を置き、実行します。先のコマンド(cargo run -r --bin gen seeds.txt
)も実行。
提出用コードをファイル入出力用にする
自分が普段使う言語で提出用コードを書き、先ほどの入力ファイル(0000.txt
など)を受け取り、答えを出力ファイル(0.txt
など)に書き込みます。このファイルはin
ディレクトリに置いてください。
こんな感じ↓
#include<bits/stdc++.h> //問題文のDFSするだけACコード
using namespace std;
int N;
int di[4]={0,1,0,-1},dj[4]={1,0,-1,0};
string dir="RDLU";
vector<string> h(39);
vector<string> v(40);
vector<vector<int>> d(40,vector<int>(40));
string ans="";
void dfs(int i,int j,vector<vector<bool>>* vis){
(*vis)[i][j]=true;
for(int k=0;k<4;k++){
int ii=i+di[k],jj=j+dj[k];
if(0<=ii&&ii<N&&0<=jj&&jj<N&&!(*vis)[ii][jj]){
if((di[k]==0&&v[i][min(j,jj)]=='0')||(dj[k]==0&&h[min(i,ii)][j]=='0')){
ans+=dir[k];
dfs(ii,jj,vis);
ans+=dir[(k+2)%4];
}
}
}
}
int main(void){
for(int i=0;i<1000;i++){ //1000個分ファイル処理
string filename="";
string num=to_string(i);
int x=4-num.size();
for(int j=0;j<x;j++){
filename+="0";
}
filename+=num+".txt";
ifstream infile(filename);
if(infile.is_open()){
infile>>N;
for(int j=0;j<N-1;j++){
infile>>h[j];
}
for(int j=0;j<N;j++){
infile>>v[j];
}
for(int j=0;j<N;j++){
for(int k=0;k<N;k++){
infile>>d[j][k];
}
}
infile.close();
vector<vector<bool>> visited(N,vector<bool>(N,false));
dfs(0,0,&visited);
}else{
cout<<"cannot open "<<filename<<endl;
exit(1);
}
string fname=to_string(i)+".txt";
ofstream outfile(fname);
if(outfile.is_open()){
outfile<<ans<<endl;
outfile.close();
ans="";
}
}
//cin>>N; //提出用
//for(int i=0;i<N-1;i++){
// cin>>h[i];
//}
//for(int i=0;i<N;i++){
// cin>>v[i];
//}
//for(int i=0;i<N;i++){
// for(int j=0;j<N;j++){
// cin>>d[i][j];
// }
//}
//vector<vector<bool>> visited(N,vector<bool>(N,false));
//dfs(0,0,&visited);
//cout<<ans<<endl;
return 0;
}
実行するとin
ディレクトリに.txt
ファイルがいっぱいできると思います。
入力ファイル0000.txt
と出力ファイル0.txt
が対応しています。tools
ディレクトリのin.txt
, out.txt
は使わないので消しても構いません。
vis.rsをn回実行する
ここで躓きました。まず思いついた方法は、提出コードをテストケースの数だけ繰り返したように、vis.rs
の中にループの処理を追記するやり方です。
fn main(){
for i in 0..1000{ //テストケースの数だけループ
//vis.rsにもともとある処理+α
}
}
この方法は不採用です。理由は、私がRustの知識が皆無だからです。公式のツールをいじるとか怖くてできません。
他の方法は?
vis.rsをいじりたくないならcargo~
コマンドをn回実行したらどうか?と思い「Rust コマンド実行」などと検索しているとstd::process::Commandなるものを見つけました。
実装
自力ですべて実装するのは厳しいため、ChatGPT先生を頼ります。文字列難しい
このファイルはtools
ディレクトリに置いてください。
use std::process::Command;
fn main() {
let mut ave: u64=0; //u128のほうが安心
for i in 0..1000{
let infile=format!("{:04}",i); //"0000"~"0999"
let outfile=i.to_string(); //"0"~"999"
let inpath=format!("in/{}{1}",infile,".txt");
let outpath=format!("in/{}{1}",outfile,".txt");
let output=Command::new("cargo") //ターミナルに打ち込んでいたコマンド
.arg("run")
.arg("-r")
.arg("--bin")
.arg("vis")
.arg(inpath)
.arg(outpath)
.output()
.expect("Failed");
//ここからほとんどChatGPTが一晩でやってくれました
if let Ok(stdout) = String::from_utf8(output.stdout) {
let s=stdout.clone(); //所有権が怖いためコピーしたけどいらない?
println!("vis.rs output: {}", s);
if let Some(score_str) = s.split_once('=') {
if let Ok(score) = score_str.1.trim().parse::<u64>() {
println!("Score: {}", score);
ave+=score;
} else {
println!("Error: Unable to parse score as integer");
}
} else {
println!("Error: Invalid format for score");
}
}
} //私が書くとこんなに丁寧にエラー処理しない()
println!("average score is {}",ave/1000);
}
何をしているかとても簡単に説明すると
cargo run -r --bin vis in/0000.txt in/0.txt
このコマンドを繰り返し実行し、標準出力に
vis.rs output: Score = 123456789
Score: 123456789
と、スコアを表示するか、エラーを表示します。最後に平均スコアも表示されます。
おわりに
汚コード、過不足しかない説明の中、ここまで読んでくださりありがとうございます。
これだけローカルテスト環境について書いておきながら、私は問題文のサンプルACコードしか提出していません。私がRust公式文書を漁っていた時間は何だったのでしょうか..
茶下位こーだーにしては頑張ったということでこの記事はおしまいです。
他の人のAHC環境構築記事などいろいろ
参考資料
https://doc.rust-jp.rs/book-ja/
https://doc.rust-jp.rs/rust-by-example-ja/std_misc/process.html
昨日の記事:https://qiita.com/nao_bc/private/20f802ea9ac41a634cb6
明日の記事:https://qiita.com/saimagu/private/0c939e67c896cd74ac7d